From 405a2964da9af043442a5bc51713670e58a95146 Mon Sep 17 00:00:00 2001 From: Cyber <44707958+CyberL1@users.noreply.github.com> Date: Sat, 4 Sep 2021 23:21:14 +0200 Subject: [PATCH 01/90] Add token authorization method to swagger openapi --- api/assets/openapi.json | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/api/assets/openapi.json b/api/assets/openapi.json index 5244da36..fa527911 100644 --- a/api/assets/openapi.json +++ b/api/assets/openapi.json @@ -32,7 +32,8 @@ "description": "User not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } - } + }, + "security": [{ "Token": [] }] } }, "/users/@me": { @@ -46,7 +47,8 @@ "description": "Authenticated user", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserPublic" } } } } - } + }, + "security": [{ "Token": [] }] } } }, @@ -1470,9 +1472,14 @@ } }, "requestBodies": {}, - "securitySchemes": {}, + "securitySchemes": { + "Token": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + }, "links": {}, "callbacks": {} - }, - "security": [] -} + } +} \ No newline at end of file From ace950482e3d37f2f6c99e659a40b27e473eef53 Mon Sep 17 00:00:00 2001 From: xnacly Date: Sun, 5 Sep 2021 13:43:01 +0200 Subject: [PATCH 02/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c9e6cec..a281aa3f 100644 --- a/README.md +++ b/README.md @@ -32,4 +32,4 @@ This repository contains: ## [Download](https://github.com/fosscord/fosscord-server/releases) -- _is not done yet_ +- _Work in progress_ From 2d26241a824651552bf795aaaa6321133822a2d6 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 7 Sep 2021 22:54:31 +0200 Subject: [PATCH 03/90] Update User.ts --- util/src/entities/User.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index f44b37b3..9394f9e8 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -250,13 +250,18 @@ export class UserFlags extends BitField { PARTNERED_SERVER_OWNER: BigInt(1) << BigInt(1), HYPESQUAD_EVENTS: BigInt(1) << BigInt(2), BUGHUNTER_LEVEL_1: BigInt(1) << BigInt(3), + MFA_SMS: BigInt(1) << BigInt(4), + PREMIUM_PROMO_DISMISSED: BigInt(1) << BigInt(5), 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), + TRUST_AND_SAFETY: BigInt(1) << BigInt(11), SYSTEM: BigInt(1) << BigInt(12), + HAS_UNREAD_URGENT_MESSAGES: BigInt(1) << BigInt(13), BUGHUNTER_LEVEL_2: BigInt(1) << BigInt(14), + UNDERAGE_DELETED: BigInt(1) << BigInt(15), VERIFIED_BOT: BigInt(1) << BigInt(16), EARLY_VERIFIED_BOT_DEVELOPER: BigInt(1) << BigInt(17), }; From 0bbb1bad5af7a289d78700d48af482d9339dce6f Mon Sep 17 00:00:00 2001 From: Stilic <63605602+Stilic@users.noreply.github.com> Date: Wed, 8 Sep 2021 13:34:37 +0200 Subject: [PATCH 04/90] Fix upload size --- api/src/Server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/Server.ts b/api/src/Server.ts index 0f444f86..b9ca3fba 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -37,7 +37,7 @@ export class FosscordServer extends Server { await initEvent(); this.app.use(CORS); - this.app.use(BodyParser({ inflate: true, limit: 1024 * 1024 * 10 })); // 10MB + this.app.use(BodyParser({ inflate: true, limit: "10mb" })); const app = this.app; const api = Router(); // @ts-ignore From 5483ea1ce9d08cd57b7cf331c704a7fe7b7db532 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Wed, 8 Sep 2021 18:42:58 +0200 Subject: [PATCH 05/90] Increased CDN max upload to 10mb --- cdn/src/Server.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts index f4a6b576..63499ce4 100644 --- a/cdn/src/Server.ts +++ b/cdn/src/Server.ts @@ -2,6 +2,7 @@ import { Server, ServerOptions } from "lambert-server"; import { Config, initDatabase } from "@fosscord/util"; import path from "path"; import avatarsRoute from "./routes/avatars"; +import bodyParser from "body-parser"; export interface CDNServerOptions extends ServerOptions {} @@ -26,6 +27,8 @@ export class CDNServer extends Server { res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*"); next(); }); + this.app.use(bodyParser.json({ inflate: true, limit: "10mb" })); + this.app.use(bodyParser.urlencoded({ inflate: true, limit: "10mb" })); await this.registerRoutes(path.join(__dirname, "routes/")); From ae59f4bafbeadd17bbe2e3bbd22dc6cbe17d6128 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Thu, 9 Sep 2021 07:50:00 +0200 Subject: [PATCH 06/90] Removed urlencoded bodyparser --- cdn/src/Server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts index 63499ce4..5c4a8ae5 100644 --- a/cdn/src/Server.ts +++ b/cdn/src/Server.ts @@ -28,7 +28,6 @@ export class CDNServer extends Server { next(); }); this.app.use(bodyParser.json({ inflate: true, limit: "10mb" })); - this.app.use(bodyParser.urlencoded({ inflate: true, limit: "10mb" })); await this.registerRoutes(path.join(__dirname, "routes/")); From c505e3dda4588b4aaf5c353b112aba88aaae0520 Mon Sep 17 00:00:00 2001 From: conner <59223342+Intevel@users.noreply.github.com> Date: Thu, 9 Sep 2021 16:40:47 +0200 Subject: [PATCH 07/90] Revert "Increased CDN max upload to 10mb, fix #318" --- cdn/src/Server.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts index 5c4a8ae5..f4a6b576 100644 --- a/cdn/src/Server.ts +++ b/cdn/src/Server.ts @@ -2,7 +2,6 @@ import { Server, ServerOptions } from "lambert-server"; import { Config, initDatabase } from "@fosscord/util"; import path from "path"; import avatarsRoute from "./routes/avatars"; -import bodyParser from "body-parser"; export interface CDNServerOptions extends ServerOptions {} @@ -27,7 +26,6 @@ export class CDNServer extends Server { res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*"); next(); }); - this.app.use(bodyParser.json({ inflate: true, limit: "10mb" })); await this.registerRoutes(path.join(__dirname, "routes/")); From e4e10b18d9e2353219152f9f6a93b1b08dbe21ac Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Thu, 9 Sep 2021 16:55:02 +0200 Subject: [PATCH 08/90] Increased CDN max upload to 10mb --- cdn/src/Server.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts index f4a6b576..5c4a8ae5 100644 --- a/cdn/src/Server.ts +++ b/cdn/src/Server.ts @@ -2,6 +2,7 @@ import { Server, ServerOptions } from "lambert-server"; import { Config, initDatabase } from "@fosscord/util"; import path from "path"; import avatarsRoute from "./routes/avatars"; +import bodyParser from "body-parser"; export interface CDNServerOptions extends ServerOptions {} @@ -26,6 +27,7 @@ export class CDNServer extends Server { res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*"); next(); }); + this.app.use(bodyParser.json({ inflate: true, limit: "10mb" })); await this.registerRoutes(path.join(__dirname, "routes/")); From 3cd404eb422fb2bac358264227839f15ba241f45 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Thu, 9 Sep 2021 18:32:36 +0200 Subject: [PATCH 09/90] Fix relationships get query --- api/src/routes/users/@me/relationships.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 8d6d8c9e..995b0244 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -19,7 +19,7 @@ const router = Router(); const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; router.get("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["relationships"] }); + const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships"] }); return res.json(user.relationships); }); From c073adf3ba3a46651a43cf995383071b2496c918 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Sun, 12 Sep 2021 19:27:29 +0200 Subject: [PATCH 10/90] Add dummy missing routes --- api/src/routes/applications/detectable.ts | 10 ++++++++++ api/src/routes/outbound-promotions.ts | 10 ++++++++++ .../routes/users/@me/affinities/{user.ts => users.ts} | 2 +- .../users/@me/applications/#app_id/entitlements.ts | 10 ++++++++++ api/src/routes/users/@me/billing/country-code.ts | 10 ++++++++++ api/src/routes/users/@me/billing/subscriptions.ts | 10 ++++++++++ 6 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 api/src/routes/applications/detectable.ts create mode 100644 api/src/routes/outbound-promotions.ts rename api/src/routes/users/@me/affinities/{user.ts => users.ts} (95%) create mode 100644 api/src/routes/users/@me/applications/#app_id/entitlements.ts create mode 100644 api/src/routes/users/@me/billing/country-code.ts create mode 100644 api/src/routes/users/@me/billing/subscriptions.ts diff --git a/api/src/routes/applications/detectable.ts b/api/src/routes/applications/detectable.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/applications/detectable.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/outbound-promotions.ts b/api/src/routes/outbound-promotions.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/outbound-promotions.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/users/@me/affinities/user.ts b/api/src/routes/users/@me/affinities/users.ts similarity index 95% rename from api/src/routes/users/@me/affinities/user.ts rename to api/src/routes/users/@me/affinities/users.ts index 0790a8a4..db67ab71 100644 --- a/api/src/routes/users/@me/affinities/user.ts +++ b/api/src/routes/users/@me/affinities/users.ts @@ -3,7 +3,7 @@ import { Router, Response, Request } from "express"; const router = Router(); router.get("/", (req: Request, res: Response) => { - // TODO: + //TODO res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); }); diff --git a/api/src/routes/users/@me/applications/#app_id/entitlements.ts b/api/src/routes/users/@me/applications/#app_id/entitlements.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/users/@me/applications/#app_id/entitlements.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/users/@me/billing/country-code.ts b/api/src/routes/users/@me/billing/country-code.ts new file mode 100644 index 00000000..ac3653a2 --- /dev/null +++ b/api/src/routes/users/@me/billing/country-code.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json({ "country_code": "US" }).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/users/@me/billing/subscriptions.ts b/api/src/routes/users/@me/billing/subscriptions.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/users/@me/billing/subscriptions.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file From 60535f5159a8674874c0a2e319d46400184bd22e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 12 Sep 2021 21:09:29 +0200 Subject: [PATCH 11/90] :art: remove long relatives paths -> short module paths --- api/package-lock.json | Bin 777362 -> 779701 bytes api/package.json | 3 +- api/scripts/tsconfig-paths-bootstrap.js | 10 ++ api/src/index.ts | 11 +- api/src/middlewares/ErrorHandler.ts | 2 +- api/src/middlewares/RateLimit.ts | 2 +- api/src/routes/auth/login.ts | 2 +- api/src/routes/auth/register.ts | 4 +- api/src/routes/channels/#channel_id/index.ts | 2 +- .../routes/channels/#channel_id/invites.ts | 4 +- .../#channel_id/messages/#message_id/ack.ts | 2 +- .../#channel_id/messages/#message_id/index.ts | 4 +- .../#channel_id/messages/bulk-delete.ts | 2 +- .../channels/#channel_id/messages/index.ts | 6 +- .../channels/#channel_id/permissions.ts | 2 +- .../routes/channels/#channel_id/webhooks.ts | 2 +- api/src/routes/guilds/#guild_id/bans.ts | 5 +- api/src/routes/guilds/#guild_id/channels.ts | 2 +- api/src/routes/guilds/#guild_id/index.ts | 4 +- .../#guild_id/members/#member_id/index.ts | 4 +- .../routes/guilds/#guild_id/members/index.ts | 2 +- api/src/routes/guilds/#guild_id/regions.ts | 6 +- api/src/routes/guilds/#guild_id/roles.ts | 2 +- api/src/routes/guilds/#guild_id/templates.ts | 4 +- api/src/routes/guilds/#guild_id/vanity-url.ts | 2 +- .../#guild_id/voice-states/#user_id/index.ts | 8 +- .../#guild_id/voice-states/@me/index.ts | 8 +- .../routes/guilds/#guild_id/welcome_screen.ts | 2 +- .../routes/guilds/#guild_id/widget.json.ts | 2 +- api/src/routes/guilds/#guild_id/widget.ts | 2 +- api/src/routes/guilds/index.ts | 2 +- api/src/routes/guilds/templates/index.ts | 2 +- api/src/routes/users/#id/index.ts | 2 +- api/src/routes/users/#id/profile.ts | 2 +- api/src/routes/users/@me/channels.ts | 6 +- api/src/routes/users/@me/index.ts | 4 +- api/src/routes/users/@me/relationships.ts | 2 +- api/src/routes/users/@me/settings.ts | 2 +- api/src/routes/voice/regions.ts | 6 +- api/src/test/password_test.ts | 12 +- api/src/util/index.ts | 11 ++ api/src/util/passwordStrength.ts | 2 +- api/tests/automatic.test.js | 0 api/tsconfig.json | 7 +- bundle/package-lock.json | Bin 66635 -> 69487 bytes bundle/package.json | 5 +- bundle/tsconfig-paths-bootstrap.js | 14 +++ cdn/package.json | 2 +- cdn/scripts/tsconfig-paths-bootstrap.js | 10 ++ cdn/src/routes/attachments.ts | 114 ++++++++++-------- cdn/src/routes/avatars.ts | 60 +++++---- cdn/src/routes/external.ts | 5 +- cdn/tsconfig.json | 7 +- gateway/package-lock.json | Bin 119032 -> 153148 bytes gateway/package.json | 3 +- gateway/scripts/tsconfig-paths-bootstrap.js | 10 ++ gateway/src/events/Close.ts | 2 +- gateway/src/events/Connection.ts | 23 ++-- gateway/src/events/Message.ts | 13 +- gateway/src/index.ts | 3 + gateway/src/listener/listener.ts | 10 +- gateway/src/opcodes/Heartbeat.ts | 8 +- gateway/src/opcodes/Identify.ts | 10 +- gateway/src/opcodes/LazyRequest.ts | 6 +- gateway/src/opcodes/PresenceUpdate.ts | 4 +- gateway/src/opcodes/RequestGuildMembers.ts | 4 +- gateway/src/opcodes/Resume.ts | 6 +- gateway/src/opcodes/VoiceStateUpdate.ts | 46 +++++-- gateway/src/opcodes/index.ts | 4 +- gateway/src/opcodes/instanceOf.ts | 4 +- gateway/src/util/Send.ts | 2 +- gateway/src/util/WebSocket.ts | 4 +- gateway/src/util/index.ts | 5 + gateway/tsconfig.json | 7 +- util/package-lock.json | Bin 544874 -> 547133 bytes util/package.json | 1 + util/src/interfaces/Event.ts | 45 +++++++ util/src/util/Constants.ts | 2 +- util/src/util/Database.ts | 2 +- 79 files changed, 392 insertions(+), 220 deletions(-) create mode 100644 api/scripts/tsconfig-paths-bootstrap.js create mode 100644 api/src/util/index.ts create mode 100644 api/tests/automatic.test.js create mode 100644 bundle/tsconfig-paths-bootstrap.js create mode 100644 cdn/scripts/tsconfig-paths-bootstrap.js create mode 100644 gateway/scripts/tsconfig-paths-bootstrap.js create mode 100644 gateway/src/util/index.ts diff --git a/api/package-lock.json b/api/package-lock.json index 7b1e000b2e7f3f30e018757d669c8227909f622b..8b2c93f8d7c14ba3e4cb65fa7d3cedde0be7f413 100644 GIT binary patch delta 1154 zcmcK2TS!xJ90zc=b9T;lmgdS!UWRj7fxDf#xw#i5I$(p zS`wvYa4#utK=}W2)U>`Ovu`y1?fnIf0k%wNtJfS3hJ4*jZzy%kWU!=C-8nYdW3oG= z`XP(k<0yrrMpmV|r_lZ*QIZ4wH>??6C$SwT7%Y8~%J9t1k+dc- o3J<*EI@nYb9WbttV$ifEY6ituaSb@uL~=m>;MK+DpmaI^4>ya0Z~y=R delta 154 zcmdmbT7S|`{SB5Zo1<7(u`rrUZ@9^(GWj2~{A3T#z|Ffjo3=Lp-rxRvKO+z`0WmWW zvuyvppLL(m_6^rq*WE+YGIMVc)HeC=I-{%mpHdizQpx<+V*!j+{Y^bScOK% diff --git a/api/package.json b/api/package.json index a501fb15..b762b944 100644 --- a/api/package.json +++ b/api/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "npm run build && jest --coverage --verbose --forceExit ./tests", "test:watch": "jest --watch", - "start": "npm run build && node dist/start", + "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start", "build": "npx tsc -b .", "build-docker": "tsc -p tsconfig-docker.json", "dev": "tsnd --respawn src/start.ts", @@ -86,6 +86,7 @@ "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "supertest": "^6.1.6", + "tsconfig-paths": "^3.11.0", "typeorm": "^0.2.37" }, "jest": { diff --git a/api/scripts/tsconfig-paths-bootstrap.js b/api/scripts/tsconfig-paths-bootstrap.js new file mode 100644 index 00000000..d6ad3c57 --- /dev/null +++ b/api/scripts/tsconfig-paths-bootstrap.js @@ -0,0 +1,10 @@ +const tsConfigPaths = require("tsconfig-paths"); +const path = require("path"); + +const cleanup = tsConfigPaths.register({ + baseUrl: path.join(__dirname, ".."), + paths: { + "@fosscord/api": ["dist/index.js"], + "@fosscord/api/*": ["dist/*"] + } +}); diff --git a/api/src/index.ts b/api/src/index.ts index fe59310f..adc7649c 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,12 +1,3 @@ export * from "./Server"; export * from "./middlewares/"; -export * from "./schema/Ban"; -export * from "./schema/Channel"; -export * from "./schema/Guild"; -export * from "./schema/Invite"; -export * from "./schema/Message"; -export * from "./util/instanceOf"; -export * from "./util/instanceOf"; -export * from "./util/RandomInviteID"; -export * from "./util/String"; -export { check as checkPassword } from "./util/passwordStrength"; +export * from "./util/"; diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index be2586cf..d288f3fb 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,7 +1,7 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { EntityNotFoundError } from "typeorm"; -import { FieldError } from "../util/instanceOf"; +import { FieldError } from "@fosscord/api"; import { ApiError } from "@fosscord/util"; export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index dffbc0d9..d1fd072f 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -1,6 +1,6 @@ import { Config, listenEvent } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; -import { getIpAdress } from "../util/ipAddress"; +import { getIpAdress } from "@fosscord/api"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; // Docs: https://discord.com/developers/docs/topics/rate-limits diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index 7fd0f870..2e2f763d 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { check, FieldErrors, Length } from "../../util/instanceOf"; +import { check, FieldErrors, Length } from "@fosscord/api"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { Config, User } from "@fosscord/util"; diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index a9518e91..d3c85778 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,10 +1,10 @@ import { Request, Response, Router } from "express"; import { trimSpecial, User, Snowflake, Config, defaultSettings } from "@fosscord/util"; import bcrypt from "bcrypt"; -import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; +import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "@fosscord/api"; import "missing-native-js-functions"; import { generateToken } from "./login"; -import { getIpAdress, IPAnalysis, isProxy } from "../../util/ipAddress"; +import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; import { HTTPError } from "lambert-server"; import { In } from "typeorm"; diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 4aa5a5b9..46554d70 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -2,7 +2,7 @@ import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermissi import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel // TODO: Get channel diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index fe22d3bc..c6909fd0 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; -import { random } from "../../../util/RandomInviteID"; +import { check } from "@fosscord/api"; +import { random } from "@fosscord/api"; import { InviteCreateSchema } from "../../../schema/Invite"; import { getPermission, Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util"; import { isTextChannel } from "./messages"; diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index 0fd5f2be..aab51484 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -1,7 +1,7 @@ import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { check } from "../../../../../util/instanceOf"; +import { check } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index 7a00de43..e9588bd3 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -1,8 +1,8 @@ import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { MessageCreateSchema } from "../../../../../schema/Message"; -import { check } from "../../../../../util/instanceOf"; -import { handleMessage, postHandleMessage } from "../../../../../util/Message"; +import { check } from "@fosscord/api"; +import { handleMessage, postHandleMessage } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts index 5c486676..5d7566e1 100644 --- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts @@ -2,7 +2,7 @@ import { Router, Response, Request } from "express"; import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { In } from "typeorm"; const router: Router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index ad590d05..591ebbbe 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -2,11 +2,11 @@ import { Router, Response, Request } from "express"; import { Attachment, Channel, ChannelType, getPermission, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../schema/Message"; -import { check, instanceOf, Length } from "../../../../util/instanceOf"; +import { check, instanceOf, Length } from "@fosscord/api"; import multer from "multer"; import { Query } from "mongoose"; -import { sendMessage } from "../../../../util/Message"; -import { uploadFile } from "../../../../util/cdn"; +import { sendMessage } from "@fosscord/api"; +import { uploadFile } from "@fosscord/api"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; const router: Router = Router(); diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 9c49542b..0465ca31 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -2,7 +2,7 @@ import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, get import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; const router: Router = Router(); // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index e4125879..821a62db 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { check, Length } from "../../../util/instanceOf"; +import { check, Length } from "@fosscord/api"; import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index 31aa2385..86bff6b4 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,9 +1,8 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { getIpAdress } from "../../../util/ipAddress"; -import { BanCreateSchema } from "../../../schema/Ban"; -import { check } from "../../../util/instanceOf"; +import { getIpAdress, check } from "@fosscord/api"; +import { BanCreateSchema } from "@fosscord/api/schema/Ban"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index 5aa1d33d..faeecb76 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -3,7 +3,7 @@ import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; const router = Router(); router.get("/", async (req: Request, res: Response) => { diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 9d302a48..244900ec 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -3,8 +3,8 @@ import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@foss import { HTTPError } from "lambert-server"; import { GuildUpdateSchema } from "../../../schema/Guild"; -import { check } from "../../../util/instanceOf"; -import { handleFile } from "../../../util/cdn"; +import { check } from "@fosscord/api"; +import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 0d62e555..8b04a508 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -11,8 +11,8 @@ import { emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../../../util/instanceOf"; -import { MemberChangeSchema } from "../../../../../schema/Member"; +import { check } from "@fosscord/api"; +import { MemberChangeSchema } from "@fosscord/api/schema/Member"; import { In } from "typeorm"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 0bfd71cb..198d6946 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,6 +1,6 @@ import { Request, Response, Router } from "express"; import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; -import { instanceOf, Length } from "../../../../util/instanceOf"; +import { instanceOf, Length } from "@fosscord/api"; import { MoreThan } from "typeorm"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/api/src/routes/guilds/#guild_id/regions.ts index 212c9bcd..86208b79 100644 --- a/api/src/routes/guilds/#guild_id/regions.ts +++ b/api/src/routes/guilds/#guild_id/regions.ts @@ -1,7 +1,7 @@ -import {Config, Guild, Member} from "@fosscord/util"; +import { Config, Guild, Member } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import {getVoiceRegions} from "../../../util/Voice"; -import {getIpAdress} from "../../../util/ipAddress"; +import { getVoiceRegions } from "@fosscord/api"; +import { getIpAdress } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 6a318688..76dd47c5 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -12,7 +12,7 @@ import { } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { RoleModifySchema, RolePositionUpdateSchema } from "../../../schema/Roles"; import { DiscordApiErrors } from "@fosscord/util"; import { In } from "typeorm"; diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index a7613abf..e9304e11 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -2,8 +2,8 @@ import { Request, Response, Router } from "express"; import { Guild, getPermission, Template } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template"; -import { check } from "../../../util/instanceOf"; -import { generateCode } from "../../../util/String"; +import { check } from "@fosscord/api"; +import { generateCode } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 58940b42..f1887cc0 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,7 +1,7 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { check, Length } from "../../../util/instanceOf"; +import { check, Length } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 02951f81..447e15c1 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -1,15 +1,15 @@ -import { check } from "../../../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { VoiceStateUpdateSchema } from "../../../../../schema"; import { Request, Response, Router } from "express"; -import { updateVoiceState } from "../../../../../util/VoiceState"; +import { updateVoiceState } from "@fosscord/api"; const router = Router(); router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; const { guild_id, user_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id, user_id) + await updateVoiceState(body, guild_id, req.user_id, user_id); return res.sendStatus(204); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts b/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts index 42ba543e..b637ff66 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts @@ -1,15 +1,15 @@ -import { check } from "../../../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { VoiceStateUpdateSchema } from "../../../../../schema"; import { Request, Response, Router } from "express"; -import { updateVoiceState } from "../../../../../util/VoiceState"; +import { updateVoiceState } from "@fosscord/api"; const router = Router(); router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; const { guild_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id) + await updateVoiceState(body, guild_id, req.user_id); return res.sendStatus(204); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts index defbcd40..7ca49b4e 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts @@ -2,7 +2,7 @@ import { Request, Response, Router } from "express"; import { Guild, getPermission, Snowflake, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { GuildUpdateWelcomeScreenSchema } from "../../../schema/Guild"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts index 193ed095..f871fac8 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/api/src/routes/guilds/#guild_id/widget.json.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { random } from "../../../util/RandomInviteID"; +import { random } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts index fcf71402..d9ce817e 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/api/src/routes/guilds/#guild_id/widget.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { getPermission, Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { WidgetModifySchema } from "../../../schema/Widget"; const router: Router = Router(); diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index e5830647..a1b199e7 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "./../../util/instanceOf"; +import { check } from "@fosscord/api"; import { GuildCreateSchema } from "../../schema/Guild"; import { DiscordApiErrors } from "@fosscord/util"; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index 58201f65..1d0f2716 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -3,7 +3,7 @@ const router: Router = Router(); import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildTemplateCreateSchema } from "../../../schema/Guild"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; router.get("/:code", async (req: Request, res: Response) => { diff --git a/api/src/routes/users/#id/index.ts b/api/src/routes/users/#id/index.ts index 3841756b..07956360 100644 --- a/api/src/routes/users/#id/index.ts +++ b/api/src/routes/users/#id/index.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { User } from "../../../../../util/dist"; +import { User } from "@fosscord/util"; const router: Router = Router(); diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 8be03b47..0f43a82f 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { PublicConnectedAccount, PublicUser, User, UserPublic } from "../../../../../util/dist"; +import { PublicConnectedAccount, PublicUser, User, UserPublic } from "@fosscord/util"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 6fd396b8..77fc8296 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,11 +1,9 @@ import { Router, Request, Response } from "express"; -import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent } from "@fosscord/util"; +import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent, Recipient } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - import { DmChannelCreateSchema } from "../../../schema/Channel"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { In } from "typeorm"; -import { Recipient } from "../../../../../util/dist/entities/Recipient"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 68649215..451a657c 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,8 +1,8 @@ import { Router, Request, Response } from "express"; import { User, PrivateUserProjection } from "@fosscord/util"; import { UserModifySchema } from "../../../schema/User"; -import { check } from "../../../util/instanceOf"; -import { handleFile } from "../../../util/cdn"; +import { check } from "@fosscord/api"; +import { handleFile } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 995b0244..67ca2f35 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -12,7 +12,7 @@ import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { DiscordApiErrors } from "@fosscord/util"; -import { check, Length } from "../../../util/instanceOf"; +import { check, Length } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/users/@me/settings.ts b/api/src/routes/users/@me/settings.ts index 90ee6372..e7db85ed 100644 --- a/api/src/routes/users/@me/settings.ts +++ b/api/src/routes/users/@me/settings.ts @@ -1,6 +1,6 @@ import { Router, Response, Request } from "express"; import { User, UserSettings } from "@fosscord/util"; -import { check } from "../../../util/instanceOf"; +import { check } from "@fosscord/api"; import { UserSettingsSchema } from "../../../schema/User"; const router = Router(); diff --git a/api/src/routes/voice/regions.ts b/api/src/routes/voice/regions.ts index 812aa8f6..da1aaade 100644 --- a/api/src/routes/voice/regions.ts +++ b/api/src/routes/voice/regions.ts @@ -1,11 +1,11 @@ import { Router, Request, Response } from "express"; -import {getIpAdress} from "../../util/ipAddress"; -import {getVoiceRegions} from "../../util/Voice"; +import { getIpAdress } from "@fosscord/api"; +import { getVoiceRegions } from "@fosscord/api"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - res.json(await getVoiceRegions(getIpAdress(req), true))//vip true? + res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true? }); export default router; diff --git a/api/src/test/password_test.ts b/api/src/test/password_test.ts index 59d36621..983b18ae 100644 --- a/api/src/test/password_test.ts +++ b/api/src/test/password_test.ts @@ -1,12 +1,12 @@ -import { check } from "./../util/passwordStrength"; +import { checkPassword } from "@fosscord/api"; -console.log(check("123456789012345")); +console.log(checkPassword("123456789012345")); // -> 0.25 -console.log(check("ABCDEFGHIJKLMOPQ")); +console.log(checkPassword("ABCDEFGHIJKLMOPQ")); // -> 0.25 -console.log(check("ABC123___...123")); +console.log(checkPassword("ABC123___...123")); // -> -console.log(check("")); +console.log(checkPassword("")); // -> -// console.log(check("")); +// console.log(checkPassword("")); // // -> diff --git a/api/src/util/index.ts b/api/src/util/index.ts new file mode 100644 index 00000000..43481289 --- /dev/null +++ b/api/src/util/index.ts @@ -0,0 +1,11 @@ +export * from "./Base64"; +export * from "./cdn"; +export * from "./instanceOf"; +export * from "./ipAddress"; +export * from "./Message"; +export * from "./passwordStrength"; +export * from "./RandomInviteID"; +export * from "./route"; +export * from "./String"; +export * from "./Voice"; +export * from "./VoiceState"; diff --git a/api/src/util/passwordStrength.ts b/api/src/util/passwordStrength.ts index dfffa2c0..047df008 100644 --- a/api/src/util/passwordStrength.ts +++ b/api/src/util/passwordStrength.ts @@ -16,7 +16,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored * * Returns: 0 > pw > 1 */ -export function check(password: string): number { +export function checkPassword(password: string): number { const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; var strength = 0; diff --git a/api/tests/automatic.test.js b/api/tests/automatic.test.js new file mode 100644 index 00000000..e69de29b diff --git a/api/tsconfig.json b/api/tsconfig.json index 6bf2e6b7..21a8eb96 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -63,6 +63,11 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, + "baseUrl": ".", + "paths": { + "@fosscord/api": ["src/index.ts"], + "@fosscord/api/*": ["src/*"] + } } } diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 944570edeaf83b7cb599254a9639785456bcd2a8..0092e0d5a34d5ce712244bc6116730042ee15909 100644 GIT binary patch delta 1732 zcmcJQO-$Qn7{?VBNI^v!Q$zWRfG~-JYsbd<>=vd#SV|HS2NLpeKqHRh*u-y4oG&s> zw3m(R_HeA$o;GP}r%|igtx_+mwjQb;dM~Of_0(;b9eU{;hfhtgRsvuA>Uo~`@$3Kl z|Gn?yE#sfx8-Fo(j9uyJ)*fp`VM{14@VLBMDJa&fW7ZX#peTX_OMh7XqT_Pcz)2M< zy0Fy^znO>BfBK$&aB|rL^UFSi0}Pw}Mv|EFz|L*QB~d9MhYh#sZ=59bgn_XPyM#iW zs}`ekxsW~R+Q1TM*6l=YjOFaG^VtAgcbL>$lh39*^_j5F2YQe7g7KH*-&r~np(T4V zCNdmaYzQ%B)UL(ics0VO{7q&JCD%wq zR(*@b4X&Ok*%b_%TcVxOI>d_OpCUmLl!~qtMNAQ-wI$?Nx}4UmfV9MyZE5v&wd#sO z*2WeELR5ehEowEN=<3wFz>N{JvDE}_{9qfDgm!C9mi-~#N;=@-qp`$-2SpO;^5Tqd zE}V(Q=?0gHx$@FNbcT1LWJC&v;w-NOxz(^zVQh)0e_0MFWkheKMThYN7r(t(w)I#C& z&G?KcHz-8ca?3TkmY2&Sl@=wjvH(wG6JXPR)$}!PxOmh*(8rpMErY>&nEd#-`LwSb z58!&?jyS&T;@Di;kLGhy%}IMC$zNBB0cR-2QyI@vJl>>4SAcDjja3XOg@TO_`F+<| zrm>h!Min2u#K>eN#Y`2sIGgr_>R80Lmd`YOuro6Xe~p^b2RpUWcA|9;j>EVH1VQbG z_jl)r2lgf$sHCT80#=ttRr(uC?}>E412d=I*$$ZC5A)=`n@>7Ut%N($R${UwQh^78!Nqt>G+CNt_ER@a@A9DE(;KJS5_bNzvN157M?DTQ}2&%;7@o RO483$YmF-__esOB@n1t2EW-c* delta 163 zcmaDqkL7d+%LY-V$)`EICtq78w0SZUE8FIs9QB-&%@=8Gt`b_wJlRr6a&xTMVZqJ! zH7?q1{+kiTw%N15R&sM@moDRG)m|B1kc!E%V$PfQ&2nd-oX^L*S!dCCCYb2teY3bG zUs%SzxpOHG?_^nK?#Zt-eocO`PIB^xBa%P~w#_YDvduRCf6d7{`O6HM&Agw4#HOF+ NVEne-k(*JE1poo`Leu~N diff --git a/bundle/package.json b/bundle/package.json index a326a131..996ab30b 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -11,7 +11,7 @@ "build:api": "cd ../api/ && npm run build", "build:cdn": "cd ../cdn/ && npm run build", "build:gateway": "cd ../gateway/ && npm run build", - "start": "npm run build && node dist/start.js", + "start": "npm run build && node -r ./tsconfig-paths-bootstrap.js dist/start.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -52,6 +52,7 @@ "async-exit-hook": "^2.0.1", "express": "^4.17.1", "missing-native-js-functions": "^1.2.15", - "node-os-utils": "^1.3.5" + "node-os-utils": "^1.3.5", + "tsconfig-paths": "^3.11.0" } } diff --git a/bundle/tsconfig-paths-bootstrap.js b/bundle/tsconfig-paths-bootstrap.js new file mode 100644 index 00000000..efe4b3bc --- /dev/null +++ b/bundle/tsconfig-paths-bootstrap.js @@ -0,0 +1,14 @@ +const tsConfigPaths = require("tsconfig-paths"); +const path = require("path"); + +const cleanup = tsConfigPaths.register({ + baseUrl: path.join(__dirname, "node_modules", "@fosscord"), + paths: { + "@fosscord/api": ["api/dist/index.js"], + "@fosscord/api/*": ["api/dist/*"], + "@fosscord/gateway": ["gateway/dist/index.js"], + "@fosscord/gateway/*": ["gateway/dist/*"], + "@fosscord/cdn": ["cdn/dist/index.js"], + "@fosscord/cdn/*": ["cdn/dist/*"], + }, +}); diff --git a/cdn/package.json b/cdn/package.json index c2bdbd79..7c59381b 100644 --- a/cdn/package.json +++ b/cdn/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "npm run build && jest --coverage ./tests", "build": "npx tsc -b .", - "start": "npm run build && node dist/start.js" + "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start.js" }, "repository": { "type": "git", diff --git a/cdn/scripts/tsconfig-paths-bootstrap.js b/cdn/scripts/tsconfig-paths-bootstrap.js new file mode 100644 index 00000000..50602b02 --- /dev/null +++ b/cdn/scripts/tsconfig-paths-bootstrap.js @@ -0,0 +1,10 @@ +const tsConfigPaths = require("tsconfig-paths"); +const path = require("path"); + +const cleanup = tsConfigPaths.register({ + baseUrl: path.join(__dirname, ".."), + paths: { + "@fosscord/cdn": ["dist/index.js"], + "@fosscord/cdn/*": ["dist/*"], + }, +}); diff --git a/cdn/src/routes/attachments.ts b/cdn/src/routes/attachments.ts index 59845c94..7c55998b 100644 --- a/cdn/src/routes/attachments.ts +++ b/cdn/src/routes/attachments.ts @@ -1,73 +1,87 @@ import { Router, Response, Request } from "express"; import { Config, Snowflake } from "@fosscord/util"; -import { storage } from "../util/Storage"; +import { storage } from "@fosscord/cdn/util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; -import { multer } from "../util/multer"; +import { multer } from "@fosscord/cdn/util/multer"; import imageSize from "image-size"; const router = Router(); -router.post("/:channel_id", multer.single("file"), async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("file missing"); +router.post( + "/:channel_id", + multer.single("file"), + async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("file missing"); - const { buffer, mimetype, size, originalname, fieldname } = req.file; - const { channel_id } = req.params; - const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); - const id = Snowflake.generate(); - const path = `attachments/${channel_id}/${id}/${filename}`; + const { buffer, mimetype, size, originalname, fieldname } = req.file; + const { channel_id } = req.params; + const filename = originalname + .replaceAll(" ", "_") + .replace(/[^a-zA-Z0-9._]+/g, ""); + const id = Snowflake.generate(); + const path = `attachments/${channel_id}/${id}/${filename}`; - const endpoint = Config.get()?.cdn.endpoint || "http://localhost:3003"; + const endpoint = Config.get()?.cdn.endpoint || "http://localhost:3003"; - await storage.set(path, buffer); - var width; - var height; - if (mimetype.includes("image")) { - const dimensions = imageSize(buffer); - if (dimensions) { - width = dimensions.width; - height = dimensions.height; + await storage.set(path, buffer); + var width; + var height; + if (mimetype.includes("image")) { + const dimensions = imageSize(buffer); + if (dimensions) { + width = dimensions.width; + height = dimensions.height; + } } + + const file = { + id, + content_type: mimetype, + filename: filename, + size, + url: `${endpoint}/${path}`, + width, + height, + }; + + return res.json(file); } +); - const file = { - id, - content_type: mimetype, - filename: filename, - size, - url: `${endpoint}/${path}`, - width, - height, - }; +router.get( + "/:channel_id/:id/:filename", + async (req: Request, res: Response) => { + const { channel_id, id, filename } = req.params; - return res.json(file); -}); + const file = await storage.get( + `attachments/${channel_id}/${id}/${filename}` + ); + if (!file) throw new HTTPError("File not found"); + const type = await FileType.fromBuffer(file); -router.get("/:channel_id/:id/:filename", async (req: Request, res: Response) => { - const { channel_id, id, filename } = req.params; + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); - const file = await storage.get(`attachments/${channel_id}/${id}/${filename}`); - if (!file) throw new HTTPError("File not found"); - const type = await FileType.fromBuffer(file); + return res.send(file); + } +); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000"); +router.delete( + "/:channel_id/:id/:filename", + async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); - return res.send(file); -}); + const { channel_id, id, filename } = req.params; + const path = `attachments/${channel_id}/${id}/${filename}`; -router.delete("/:channel_id/:id/:filename", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); + await storage.delete(path); - const { channel_id, id, filename } = req.params; - const path = `attachments/${channel_id}/${id}/${filename}`; - - await storage.delete(path); - - return res.send({ success: true }); -}); + return res.send({ success: true }); + } +); export default router; diff --git a/cdn/src/routes/avatars.ts b/cdn/src/routes/avatars.ts index 03388afc..3d745f90 100644 --- a/cdn/src/routes/avatars.ts +++ b/cdn/src/routes/avatars.ts @@ -1,9 +1,9 @@ import { Router, Response, Request } from "express"; import { Config, Snowflake } from "@fosscord/util"; -import { storage } from "../util/Storage"; +import { storage } from "@fosscord/cdn/util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; -import { multer } from "../util/multer"; +import { multer } from "@fosscord/cdn/util/multer"; import crypto from "crypto"; // TODO: check premium and animated pfp are allowed in the config @@ -12,36 +12,50 @@ import crypto from "crypto"; // TODO: delete old icons const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"]; -const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; +const STATIC_MIME_TYPES = [ + "image/png", + "image/jpeg", + "image/webp", + "image/svg+xml", + "image/svg", +]; const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; const router = Router(); -router.post("/:user_id", multer.single("file"), async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, mimetype, size, originalname, fieldname } = req.file; - const { user_id } = req.params; +router.post( + "/:user_id", + multer.single("file"), + async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, mimetype, size, originalname, fieldname } = req.file; + const { user_id } = req.params; - var hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + var hash = crypto + .createHash("md5") + .update(Snowflake.generate()) + .digest("hex"); - const type = await FileType.fromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); - if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash + const type = await FileType.fromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) + throw new HTTPError("Invalid file type"); + if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash - const path = `avatars/${user_id}/${hash}`; - const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; + const path = `avatars/${user_id}/${hash}`; + const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; - await storage.set(path, buffer); + await storage.set(path, buffer); - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, - }); -}); + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, + }); + } +); router.get("/:user_id/:hash", async (req: Request, res: Response) => { var { user_id, hash } = req.params; diff --git a/cdn/src/routes/external.ts b/cdn/src/routes/external.ts index 10bb0f7d..e7783ec3 100644 --- a/cdn/src/routes/external.ts +++ b/cdn/src/routes/external.ts @@ -2,7 +2,7 @@ import { Router, Response, Request } from "express"; import fetch from "node-fetch"; import { HTTPError } from "lambert-server"; import { Snowflake } from "@fosscord/util"; -import { storage } from "../util/Storage"; +import { storage } from "@fosscord/cdn/util/Storage"; import FileType from "file-type"; import { Config } from "@fosscord/util"; @@ -13,7 +13,8 @@ const DEFAULT_FETCH_OPTIONS: any = { redirect: "follow", follow: 1, headers: { - "user-agent": "Mozilla/5.0 (compatible Fosscordbot/0.1; +https://fosscord.com)", + "user-agent": + "Mozilla/5.0 (compatible Fosscordbot/0.1; +https://fosscord.com)", }, size: 1024 * 1024 * 8, compress: true, diff --git a/cdn/tsconfig.json b/cdn/tsconfig.json index 08e39435..2daac1ad 100644 --- a/cdn/tsconfig.json +++ b/cdn/tsconfig.json @@ -65,6 +65,11 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, + "baseUrl": ".", + "paths": { + "@fosscord/cdn/": ["src/index.ts"], + "@fosscord/cdn/*": ["src/*"] + } } } diff --git a/gateway/package-lock.json b/gateway/package-lock.json index 340f595d883b5120b2e0f897d40b19ac8c68caf1..64c542e46bfdc095e8f2b3d08d678bf87c09b735 100644 GIT binary patch delta 18926 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 Dhe+eV delta 842 zcmZ9~X-Je&6b4}4JNnJ{O^s`5qb=5?O&e+kEzFdaR!EX%o7hItf(A+(RM;X?Bt+3I zkA%iV`)*w9WJ!xZ633#bKc!_9mZU{LEEg#3o5Z&NFXz7JJh%Fa(RIpN(FGMI+!jnE zq~VzChJ&7@VU}90ErN`(X{uIthbl+(>_FP!MwBWaUIO*iSqOO{u1l4;_6QkU6e5T9 zi%^N<-v`kOkI*SO6`IVmN2oFGMxiohICT{}zc_%LS#Zd_Gj{1&9Ijquo(Yw^)_;_R z8}F*QIhDGK+RE178P&4ux{!@~8tHN7-y?l{+bp!O3a`}F1IwY3iYHrS?%|6v+cQ$R zj*QUMamui$X=RCyf4flHTMUyM_QlcabQnhy?5e3U(jvzeh0u_2*p;ZN4pjpmm08r; zr`4cOClKUcfqY$v@~6w5r9mxy(FB?036YndN2`p6rXV$~)dA{Wg&>Lv7Huly^HE*Z zb);C;j-CZp_8G{raA7cN`f=i5YzNz2DAsA`1eE^XlwC<^2Va|nQQ);HFu*Ny;1K_r zq@RAEgT5>_PuOHkr2J*Da(+4n1oNR4SQ)?#YY+?h)!FXPgvlQxr_jgs_{Oyxv91rv z;NhFlW2bD7u#tBi2FWq*nY?wU`5NCYz-x`=K?L+dvG)+R1(Wq8ZkR+B9CHdn=fF!S z)qYjya}|gbbVuPO)o1q2^X)B{J@p39tHdI~rc)g_tQvO(-MokM{@eYk$9eY=7|6vB zQ7<^xhsP0rIQSm(f6Py~Cw4+t<)K2UMwVuET zHmTyl-+#ah8Q-AM=q!Zu*&dYk=VZI+-|NJtQ>vAxM~eL#cMcUL8b`ZCjo>%qM7d6; pQZpq_5=(fCTXbktlOlShXl<%!BNOR^zY%pV6z?>fQD7^u{RDZnIi&yq diff --git a/gateway/package.json b/gateway/package.json index 310e867e..6b9e27d6 100644 --- a/gateway/package.json +++ b/gateway/package.json @@ -5,7 +5,7 @@ "main": "dist/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "npm run build && node dist/start.js", + "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start.js", "build": "npx tsc -b .", "dev": "tsnd --respawn src/start.ts" }, @@ -35,6 +35,7 @@ "missing-native-js-functions": "^1.2.15", "mongoose-autopopulate": "^0.12.3", "node-fetch": "^2.6.1", + "typeorm": "^0.2.37", "uuid": "^8.3.2", "ws": "^7.4.2" } diff --git a/gateway/scripts/tsconfig-paths-bootstrap.js b/gateway/scripts/tsconfig-paths-bootstrap.js new file mode 100644 index 00000000..7308523b --- /dev/null +++ b/gateway/scripts/tsconfig-paths-bootstrap.js @@ -0,0 +1,10 @@ +const tsConfigPaths = require("tsconfig-paths"); +const path = require("path"); + +const cleanup = tsConfigPaths.register({ + baseUrl: path.join(__dirname, ".."), + paths: { + "@fosscord/gateway": ["dist/index.js"], + "@fosscord/gateway/*": ["dist/*"], + }, +}); diff --git a/gateway/src/events/Close.ts b/gateway/src/events/Close.ts index 2f274ec4..4afe8352 100644 --- a/gateway/src/events/Close.ts +++ b/gateway/src/events/Close.ts @@ -1,4 +1,4 @@ -import WebSocket from "../util/WebSocket"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { Message } from "./Message"; import { Session } from "@fosscord/util"; diff --git a/gateway/src/events/Connection.ts b/gateway/src/events/Connection.ts index b3c94eaf..625ceaa2 100644 --- a/gateway/src/events/Connection.ts +++ b/gateway/src/events/Connection.ts @@ -1,10 +1,10 @@ -import WebSocket, { Server } from "../util/WebSocket"; +import WebSocket, { Server } from "@fosscord/gateway/util/WebSocket"; import { IncomingMessage } from "http"; import { Close } from "./Close"; import { Message } from "./Message"; -import { setHeartbeat } from "../util/setHeartbeat"; -import { Send } from "../util/Send"; -import { CLOSECODES, OPCODES } from "../util/Constants"; +import { setHeartbeat } from "@fosscord/gateway/util/setHeartbeat"; +import { Send } from "@fosscord/gateway/util/Send"; +import { CLOSECODES, OPCODES } from "@fosscord/gateway/util/Constants"; import { createDeflate } from "zlib"; import { URL } from "url"; import { Session } from "@fosscord/util"; @@ -17,7 +17,11 @@ try { // TODO: specify rate limit in config // TODO: check msg max size -export async function Connection(this: Server, socket: WebSocket, request: IncomingMessage) { +export async function Connection( + this: Server, + socket: WebSocket, + request: IncomingMessage +) { try { socket.on("close", Close); // @ts-ignore @@ -27,18 +31,21 @@ export async function Connection(this: Server, socket: WebSocket, request: Incom // @ts-ignore socket.encoding = searchParams.get("encoding") || "json"; if (!["json", "etf"].includes(socket.encoding)) { - if (socket.encoding === "etf" && erlpack) throw new Error("Erlpack is not installed: 'npm i -D erlpack'"); + if (socket.encoding === "etf" && erlpack) + throw new Error("Erlpack is not installed: 'npm i -D erlpack'"); return socket.close(CLOSECODES.Decode_error); } // @ts-ignore socket.version = Number(searchParams.get("version")) || 8; - if (socket.version != 8) return socket.close(CLOSECODES.Invalid_API_version); + if (socket.version != 8) + return socket.close(CLOSECODES.Invalid_API_version); // @ts-ignore socket.compress = searchParams.get("compress") || ""; if (socket.compress) { - if (socket.compress !== "zlib-stream") return socket.close(CLOSECODES.Decode_error); + if (socket.compress !== "zlib-stream") + return socket.close(CLOSECODES.Decode_error); socket.deflate = createDeflate({ chunkSize: 65535 }); socket.deflate.on("data", (chunk) => socket.send(chunk)); } diff --git a/gateway/src/events/Message.ts b/gateway/src/events/Message.ts index a8bf5d78..5c6bd2fc 100644 --- a/gateway/src/events/Message.ts +++ b/gateway/src/events/Message.ts @@ -1,10 +1,10 @@ -import WebSocket, { Data } from "../util/WebSocket"; +import WebSocket, { Data } from "@fosscord/gateway/util/WebSocket"; var erlpack: any; try { erlpack = require("erlpack"); } catch (error) {} import OPCodeHandlers from "../opcodes"; -import { Payload, CLOSECODES, OPCODES } from "../util/Constants"; +import { Payload, CLOSECODES, OPCODES } from "@fosscord/gateway/util/Constants"; import { instanceOf, Tuple } from "lambert-server"; import { check } from "../opcodes/instanceOf"; import WS from "ws"; @@ -20,8 +20,10 @@ export async function Message(this: WebSocket, buffer: WS.Data) { // TODO: compression var data: Payload; - if (this.encoding === "etf" && buffer instanceof Buffer) data = erlpack.unpack(buffer); - else if (this.encoding === "json" && typeof buffer === "string") data = JSON.parse(buffer); + if (this.encoding === "etf" && buffer instanceof Buffer) + data = erlpack.unpack(buffer); + else if (this.encoding === "json" && typeof buffer === "string") + data = JSON.parse(buffer); else return; check.call(this, PayloadSchema, data); @@ -41,6 +43,7 @@ export async function Message(this: WebSocket, buffer: WS.Data) { return await OPCodeHandler.call(this, data); } catch (error) { console.error(error); - if (!this.CLOSED && this.CLOSING) return this.close(CLOSECODES.Unknown_error); + if (!this.CLOSED && this.CLOSING) + return this.close(CLOSECODES.Unknown_error); } } diff --git a/gateway/src/index.ts b/gateway/src/index.ts index 7513bd2f..d77ce931 100644 --- a/gateway/src/index.ts +++ b/gateway/src/index.ts @@ -1 +1,4 @@ export * from "./Server"; +export * from "./util/"; +export * from "./opcodes/"; +export * from "./listener/listener"; diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index 0b6fa50c..e5630a43 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -9,13 +9,13 @@ import { ListenEventOpts, Member, } from "@fosscord/util"; -import { OPCODES } from "../util/Constants"; -import { Send } from "../util/Send"; -import WebSocket from "../util/WebSocket"; +import { OPCODES } from "@fosscord/gateway/util/Constants"; +import { Send } from "@fosscord/gateway/util/Send"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import "missing-native-js-functions"; import { Channel as AMQChannel } from "amqplib"; -import { In, Like } from "../../../util/node_modules/typeorm"; -import { Recipient } from "../../../util/dist/entities/Recipient"; +import { In, Like } from "typeorm"; +import { Recipient } from "@fosscord/util"; // TODO: close connection on Invalidated Token // TODO: check intent diff --git a/gateway/src/opcodes/Heartbeat.ts b/gateway/src/opcodes/Heartbeat.ts index 015257b9..34d8ca74 100644 --- a/gateway/src/opcodes/Heartbeat.ts +++ b/gateway/src/opcodes/Heartbeat.ts @@ -1,7 +1,7 @@ -import { CLOSECODES, Payload } from "../util/Constants"; -import { Send } from "../util/Send"; -import { setHeartbeat } from "../util/setHeartbeat"; -import WebSocket from "../util/WebSocket"; +import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; +import { Send } from "@fosscord/gateway/util/Send"; +import { setHeartbeat } from "@fosscord/gateway/util/setHeartbeat"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export async function onHeartbeat(this: WebSocket, data: Payload) { // TODO: validate payload diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 95db7f3d..04a6c84c 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -1,5 +1,5 @@ -import { CLOSECODES, Payload, OPCODES } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { CLOSECODES, Payload, OPCODES } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { Channel, checkToken, @@ -17,12 +17,12 @@ import { } from "@fosscord/util"; import { setupListener } from "../listener/listener"; import { IdentifySchema } from "../schema/Identify"; -import { Send } from "../util/Send"; +import { Send } from "@fosscord/gateway/util/Send"; // import experiments from "./experiments.json"; const experiments: any = []; import { check } from "./instanceOf"; -import { Recipient } from "../../../util/dist/entities/Recipient"; -import { genSessionId } from "../util/SessionUtils"; +import { Recipient } from "@fosscord/util"; +import { genSessionId } from "@fosscord/gateway/util/SessionUtils"; // TODO: bot sharding // TODO: check priviliged intents diff --git a/gateway/src/opcodes/LazyRequest.ts b/gateway/src/opcodes/LazyRequest.ts index b7ee9a96..41ec3446 100644 --- a/gateway/src/opcodes/LazyRequest.ts +++ b/gateway/src/opcodes/LazyRequest.ts @@ -6,9 +6,9 @@ import { Role, } from "@fosscord/util"; import { LazyRequest } from "../schema/LazyRequest"; -import { OPCODES, Payload } from "../util/Constants"; -import { Send } from "../util/Send"; -import WebSocket from "../util/WebSocket"; +import { OPCODES, Payload } from "@fosscord/gateway/util/Constants"; +import { Send } from "@fosscord/gateway/util/Send"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { check } from "./instanceOf"; import "missing-native-js-functions"; diff --git a/gateway/src/opcodes/PresenceUpdate.ts b/gateway/src/opcodes/PresenceUpdate.ts index 3760f8a3..4febbfcb 100644 --- a/gateway/src/opcodes/PresenceUpdate.ts +++ b/gateway/src/opcodes/PresenceUpdate.ts @@ -1,5 +1,5 @@ -import { CLOSECODES, Payload } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export function onPresenceUpdate(this: WebSocket, data: Payload) { // return this.close(CLOSECODES.Unknown_error); diff --git a/gateway/src/opcodes/RequestGuildMembers.ts b/gateway/src/opcodes/RequestGuildMembers.ts index 2701d978..9c1654fa 100644 --- a/gateway/src/opcodes/RequestGuildMembers.ts +++ b/gateway/src/opcodes/RequestGuildMembers.ts @@ -1,6 +1,6 @@ -import { CLOSECODES, Payload } from "../util/Constants"; +import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; -import WebSocket from "../util/WebSocket"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export function onRequestGuildMembers(this: WebSocket, data: Payload) { // return this.close(CLOSECODES.Unknown_error); diff --git a/gateway/src/opcodes/Resume.ts b/gateway/src/opcodes/Resume.ts index 4efde9b0..e155c139 100644 --- a/gateway/src/opcodes/Resume.ts +++ b/gateway/src/opcodes/Resume.ts @@ -1,7 +1,7 @@ -import { CLOSECODES, Payload } from "../util/Constants"; -import { Send } from "../util/Send"; +import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; +import { Send } from "@fosscord/gateway/util/Send"; -import WebSocket from "../util/WebSocket"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export async function onResume(this: WebSocket, data: Payload) { console.log("Got Resume -> cancel not implemented"); diff --git a/gateway/src/opcodes/VoiceStateUpdate.ts b/gateway/src/opcodes/VoiceStateUpdate.ts index 95a01608..60803ec3 100644 --- a/gateway/src/opcodes/VoiceStateUpdate.ts +++ b/gateway/src/opcodes/VoiceStateUpdate.ts @@ -1,9 +1,18 @@ import { VoiceStateUpdateSchema } from "../schema/VoiceStateUpdateSchema"; -import { Payload } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { Payload } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { check } from "./instanceOf"; -import { Config, emitEvent, Guild, Member, Region, VoiceServerUpdateEvent, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; -import { genVoiceToken } from "../util/SessionUtils"; +import { + Config, + emitEvent, + Guild, + Member, + Region, + VoiceServerUpdateEvent, + VoiceState, + VoiceStateUpdateEvent, +} from "@fosscord/util"; +import { genVoiceToken } from "@fosscord/gateway/util/SessionUtils"; // TODO: check if a voice server is setup // Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not. @@ -14,21 +23,27 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { let voiceState: VoiceState; try { voiceState = await VoiceState.findOneOrFail({ - where: { user_id: this.user_id } + where: { user_id: this.user_id }, }); - if (voiceState.session_id !== this.session_id && body.channel_id === null) { + if ( + voiceState.session_id !== this.session_id && + body.channel_id === null + ) { //Should we also check guild_id === null? //changing deaf or mute on a client that's not the one with the same session of the voicestate in the database should be ignored return; } //If a user change voice channel between guild we should send a left event first - if (voiceState.guild_id !== body.guild_id && voiceState.session_id === this.session_id) { + if ( + voiceState.guild_id !== body.guild_id && + voiceState.session_id === this.session_id + ) { await emitEvent({ event: "VOICE_STATE_UPDATE", data: { ...voiceState, channel_id: null }, guild_id: voiceState.guild_id, - }) + }); } //The event send by Discord's client on channel leave has both guild_id and channel_id as null @@ -50,10 +65,11 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { voiceState.member = await Member.findOneOrFail({ where: { id: voiceState.user_id, guild_id: voiceState.guild_id }, relations: ["user", "roles"], - }) + }); //If the session changed we generate a new token - if (voiceState.session_id !== this.session_id) voiceState.token = genVoiceToken(); + if (voiceState.session_id !== this.session_id) + voiceState.token = genVoiceToken(); voiceState.session_id = this.session_id; const { id, ...newObj } = voiceState; @@ -69,13 +85,17 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { //If it's null it means that we are leaving the channel and this event is not needed if (voiceState.channel_id !== null) { - const guild = await Guild.findOne({ id: voiceState.guild_id }) + const guild = await Guild.findOne({ id: voiceState.guild_id }); const regions = Config.get().regions; let guildRegion: Region; if (guild && guild.region) { - guildRegion = regions.available.filter(r => (r.id === guild.region))[0] + guildRegion = regions.available.filter( + (r) => r.id === guild.region + )[0]; } else { - guildRegion = regions.available.filter(r => (r.id === regions.default))[0] + guildRegion = regions.available.filter( + (r) => r.id === regions.default + )[0]; } await emitEvent({ diff --git a/gateway/src/opcodes/index.ts b/gateway/src/opcodes/index.ts index fa57f568..a6d13bfb 100644 --- a/gateway/src/opcodes/index.ts +++ b/gateway/src/opcodes/index.ts @@ -1,5 +1,5 @@ -import { Payload } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { Payload } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { onHeartbeat } from "./Heartbeat"; import { onIdentify } from "./Identify"; import { onLazyRequest } from "./LazyRequest"; diff --git a/gateway/src/opcodes/instanceOf.ts b/gateway/src/opcodes/instanceOf.ts index c4ee5ee6..6d84ac21 100644 --- a/gateway/src/opcodes/instanceOf.ts +++ b/gateway/src/opcodes/instanceOf.ts @@ -1,6 +1,6 @@ import { instanceOf } from "lambert-server"; -import { CLOSECODES } from "../util/Constants"; -import WebSocket from "../util/WebSocket"; +import { CLOSECODES } from "@fosscord/gateway/util/Constants"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; export function check(this: WebSocket, schema: any, data: any) { try { diff --git a/gateway/src/util/Send.ts b/gateway/src/util/Send.ts index be25ac4f..1b00e361 100644 --- a/gateway/src/util/Send.ts +++ b/gateway/src/util/Send.ts @@ -2,7 +2,7 @@ var erlpack: any; try { erlpack = require("erlpack"); } catch (error) {} -import { Payload } from "../util/Constants"; +import { Payload } from "@fosscord/gateway/util/Constants"; import WebSocket from "./WebSocket"; diff --git a/gateway/src/util/WebSocket.ts b/gateway/src/util/WebSocket.ts index 2c763743..820cb1a3 100644 --- a/gateway/src/util/WebSocket.ts +++ b/gateway/src/util/WebSocket.ts @@ -1,7 +1,6 @@ import { Intents, Permissions } from "@fosscord/util"; -import WS, { Server, Data } from "ws"; +import WS from "ws"; import { Deflate } from "zlib"; -import { Channel } from "amqplib"; interface WebSocket extends WS { version: number; @@ -21,4 +20,3 @@ interface WebSocket extends WS { } export default WebSocket; -export { Server, Data }; diff --git a/gateway/src/util/index.ts b/gateway/src/util/index.ts new file mode 100644 index 00000000..27af5813 --- /dev/null +++ b/gateway/src/util/index.ts @@ -0,0 +1,5 @@ +export * from "./Constants"; +export * from "./Send"; +export * from "./SessionUtils"; +export * from "./setHeartbeat"; +export * from "./WebSocket"; diff --git a/gateway/tsconfig.json b/gateway/tsconfig.json index e5bf92c6..2530aa41 100644 --- a/gateway/tsconfig.json +++ b/gateway/tsconfig.json @@ -67,6 +67,11 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, - "resolveJsonModule": true + "resolveJsonModule": true, + "baseUrl": ".", + "paths": { + "@fosscord/gateway/*": ["src/*"], + "@fosscord/util/*": ["../util/src/*"] + } } } diff --git a/util/package-lock.json b/util/package-lock.json index 1f4c1875ba04231b18c67792c2c442f8f29b364e..97bd66d2cf3825b06bddbd583972cb3d84a132ee 100644 GIT binary patch delta 1079 zcmaF0KymLT#SO(w62-~+d1;yHx&?_P8O2Ig3QBRtdWMF22Ai{(7R_fgo%)bbjKx6D z$Z~okFRLZLslKj$R&jowsjh(@P;~OcM{?5UnHlA&r8)ktDc&Yt=9Su^dCnGQc^2Bq zsV0*XPf1Kpn8@DDv8bJ65hD;YZRc3Te7Sh~1~z8#=>>{RCesbXSXigAGYd>^s#B0L z)b*?iamp&FFw8S9O9?A8E=wyY$})`1%FQa7zOjK>Wx7K@v+VY(qAV?<+(<5;oG2v9 zXgvL+DXXz!35u714n`HwHJ)y0#;Ou(o|>Ez>5}J}l3QkGY?+(uQeK?nZsr}BZWv`5 z91>Dxm}TysTxC#^sGV1wk)faN?c(T>98eLI6z*T_XdE0+U{DefV3d;@k{oI2U9Rot zn4K0?H9b&`QDO3g1?-yOPy&Z1IHW9%ta7nEg}m?qE6IsLafn-(|;Fd9w|)L>H$O{7T*m?*-bIDLK& SyTJ4; } +export type EventData = + | InvalidatedEvent + | ReadyEvent + | ChannelCreateEvent + | ChannelUpdateEvent + | ChannelDeleteEvent + | ChannelPinsUpdateEvent + | GuildCreateEvent + | GuildUpdateEvent + | GuildDeleteEvent + | GuildBanAddEvent + | GuildBanRemoveEvent + | GuildEmojiUpdateEvent + | GuildIntegrationUpdateEvent + | GuildMemberAddEvent + | GuildMemberRemoveEvent + | GuildMemberUpdateEvent + | GuildMembersChunkEvent + | GuildRoleCreateEvent + | GuildRoleUpdateEvent + | GuildRoleDeleteEvent + | InviteCreateEvent + | InviteDeleteEvent + | MessageCreateEvent + | MessageUpdateEvent + | MessageDeleteEvent + | MessageDeleteBulkEvent + | MessageReactionAddEvent + | MessageReactionRemoveEvent + | MessageReactionRemoveAllEvent + | MessageReactionRemoveEmojiEvent + | PresenceUpdateEvent + | TypingStartEvent + | UserUpdateEvent + | VoiceStateUpdateEvent + | VoiceServerUpdateEvent + | WebhooksUpdateEvent + | ApplicationCommandCreateEvent + | ApplicationCommandUpdateEvent + | ApplicationCommandDeleteEvent + | InteractionCreateEvent + | MessageAckEvent + | RelationshipAddEvent + | RelationshipRemoveEvent; + // located in collection events export enum EVENTEnum { diff --git a/util/src/util/Constants.ts b/util/src/util/Constants.ts index 713d59da..d2cc5130 100644 --- a/util/src/util/Constants.ts +++ b/util/src/util/Constants.ts @@ -632,7 +632,7 @@ export const DiscordApiErrors = { OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010), MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011), INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012), - MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action", 50013), + MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action ({})", 50013, undefined, [""]), INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014), NOTE_TOO_LONG: new ApiError("Note was too long", 50015), INVALID_BULK_DELETE_QUANTITY: new ApiError( diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts index d3844cd9..c22d8abd 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts @@ -21,7 +21,7 @@ export function initDatabase() { // entities: Object.values(Models).filter((x) => x.constructor.name !== "Object"), synchronize: true, - logging: false, + logging: true, cache: { duration: 1000 * 3, // cache all find queries for 3 seconds }, From 0a0a61565e761b78ae4e39c0d1fb5f0489b37ecd Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 12 Sep 2021 21:20:47 +0200 Subject: [PATCH 12/90] :bug: fix gateway --- gateway/src/events/Connection.ts | 5 +++-- gateway/src/events/Message.ts | 2 +- gateway/src/util/WebSocket.ts | 1 + util/src/util/Database.ts | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gateway/src/events/Connection.ts b/gateway/src/events/Connection.ts index 625ceaa2..b9d2d6a1 100644 --- a/gateway/src/events/Connection.ts +++ b/gateway/src/events/Connection.ts @@ -1,4 +1,5 @@ -import WebSocket, { Server } from "@fosscord/gateway/util/WebSocket"; +import WS from "ws"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; import { IncomingMessage } from "http"; import { Close } from "./Close"; import { Message } from "./Message"; @@ -18,7 +19,7 @@ try { // TODO: check msg max size export async function Connection( - this: Server, + this: WS.Server, socket: WebSocket, request: IncomingMessage ) { diff --git a/gateway/src/events/Message.ts b/gateway/src/events/Message.ts index 5c6bd2fc..66f98f1c 100644 --- a/gateway/src/events/Message.ts +++ b/gateway/src/events/Message.ts @@ -1,4 +1,4 @@ -import WebSocket, { Data } from "@fosscord/gateway/util/WebSocket"; +import WebSocket from "@fosscord/gateway/util/WebSocket"; var erlpack: any; try { erlpack = require("erlpack"); diff --git a/gateway/src/util/WebSocket.ts b/gateway/src/util/WebSocket.ts index 820cb1a3..15d1549f 100644 --- a/gateway/src/util/WebSocket.ts +++ b/gateway/src/util/WebSocket.ts @@ -1,6 +1,7 @@ import { Intents, Permissions } from "@fosscord/util"; import WS from "ws"; import { Deflate } from "zlib"; +import { Channel } from "amqplib"; interface WebSocket extends WS { version: number; diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts index c22d8abd..d3844cd9 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts @@ -21,7 +21,7 @@ export function initDatabase() { // entities: Object.values(Models).filter((x) => x.constructor.name !== "Object"), synchronize: true, - logging: true, + logging: false, cache: { duration: 1000 * 3, // cache all find queries for 3 seconds }, From e2c66a1fe578ece2c126d554ba04743ecb6991d8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 12 Sep 2021 21:21:08 +0200 Subject: [PATCH 13/90] :construction: :sparkles: new body parser (bans route) --- api/assets/schemas.json | 939 ++++++++++++++++++++++++ api/patches/ajv+8.6.2.patch | 249 +++++++ api/scripts/generate_body_schema.ts | 59 ++ api/src/routes/guilds/#guild_id/bans.ts | 25 +- api/src/util/route.ts | 66 ++ 5 files changed, 1323 insertions(+), 15 deletions(-) create mode 100644 api/assets/schemas.json create mode 100644 api/patches/ajv+8.6.2.patch create mode 100644 api/scripts/generate_body_schema.ts create mode 100644 api/src/util/route.ts diff --git a/api/assets/schemas.json b/api/assets/schemas.json new file mode 100644 index 00000000..b575bf60 --- /dev/null +++ b/api/assets/schemas.json @@ -0,0 +1,939 @@ +{ + "DmChannelCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "recipients": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "recipients" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "ChannelGuildPositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id" + ] + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "InviteCreateSchema": { + "type": "object", + "properties": { + "target_user_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "validate": { + "type": "string" + }, + "max_age": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "MessageCreateSchema": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "tts": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "embed": { + "additionalProperties": false, + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + } + }, + "allowed_mentions": { + "type": "object", + "properties": { + "parse": { + "type": "array", + "items": { + "type": "string" + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + }, + "replied_user": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "message_reference": { + "type": "object", + "properties": { + "message_id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "fail_if_not_exists": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "message_id" + ] + }, + "payload_json": { + "type": "string" + }, + "file": {} + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "BanCreateSchema": { + "type": "object", + "properties": { + "delete_message_days": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/definitions/ChannelModifySchema" + } + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildUpdateSchema": { + "type": "object", + "properties": { + "banner": { + "type": "string" + }, + "splash": { + "type": "string" + }, + "description": { + "type": "string" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "verification_level": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "system_channel_flags": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "afk_channel_id": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "name": { + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildTemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildUpdateWelcomeScreenSchema": { + "type": "object", + "properties": { + "welcome_channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "description", + "emoji_name" + ] + } + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "VoiceStateUpdateSchema": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "suppress": { + "type": "boolean" + }, + "request_to_speak_timestamp": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberCreateSchema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "nick": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "joined_at": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "guild_id", + "id", + "joined_at", + "nick" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberNickChangeSchema": { + "type": "object", + "properties": { + "nick": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "nick" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberChangeSchema": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RoleModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "permissions": { + "type": "bigint" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RolePositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id", + "position" + ] + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "TemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "TemplateModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "EmojiCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "image": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "image", + "name" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "UserModifySchema": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "UserSettingsSchema": { + "type": "object", + "properties": { + "afk_timeout": { + "type": "integer" + }, + "allow_accessibility_detection": { + "type": "boolean" + }, + "animate_emoji": { + "type": "boolean" + }, + "animate_stickers": { + "type": "integer" + }, + "contact_sync_enabled": { + "type": "boolean" + }, + "convert_emoticons": { + "type": "boolean" + }, + "custom_status": { + "type": "object", + "properties": { + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "expires_at": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "additionalProperties": false + }, + "default_guilds_restricted": { + "type": "boolean" + }, + "detect_platform_accounts": { + "type": "boolean" + }, + "developer_mode": { + "type": "boolean" + }, + "disable_games_tab": { + "type": "boolean" + }, + "enable_tts_command": { + "type": "boolean" + }, + "explicit_content_filter": { + "type": "integer" + }, + "friend_source_flags": { + "type": "object", + "properties": { + "all": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "all" + ] + }, + "gateway_connected": { + "type": "boolean" + }, + "gif_auto_play": { + "type": "boolean" + }, + "guild_folders": { + "type": "array", + "items": { + "type": "object", + "properties": { + "color": { + "type": "integer" + }, + "guild_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "color", + "guild_ids", + "id", + "name" + ] + } + }, + "guild_positions": { + "type": "array", + "items": { + "type": "string" + } + }, + "inline_attachment_media": { + "type": "boolean" + }, + "inline_embed_media": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "message_display_compact": { + "type": "boolean" + }, + "native_phone_integration_enabled": { + "type": "boolean" + }, + "render_embeds": { + "type": "boolean" + }, + "render_reactions": { + "type": "boolean" + }, + "restricted_guilds": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_current_game": { + "type": "boolean" + }, + "status": { + "enum": [ + "dnd", + "idle", + "offline", + "online" + ], + "type": "string" + }, + "stream_notifications_enabled": { + "type": "boolean" + }, + "theme": { + "enum": [ + "dark", + "white" + ], + "type": "string" + }, + "timezone_offset": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "afk_timeout", + "allow_accessibility_detection", + "animate_emoji", + "animate_stickers", + "contact_sync_enabled", + "convert_emoticons", + "custom_status", + "default_guilds_restricted", + "detect_platform_accounts", + "developer_mode", + "disable_games_tab", + "enable_tts_command", + "explicit_content_filter", + "friend_source_flags", + "gateway_connected", + "gif_auto_play", + "guild_folders", + "guild_positions", + "inline_attachment_media", + "inline_embed_media", + "locale", + "message_display_compact", + "native_phone_integration_enabled", + "render_embeds", + "render_reactions", + "restricted_guilds", + "show_current_game", + "status", + "stream_notifications_enabled", + "theme", + "timezone_offset" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "WidgetModifySchema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "enabled" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + } +} \ No newline at end of file diff --git a/api/patches/ajv+8.6.2.patch b/api/patches/ajv+8.6.2.patch new file mode 100644 index 00000000..3f54881b --- /dev/null +++ b/api/patches/ajv+8.6.2.patch @@ -0,0 +1,249 @@ +diff --git a/node_modules/ajv/dist/compile/jtd/parse.js b/node_modules/ajv/dist/compile/jtd/parse.js +index 1eeb1be..7684121 100644 +--- a/node_modules/ajv/dist/compile/jtd/parse.js ++++ b/node_modules/ajv/dist/compile/jtd/parse.js +@@ -239,6 +239,9 @@ function parseType(cxt) { + gen.if(fail, () => parsingError(cxt, codegen_1.str `invalid timestamp`)); + break; + } ++ case "bigint": ++ parseBigInt(cxt); ++ break + case "float32": + case "float64": + parseNumber(cxt); +@@ -284,6 +287,15 @@ function parseNumber(cxt, maxDigits) { + skipWhitespace(cxt); + gen.if(codegen_1._ `"-0123456789".indexOf(${jsonSlice(1)}) < 0`, () => jsonSyntaxError(cxt), () => parseWith(cxt, parseJson_1.parseJsonNumber, maxDigits)); + } ++function parseBigInt(cxt, maxDigits) { ++ const {gen} = cxt ++ skipWhitespace(cxt) ++ gen.if( ++ _`"-0123456789".indexOf(${jsonSlice(1)}) < 0`, ++ () => jsonSyntaxError(cxt), ++ () => parseWith(cxt, parseJson_1.parseJsonBigInt, maxDigits) ++ ) ++} + function parseBooleanToken(bool, fail) { + return (cxt) => { + const { gen, data } = cxt; +diff --git a/node_modules/ajv/dist/compile/rules.js b/node_modules/ajv/dist/compile/rules.js +index 82a591f..1ebd8fe 100644 +--- a/node_modules/ajv/dist/compile/rules.js ++++ b/node_modules/ajv/dist/compile/rules.js +@@ -1,7 +1,7 @@ + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.getRules = exports.isJSONType = void 0; +-const _jsonTypes = ["string", "number", "integer", "boolean", "null", "object", "array"]; ++const _jsonTypes = ["string", "number", "integer", "boolean", "null", "object", "array","bigint"]; + const jsonTypes = new Set(_jsonTypes); + function isJSONType(x) { + return typeof x == "string" && jsonTypes.has(x); +@@ -13,10 +13,11 @@ function getRules() { + string: { type: "string", rules: [] }, + array: { type: "array", rules: [] }, + object: { type: "object", rules: [] }, ++ bigint: {type: "bigint", rules: []} + }; + return { +- types: { ...groups, integer: true, boolean: true, null: true }, +- rules: [{ rules: [] }, groups.number, groups.string, groups.array, groups.object], ++ types: { ...groups, integer: true, boolean: true, null: true, bigint: true }, ++ rules: [{ rules: [] }, groups.number, groups.string, groups.array, groups.object, groups.bigint], + post: { rules: [] }, + all: {}, + keywords: {}, +diff --git a/node_modules/ajv/dist/compile/validate/dataType.js b/node_modules/ajv/dist/compile/validate/dataType.js +index 6319e76..8b50b4c 100644 +--- a/node_modules/ajv/dist/compile/validate/dataType.js ++++ b/node_modules/ajv/dist/compile/validate/dataType.js +@@ -52,7 +52,7 @@ function coerceAndCheckDataType(it, types) { + return checkTypes; + } + exports.coerceAndCheckDataType = coerceAndCheckDataType; +-const COERCIBLE = new Set(["string", "number", "integer", "boolean", "null"]); ++const COERCIBLE = new Set(["string", "number", "integer", "boolean", "null","bigint"]); + function coerceToTypes(types, coerceTypes) { + return coerceTypes + ? types.filter((t) => COERCIBLE.has(t) || (coerceTypes === "array" && t === "array")) +@@ -83,6 +83,14 @@ function coerceData(it, types, coerceTo) { + }); + function coerceSpecificType(t) { + switch (t) { ++ case "bigint": ++ gen ++ .elseIf( ++ codegen_1._`${dataType} == "boolean" || ${data} === null ++ || (${dataType} == "string" && ${data} && ${data} == BigInt(${data}))` ++ ) ++ .assign(coerced, codegen_1._`BigInt(${data})`) ++ return + case "string": + gen + .elseIf(codegen_1._ `${dataType} == "number" || ${dataType} == "boolean"`) +@@ -143,6 +151,9 @@ function checkDataType(dataType, data, strictNums, correct = DataType.Correct) { + case "number": + cond = numCond(); + break; ++ case "bigint": ++ cond = codegen_1._`typeof ${data} == "bigint" && isFinite(${data})` ++ break + default: + return codegen_1._ `typeof ${data} ${EQ} ${dataType}`; + } +diff --git a/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json b/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json +index 7027a12..25679c8 100644 +--- a/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json ++++ b/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json +@@ -78,7 +78,7 @@ + "default": 0 + }, + "simpleTypes": { +- "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] ++ "enum": ["array", "boolean", "integer", "null", "number", "object", "string","bigint"] + }, + "stringArray": { + "type": "array", +diff --git a/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json b/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json +index e0ae13d..57c9036 100644 +--- a/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json ++++ b/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json +@@ -78,7 +78,7 @@ + "default": 0 + }, + "simpleTypes": { +- "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] ++ "enum": ["array", "boolean", "integer", "null", "number", "object", "string","bigint"] + }, + "stringArray": { + "type": "array", +diff --git a/node_modules/ajv/dist/refs/json-schema-draft-06.json b/node_modules/ajv/dist/refs/json-schema-draft-06.json +index 5410064..774435b 100644 +--- a/node_modules/ajv/dist/refs/json-schema-draft-06.json ++++ b/node_modules/ajv/dist/refs/json-schema-draft-06.json +@@ -16,7 +16,7 @@ + "allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}] + }, + "simpleTypes": { +- "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] ++ "enum": ["array", "boolean", "integer", "null", "number", "object", "string","bigint"] + }, + "stringArray": { + "type": "array", +diff --git a/node_modules/ajv/dist/refs/json-schema-draft-07.json b/node_modules/ajv/dist/refs/json-schema-draft-07.json +index 6a74851..fc6dd7d 100644 +--- a/node_modules/ajv/dist/refs/json-schema-draft-07.json ++++ b/node_modules/ajv/dist/refs/json-schema-draft-07.json +@@ -16,7 +16,7 @@ + "allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}] + }, + "simpleTypes": { +- "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] ++ "enum": ["array", "boolean", "integer", "null", "number", "object", "string","bigint"] + }, + "stringArray": { + "type": "array", +diff --git a/node_modules/ajv/dist/refs/jtd-schema.js b/node_modules/ajv/dist/refs/jtd-schema.js +index 1ee940a..1148887 100644 +--- a/node_modules/ajv/dist/refs/jtd-schema.js ++++ b/node_modules/ajv/dist/refs/jtd-schema.js +@@ -38,6 +38,7 @@ const typeForm = (root) => ({ + "uint16", + "int32", + "uint32", ++ "bigint", + ], + }, + }, +diff --git a/node_modules/ajv/dist/runtime/parseJson.js b/node_modules/ajv/dist/runtime/parseJson.js +index 2576a6e..e7447b1 100644 +--- a/node_modules/ajv/dist/runtime/parseJson.js ++++ b/node_modules/ajv/dist/runtime/parseJson.js +@@ -97,6 +97,71 @@ exports.parseJsonNumber = parseJsonNumber; + parseJsonNumber.message = undefined; + parseJsonNumber.position = 0; + parseJsonNumber.code = 'require("ajv/dist/runtime/parseJson").parseJsonNumber'; ++ ++function parseJsonBigInt(s, pos, maxDigits) { ++ let numStr = ""; ++ let c; ++ parseJsonBigInt.message = undefined; ++ if (s[pos] === "-") { ++ numStr += "-"; ++ pos++; ++ } ++ if (s[pos] === "0") { ++ numStr += "0"; ++ pos++; ++ } ++ else { ++ if (!parseDigits(maxDigits)) { ++ errorMessage(); ++ return undefined; ++ } ++ } ++ if (maxDigits) { ++ parseJsonBigInt.position = pos; ++ return BigInt(numStr); ++ } ++ if (s[pos] === ".") { ++ numStr += "."; ++ pos++; ++ if (!parseDigits()) { ++ errorMessage(); ++ return undefined; ++ } ++ } ++ if (((c = s[pos]), c === "e" || c === "E")) { ++ numStr += "e"; ++ pos++; ++ if (((c = s[pos]), c === "+" || c === "-")) { ++ numStr += c; ++ pos++; ++ } ++ if (!parseDigits()) { ++ errorMessage(); ++ return undefined; ++ } ++ } ++ parseJsonBigInt.position = pos; ++ return BigInt(numStr); ++ function parseDigits(maxLen) { ++ let digit = false; ++ while (((c = s[pos]), c >= "0" && c <= "9" && (maxLen === undefined || maxLen-- > 0))) { ++ digit = true; ++ numStr += c; ++ pos++; ++ } ++ return digit; ++ } ++ function errorMessage() { ++ parseJsonBigInt.position = pos; ++ parseJsonBigInt.message = pos < s.length ? `unexpected token ${s[pos]}` : "unexpected end"; ++ } ++} ++exports.parseJsonBigInt = parseJsonBigInt; ++parseJsonBigInt.message = undefined; ++parseJsonBigInt.position = 0; ++parseJsonBigInt.code = 'require("ajv/dist/runtime/parseJson").parseJsonBigInt'; ++ ++ + const escapedChars = { + b: "\b", + f: "\f", +diff --git a/node_modules/ajv/dist/vocabularies/jtd/type.js b/node_modules/ajv/dist/vocabularies/jtd/type.js +index 428bddb..fbc3070 100644 +--- a/node_modules/ajv/dist/vocabularies/jtd/type.js ++++ b/node_modules/ajv/dist/vocabularies/jtd/type.js +@@ -45,6 +45,9 @@ const def = { + cond = timestampCode(cxt); + break; + } ++ case "bigint": ++ cond = codegen_1._`typeof ${data} == "bigint" || typeof ${data} == "string"` ++ break + case "float32": + case "float64": + cond = codegen_1._ `typeof ${data} == "number"`; \ No newline at end of file diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts new file mode 100644 index 00000000..69e62085 --- /dev/null +++ b/api/scripts/generate_body_schema.ts @@ -0,0 +1,59 @@ +// https://mermade.github.io/openapi-gui/# +// https://editor.swagger.io/ +import path from "path"; +import fs from "fs"; +import * as TJS from "typescript-json-schema"; +import "missing-native-js-functions"; +const schemaPath = path.join(__dirname, "..", "assets", "schemas.json"); + +const settings: TJS.PartialArgs = { + required: true, + ignoreErrors: true, + excludePrivate: true, + defaultNumberType: "integer", + noExtraProps: true, + defaultProps: false +}; +const compilerOptions: TJS.CompilerOptions = { + strictNullChecks: false +}; +const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"]; + +function main() { + const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions); + const generator = TJS.buildGenerator(program, settings); + if (!generator || !program) return; + + const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x)); + console.log(schemas); + + var definitions: any = {}; + + for (const name of schemas) { + const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); + if (!part) continue; + + definitions = { ...definitions, ...part.definitions, [name]: { ...part, definitions: undefined } }; + } + + fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); +} + +main(); + +function walk(dir: string) { + var results = [] as string[]; + var list = fs.readdirSync(dir); + list.forEach(function (file) { + file = dir + "/" + file; + var stat = fs.statSync(file); + if (stat && stat.isDirectory()) { + /* Recurse into a subdirectory */ + results = results.concat(walk(file)); + } else { + if (!file.endsWith(".ts")) return; + results.push(file); + } + }); + return results; +} diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index 86bff6b4..41a97b6a 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,19 +1,18 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { getIpAdress, check } from "@fosscord/api"; +import { getIpAdress, check, route } from "@fosscord/api"; import { BanCreateSchema } from "@fosscord/api/schema/Ban"; const router: Router = Router(); - -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id } = req.params; var bans = await Ban.find({ guild_id: guild_id }); return res.json(bans); }); -router.get("/:user", async (req: Request, res: Response) => { +router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const user_id = req.params.ban; @@ -21,15 +20,14 @@ router.get("/:user", async (req: Request, res: Response) => { return res.json(ban); }); -router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Response) => { +router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const banned_user_id = req.params.user_id; const banned_user = await User.getPublicUser(banned_user_id); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("BAN_MEMBERS"); + if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400); - if (perms.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); + if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); const ban = new Ban({ user_id: banned_user_id, @@ -55,17 +53,14 @@ router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Respon return res.json(ban); }); -router.delete("/:user_id", async (req: Request, res: Response) => { - var { guild_id } = req.params; - var banned_user_id = req.params.user_id; +router.delete("/:user_id", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { + const { guild_id, user_id } = req.params; - const banned_user = await User.getPublicUser(banned_user_id); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("BAN_MEMBERS"); + const banned_user = await User.getPublicUser(user_id); await Promise.all([ Ban.delete({ - user_id: banned_user_id, + user_id: user_id, guild_id }), diff --git a/api/src/util/route.ts b/api/src/util/route.ts new file mode 100644 index 00000000..0302f3ec --- /dev/null +++ b/api/src/util/route.ts @@ -0,0 +1,66 @@ +import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; +import { NextFunction, Request, Response } from "express"; +import fs from "fs"; +import path from "path"; +import Ajv from "ajv"; +import { AnyValidateFunction } from "ajv/dist/core"; +import { FieldErrors } from ".."; + +const SchemaPath = path.join(__dirname, "..", "..", "assets", "schemas.json"); +const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); +export const ajv = new Ajv({ allErrors: true, parseDate: true, allowDate: true, schemas, messages: true }); + +declare global { + namespace Express { + interface Request { + permission?: Permissions; + } + } +} + +export type RouteSchema = string; // typescript interface name +export type RouteResponse = { status: number; body?: RouteSchema; headers?: Record }; + +export interface RouteOptions { + permission?: PermissionResolvable; + body?: RouteSchema; + response?: RouteResponse; + example?: { + body?: any; + path?: string; + event?: EventData; + headers?: Record; + }; +} + +export function route(opts: RouteOptions) { + var validate: AnyValidateFunction; + if (opts.body) { + // @ts-ignore + validate = ajv.getSchema(opts.body); + if (!validate) throw new Error(`Body schema ${opts.body} not found`); + } + + return async (req: Request, res: Response, next: NextFunction) => { + if (opts.permission) { + const required = new Permissions(opts.permission); + const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); + console.log(required.bitfield, permission.bitfield, permission.bitfield & required.bitfield); + + // bitfield comparison: check if user lacks certain permission + if (!permission.has(required)) { + throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); + } + + if (validate) { + const valid = validate(req.body); + if (!valid) { + const fields: Record = {}; + validate.errors?.forEach((x) => (fields[x.instancePath] = { code: x.keyword, message: x.message || "" })); + throw FieldErrors(fields); + } + } + } + next(); + }; +} From 6ba9c834bd2d5f4fac07e3fb9ded2625ce3c44ad Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 12 Sep 2021 23:28:56 +0200 Subject: [PATCH 14/90] :sparkles: #307 done --- api/assets/schemas.json | 7039 ++++++++++++++++- api/package-lock.json | Bin 779701 -> 779701 bytes api/package.json | 6 +- api/scripts/generate_body_schema.ts | 3 +- api/src/routes/auth/login.ts | 110 +- api/src/routes/auth/register.ts | 373 +- api/src/routes/channels/#channel_id/index.ts | 49 +- .../routes/channels/#channel_id/invites.ts | 40 +- .../#channel_id/messages/#message_id/ack.ts | 10 +- .../#channel_id/messages/#message_id/index.ts | 16 +- .../messages/#message_id/reactions.ts | 33 +- .../#channel_id/messages/bulk-delete.ts | 9 +- .../channels/#channel_id/messages/index.ts | 103 +- .../channels/#channel_id/permissions.ts | 82 +- api/src/routes/channels/#channel_id/pins.ts | 33 +- api/src/routes/channels/#channel_id/typing.ts | 6 +- .../routes/channels/#channel_id/webhooks.ts | 16 +- api/src/routes/discoverable-guilds.ts | 4 +- api/src/routes/experiments.ts | 3 +- api/src/routes/gateway.ts | 3 +- api/src/routes/guilds/#guild_id/bans.ts | 8 +- api/src/routes/guilds/#guild_id/channels.ts | 68 +- api/src/routes/guilds/#guild_id/delete.ts | 3 +- api/src/routes/guilds/#guild_id/index.ts | 34 +- api/src/routes/guilds/#guild_id/invites.ts | 6 +- .../#guild_id/members/#member_id/index.ts | 36 +- .../#guild_id/members/#member_id/nick.ts | 9 +- .../#member_id/roles/#role_id/index.ts | 11 +- .../routes/guilds/#guild_id/members/index.ts | 6 +- api/src/routes/guilds/#guild_id/regions.ts | 4 +- api/src/routes/guilds/#guild_id/roles.ts | 44 +- api/src/routes/guilds/#guild_id/templates.ts | 51 +- api/src/routes/guilds/#guild_id/vanity-url.ts | 24 +- .../#guild_id/voice-states/#user_id/index.ts | 57 +- .../#guild_id/voice-states/@me/index.ts | 15 - .../routes/guilds/#guild_id/welcome_screen.ts | 23 +- .../routes/guilds/#guild_id/widget.json.ts | 4 +- api/src/routes/guilds/#guild_id/widget.png.ts | 3 +- api/src/routes/guilds/#guild_id/widget.ts | 18 +- api/src/routes/guilds/index.ts | 19 +- api/src/routes/guilds/templates/index.ts | 12 +- api/src/routes/invites/index.ts | 8 +- api/src/routes/ping.ts | 3 +- api/src/routes/science.ts | 3 +- api/src/routes/users/#id/index.ts | 3 +- api/src/routes/users/#id/profile.ts | 17 +- api/src/routes/users/@me/affinities/guilds.ts | 3 +- api/src/routes/users/@me/affinities/user.ts | 3 +- api/src/routes/users/@me/channels.ts | 12 +- api/src/routes/users/@me/delete.ts | 4 +- api/src/routes/users/@me/devices.ts | 3 +- api/src/routes/users/@me/disable.ts | 3 +- api/src/routes/users/@me/guilds.ts | 6 +- api/src/routes/users/@me/index.ts | 25 +- api/src/routes/users/@me/library.ts | 3 +- api/src/routes/users/@me/relationships.ts | 150 +- api/src/routes/users/@me/settings.ts | 7 +- api/src/routes/voice/regions.ts | 4 +- api/src/schema/Ban.ts | 9 - api/src/schema/Channel.ts | 68 - api/src/schema/Emoji.ts | 13 - api/src/schema/Guild.ts | 106 - api/src/schema/Invite.ts | 22 - api/src/schema/Member.ts | 29 - api/src/schema/Message.ts | 92 - api/src/schema/Roles.ts | 29 - api/src/schema/Template.ts | 19 - api/src/schema/User.ts | 74 - api/src/schema/Widget.ts | 10 - api/src/schema/index.ts | 11 - api/src/util/Message.ts | 2 +- api/src/util/Voice.ts | 50 +- api/src/util/VoiceState.ts | 54 - api/src/util/index.ts | 1 - api/src/util/route.ts | 14 +- 75 files changed, 7703 insertions(+), 1550 deletions(-) delete mode 100644 api/src/routes/guilds/#guild_id/voice-states/@me/index.ts delete mode 100644 api/src/schema/Ban.ts delete mode 100644 api/src/schema/Channel.ts delete mode 100644 api/src/schema/Emoji.ts delete mode 100644 api/src/schema/Guild.ts delete mode 100644 api/src/schema/Invite.ts delete mode 100644 api/src/schema/Member.ts delete mode 100644 api/src/schema/Message.ts delete mode 100644 api/src/schema/Roles.ts delete mode 100644 api/src/schema/Template.ts delete mode 100644 api/src/schema/User.ts delete mode 100644 api/src/schema/Widget.ts delete mode 100644 api/src/schema/index.ts delete mode 100644 api/src/util/VoiceState.ts diff --git a/api/assets/schemas.json b/api/assets/schemas.json index b575bf60..59ab4c04 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -1,20 +1,75 @@ { - "DmChannelCreateSchema": { + "RegisterSchema": { "type": "object", "properties": { - "name": { + "username": { + "minLength": 2, + "maxLength": 32, "type": "string" }, - "recipients": { - "type": "array", - "items": { - "type": "string" - } + "password": { + "minLength": 1, + "maxLength": 72, + "type": "string" + }, + "consent": { + "type": "boolean" + }, + "email": { + "format": "email", + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "invite": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date-time" + }, + "gift_code_sku_id": { + "type": "string" + }, + "captcha_key": { + "type": "string" } }, "additionalProperties": false, "required": [ - "recipients" + "consent", + "password", + "username" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "LoginSchema": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + }, + "undelete": { + "type": "boolean" + }, + "captcha_key": { + "type": "string" + }, + "login_source": { + "type": "string" + }, + "gift_code_sku_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "login", + "password" ], "$schema": "http://json-schema.org/draft-07/schema#" }, @@ -22,10 +77,11 @@ "type": "object", "properties": { "name": { + "maxLength": 100, "type": "string" }, "type": { - "type": "integer" + "$ref": "#/definitions/ChannelType" }, "topic": { "type": "string" @@ -51,7 +107,7 @@ "type": "string" }, "type": { - "type": "integer" + "$ref": "#/definitions/ChannelPermissionOverwriteType" }, "allow": { "type": "bigint" @@ -89,107 +145,32 @@ "required": [ "name", "type" - ] - }, - "ChannelGuildPositionUpdateSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "position": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "id" - ] - }, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "InviteCreateSchema": { - "type": "object", - "properties": { - "target_user_id": { - "type": "string" - }, - "target_type": { - "type": "string" - }, - "validate": { - "type": "string" - }, - "max_age": { - "type": "integer" - }, - "max_uses": { - "type": "integer" - }, - "temporary": { - "type": "boolean" - }, - "unique": { - "type": "boolean" - }, - "target_user": { - "type": "string" - }, - "target_user_type": { - "type": "integer" - } - }, - "additionalProperties": false, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" ], - "type": "string" - }, - "EmbedImage": { - "type": "object", - "properties": { - "url": { - "type": "string" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "proxy_url": { - "type": "string" + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" }, - "height": { - "type": "integer" - }, - "width": { - "type": "integer" - } - }, - "additionalProperties": false - }, - "MessageCreateSchema": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "nonce": { - "type": "string" - }, - "tts": { - "type": "boolean" - }, - "flags": { - "type": "string" - }, - "embed": { - "additionalProperties": false, + "Embed": { "type": "object", "properties": { "title": { @@ -205,7 +186,8 @@ "type": "string" }, "timestamp": { - "type": "string" + "type": "string", + "format": "date-time" }, "color": { "type": "integer" @@ -289,8 +271,148 @@ ] } } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MessageCreateSchema": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "tts": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "embeds": { + "type": "array", + "items": { + "$ref": "#/definitions/Embed" } }, + "embed": { + "$ref": "#/definitions/Embed" + }, "allowed_mentions": { "type": "object", "properties": { @@ -346,6 +468,1558 @@ "file": {} }, "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "InviteCreateSchema": { + "type": "object", + "properties": { + "target_user_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "validate": { + "type": "string" + }, + "max_age": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MessageAcknowledgeSchema": { + "type": "object", + "properties": { + "manual": { + "type": "boolean" + }, + "mention_count": { + "type": "integer" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "BulkDeleteSchema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "messages" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "ChannelPermissionOverwriteSchema": { + "type": "object", + "properties": { + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "WebhookCreateSchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 80, + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "avatar", + "name" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, "$schema": "http://json-schema.org/draft-07/schema#" }, "BanCreateSchema": { @@ -359,12 +2033,520 @@ } }, "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "ChannelReorderSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "lock_permissions": { + "type": "boolean" + }, + "parent_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "id" + ] + }, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, "$schema": "http://json-schema.org/draft-07/schema#" }, "GuildCreateSchema": { "type": "object", "properties": { "name": { + "maxLength": 100, "type": "string" }, "region": { @@ -393,6 +2575,247 @@ "required": [ "name" ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, "$schema": "http://json-schema.org/draft-07/schema#" }, "GuildUpdateSchema": { @@ -438,6 +2861,7 @@ "type": "string" }, "name": { + "maxLength": 100, "type": "string" }, "region": { @@ -460,15 +2884,1291 @@ "required": [ "name" ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "GuildTemplateCreateSchema": { + "MemberChangeSchema": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MemberNickChangeSchema": { + "type": "object", + "properties": { + "nick": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "nick" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RoleModifySchema": { "type": "object", "properties": { "name": { "type": "string" }, - "avatar": { + "permissions": { + "type": "bigint" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RolePositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id", + "position" + ] + }, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "TemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { "type": "string" } }, @@ -476,6 +4176,1030 @@ "required": [ "name" ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "TemplateModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "VanityUrlSchema": { + "type": "object", + "properties": { + "code": { + "minLength": 1, + "maxLength": 20, + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "VoiceStateUpdateSchema": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "suppress": { + "type": "boolean" + }, + "request_to_speak_timestamp": { + "type": "string", + "format": "date-time" + }, + "self_mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "channel_id" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, "$schema": "http://json-schema.org/draft-07/schema#" }, "GuildUpdateWelcomeScreenSchema": { @@ -515,131 +5239,514 @@ } }, "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "VoiceStateUpdateSchema": { + "WidgetModifySchema": { "type": "object", "properties": { + "enabled": { + "type": "boolean" + }, "channel_id": { "type": "string" - }, - "suppress": { - "type": "boolean" - }, - "request_to_speak_timestamp": { - "type": "string" } }, "additionalProperties": false, "required": [ - "channel_id" + "channel_id", + "enabled" ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "MemberCreateSchema": { - "type": "object", - "properties": { - "id": { - "type": "string" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "nick": { - "type": "string" + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" }, - "guild_id": { - "type": "string" - }, - "joined_at": { - "type": "string", - "format": "date-time" - } - }, - "additionalProperties": false, - "required": [ - "guild_id", - "id", - "joined_at", - "nick" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "MemberNickChangeSchema": { - "type": "object", - "properties": { - "nick": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "nick" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "MemberChangeSchema": { - "type": "object", - "properties": { - "roles": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "RoleModifySchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "permissions": { - "type": "bigint" - }, - "color": { - "type": "integer" - }, - "hoist": { - "type": "boolean" - }, - "mentionable": { - "type": "boolean" - }, - "position": { - "type": "integer" - } - }, - "additionalProperties": false, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "RolePositionUpdateSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } }, - "position": { - "type": "integer" - } + "additionalProperties": false }, - "additionalProperties": false, - "required": [ - "id", - "position" - ] + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "TemplateCreateSchema": { + "GuildTemplateCreateSchema": { "type": "object", "properties": { "name": { "type": "string" }, - "description": { + "avatar": { "type": "string" } }, @@ -647,34 +5754,256 @@ "required": [ "name" ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "TemplateModifySchema": { - "type": "object", - "properties": { - "name": { + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], "type": "string" }, - "description": { - "type": "string" + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, - "additionalProperties": false, - "required": [ - "name" - ], "$schema": "http://json-schema.org/draft-07/schema#" }, - "EmojiCreateSchema": { + "DmChannelCreateSchema": { "type": "object", "properties": { "name": { "type": "string" }, - "image": { - "type": "string" - }, - "roles": { + "recipients": { "type": "array", "items": { "type": "string" @@ -683,21 +6012,264 @@ }, "additionalProperties": false, "required": [ - "image", - "name" + "recipients" ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, "$schema": "http://json-schema.org/draft-07/schema#" }, "UserModifySchema": { "type": "object", "properties": { "username": { + "minLength": 1, + "maxLength": 100, "type": "string" }, "avatar": { "type": "string" }, "bio": { + "maxLength": 1024, "type": "string" }, "accent_color": { @@ -717,6 +6289,759 @@ } }, "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RelationshipPutSchema": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/RelationshipType" + } + }, + "additionalProperties": false, + "required": [ + "type" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RelationshipPostSchema": { + "type": "object", + "properties": { + "discriminator": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "discriminator", + "username" + ], + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, "$schema": "http://json-schema.org/draft-07/schema#" }, "UserSettingsSchema": { @@ -917,23 +7242,247 @@ "theme", "timezone_offset" ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "WidgetModifySchema": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, - "channel_id": { + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/EmbedType" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" } }, - "additionalProperties": false, - "required": [ - "channel_id", - "enabled" - ], "$schema": "http://json-schema.org/draft-07/schema#" } } \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index 8b2c93f8d7c14ba3e4cb65fa7d3cedde0be7f413..c14b92fa729601463402b4691a33a91713d1bb95 100644 GIT binary patch delta 63 zcmdmbT7TLZS^xk5 diff --git a/api/package.json b/api/package.json index b762b944..e7b476c7 100644 --- a/api/package.json +++ b/api/package.json @@ -12,7 +12,9 @@ "build-docker": "tsc -p tsconfig-docker.json", "dev": "tsnd --respawn src/start.ts", "patch": "npx patch-package", - "postinstall": "npm run patch" + "postinstall": "npm run patch", + "generate:docs": "ts-node scripts/generate_openapi_schema.ts", + "generate:schema": "ts-node scripts/generate_body_schema.ts" }, "repository": { "type": "git", @@ -60,7 +62,7 @@ "dependencies": { "@fosscord/util": "file:../util", "ajv": "^8.4.0", - "ajv-formats": "^2.1.0", + "ajv-formats": "^2.1.1", "amqplib": "^0.8.0", "assert": "^1.5.0", "atomically": "^1.7.0", diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts index 69e62085..c406cab8 100644 --- a/api/scripts/generate_body_schema.ts +++ b/api/scripts/generate_body_schema.ts @@ -33,12 +33,13 @@ function main() { const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); if (!part) continue; - definitions = { ...definitions, ...part.definitions, [name]: { ...part, definitions: undefined } }; + definitions = { ...definitions, [name]: { ...part } }; } fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); } +// #/definitions/ main(); function walk(dir: string) { diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index 2e2f763d..f672658a 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { check, FieldErrors, Length } from "@fosscord/api"; +import { FieldErrors, route } from "@fosscord/api"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { Config, User } from "@fosscord/util"; @@ -8,67 +8,65 @@ import { adjustEmail } from "./register"; const router: Router = Router(); export default router; -router.post( - "/", - check({ - login: new Length(String, 2, 100), // email or telephone - password: new Length(String, 8, 72), - $undelete: Boolean, - $captcha_key: String, - $login_source: String, - $gift_code_sku_id: String - }), - async (req: Request, res: Response) => { - const { login, password, captcha_key, undelete } = req.body; - const email = adjustEmail(login); - console.log("login", email); +export interface LoginSchema { + login: string; + password: string; + undelete?: boolean; + captcha_key?: string; + login_source?: string; + gift_code_sku_id?: string; +} - const config = Config.get(); +router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Response) => { + const { login, password, captcha_key, undelete } = req.body as LoginSchema; + const email = adjustEmail(login); + console.log("login", email); - if (config.login.requireCaptcha && config.security.captcha.enabled) { - if (!captcha_key) { - const { sitekey, service } = config.security.captcha; - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service - }); - } + const config = Config.get(); - // TODO: check captcha + if (config.login.requireCaptcha && config.security.captcha.enabled) { + if (!captcha_key) { + const { sitekey, service } = config.security.captcha; + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service + }); } - const user = await User.findOneOrFail({ - where: [{ phone: login }, { email: login }], - select: ["data", "id", "disabled", "deleted", "settings"] - }).catch((e) => { - throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); - }); - - if (undelete) { - // undelete refers to un'disable' here - if (user.disabled) await User.update({ id: user.id }, { disabled: false }); - if (user.deleted) await User.update({ id: user.id }, { deleted: false }); - } else { - if (user.deleted) return res.status(400).json({ message: "This account is scheduled for deletion.", code: 20011 }); - if (user.disabled) return res.status(400).json({ message: req.t("auth:login.ACCOUNT_DISABLED"), code: 20013 }); - } - - // the salt is saved in the password refer to bcrypt docs - const same_password = await bcrypt.compare(password, user.data.hash || ""); - if (!same_password) { - throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); - } - - const token = await generateToken(user.id); - - // Notice this will have a different token structure, than discord - // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package - // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png - - res.json({ token, settings: user.settings }); + // TODO: check captcha } -); + + const user = await User.findOneOrFail({ + where: [{ phone: login }, { email: login }], + select: ["data", "id", "disabled", "deleted", "settings"] + }).catch((e) => { + throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); + }); + + if (undelete) { + // undelete refers to un'disable' here + if (user.disabled) await User.update({ id: user.id }, { disabled: false }); + if (user.deleted) await User.update({ id: user.id }, { deleted: false }); + } else { + if (user.deleted) return res.status(400).json({ message: "This account is scheduled for deletion.", code: 20011 }); + if (user.disabled) return res.status(400).json({ message: req.t("auth:login.ACCOUNT_DISABLED"), code: 20013 }); + } + + // the salt is saved in the password refer to bcrypt docs + const same_password = await bcrypt.compare(password, user.data.hash || ""); + if (!same_password) { + throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); + } + + const token = await generateToken(user.id); + + // Notice this will have a different token structure, than discord + // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package + // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png + + res.json({ token, settings: user.settings }); +}); export async function generateToken(id: string) { const iat = Math.floor(Date.now() / 1000); diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index d3c85778..e0af1d6d 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { trimSpecial, User, Snowflake, Config, defaultSettings } from "@fosscord/util"; import bcrypt from "bcrypt"; -import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "@fosscord/api"; +import { EMAIL_REGEX, FieldErrors, route } from "@fosscord/api"; import "missing-native-js-functions"; import { generateToken } from "./login"; import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; @@ -10,208 +10,215 @@ import { In } from "typeorm"; const router: Router = Router(); -router.post( - "/", - check({ - username: new Length(String, 2, 32), - // TODO: check min password length in config - // prevent Denial of Service with max length of 72 chars - password: new Length(String, 8, 72), - consent: Boolean, - $email: new Length(Email, 5, 100), - $fingerprint: String, - $invite: String, - $date_of_birth: Date, // "2000-04-03" - $gift_code_sku_id: String, - $captcha_key: String - }), - async (req: Request, res: Response) => { - const { - email, - username, - password, - consent, - fingerprint, - invite, - date_of_birth, - gift_code_sku_id, // ? what is this - captcha_key - } = req.body; +export interface RegisterSchema { + /** + * @minLength 2 + * @maxLength 32 + */ + username: string; + /** + * @minLength 1 + * @maxLength 72 + */ + password: string; // TODO: use password strength of config + consent: boolean; + /** + * @TJS-format email + */ + email?: string; + fingerprint?: string; + invite?: string; + date_of_birth?: Date; // "2000-04-03" + gift_code_sku_id?: string; + captcha_key?: string; +} - // get register Config - const { register, security } = Config.get(); - const ip = getIpAdress(req); +router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Response) => { + const { + email, + username, + password, + consent, + fingerprint, + invite, + date_of_birth, + gift_code_sku_id, // ? what is this + captcha_key + } = req.body; - if (register.blockProxies) { - if (isProxy(await IPAnalysis(ip))) { - console.log(`proxy ${ip} blocked from registration`); - throw new HTTPError("Your IP is blocked from registration"); - } + // get register Config + const { register, security } = Config.get(); + const ip = getIpAdress(req); + + if (register.blockProxies) { + if (isProxy(await IPAnalysis(ip))) { + console.log(`proxy ${ip} blocked from registration`); + throw new HTTPError("Your IP is blocked from registration"); } + } - console.log("register", req.body.email, req.body.username, ip); - // TODO: automatically join invite - // TODO: gift_code_sku_id? - // TODO: check password strength + console.log("register", req.body.email, req.body.username, ip); + // TODO: automatically join invite + // TODO: gift_code_sku_id? + // TODO: check password strength - // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick - let adjusted_email = adjustEmail(email); + // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick + let adjusted_email = adjustEmail(email); - // adjusted_password will be the hash of the password - let adjusted_password = ""; + // adjusted_password will be the hash of the password + let adjusted_password = ""; - // trim special uf8 control characters -> Backspace, Newline, ... - let adjusted_username = trimSpecial(username); + // trim special uf8 control characters -> Backspace, Newline, ... + let adjusted_username = trimSpecial(username); - // discriminator will be randomly generated - let discriminator = ""; + // discriminator will be randomly generated + let discriminator = ""; - // check if registration is allowed - if (!register.allowNewRegistration) { - throw FieldErrors({ - email: { code: "REGISTRATION_DISABLED", message: req.t("auth:register.REGISTRATION_DISABLED") } - }); - } + // check if registration is allowed + if (!register.allowNewRegistration) { + throw FieldErrors({ + email: { code: "REGISTRATION_DISABLED", message: req.t("auth:register.REGISTRATION_DISABLED") } + }); + } - // check if the user agreed to the Terms of Service - if (!consent) { - throw FieldErrors({ - consent: { code: "CONSENT_REQUIRED", message: req.t("auth:register.CONSENT_REQUIRED") } - }); - } + // check if the user agreed to the Terms of Service + if (!consent) { + throw FieldErrors({ + consent: { code: "CONSENT_REQUIRED", message: req.t("auth:register.CONSENT_REQUIRED") } + }); + } - // require invite to register -> e.g. for organizations to send invites to their employees - if (register.requireInvite && !invite) { - throw FieldErrors({ - email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } - }); - } + // require invite to register -> e.g. for organizations to send invites to their employees + if (register.requireInvite && !invite) { + throw FieldErrors({ + email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } + }); + } - if (email) { - // replace all dots and chars after +, if its a gmail.com email - if (!adjusted_email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); + if (email) { + // replace all dots and chars after +, if its a gmail.com email + if (!adjusted_email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); - // check if there is already an account with this email - const exists = await User.findOneOrFail({ email: adjusted_email }).catch((e) => {}); - - if (exists) { - throw FieldErrors({ - email: { - code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") - } - }); - } - } else if (register.email.necessary) { - throw FieldErrors({ - email: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } - }); - } - - if (register.dateOfBirth.necessary && !date_of_birth) { - throw FieldErrors({ - date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } - }); - } else if (register.dateOfBirth.minimum) { - const minimum = new Date(); - minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); - - // higher is younger - if (date_of_birth > minimum) { - throw FieldErrors({ - date_of_birth: { - code: "DATE_OF_BIRTH_UNDERAGE", - message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { years: register.dateOfBirth.minimum }) - } - }); - } - } - - if (!register.allowMultipleAccounts) { - // TODO: check if fingerprint was eligible generated - const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } }); - - if (exists) { - throw FieldErrors({ - email: { - code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") - } - }); - } - } - - if (register.requireCaptcha && security.captcha.enabled) { - if (!captcha_key) { - const { sitekey, service } = security.captcha; - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service - }); - } - - // TODO: check captcha - } - - // the salt is saved in the password refer to bcrypt docs - adjusted_password = await bcrypt.hash(password, 12); - - let exists; - // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists - // if it all five times already exists, abort with USERNAME_TOO_MANY_USERS error - // else just continue - // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? - for (let tries = 0; tries < 5; tries++) { - discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); - exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] }); - if (!exists) break; - } + // check if there is already an account with this email + const exists = await User.findOneOrFail({ email: adjusted_email }).catch((e) => {}); if (exists) { throw FieldErrors({ - username: { - code: "USERNAME_TOO_MANY_USERS", - message: req.t("auth:register.USERNAME_TOO_MANY_USERS") + email: { + code: "EMAIL_ALREADY_REGISTERED", + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") } }); } - - // TODO: save date_of_birth - // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed - // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - - const user = await new User({ - created_at: new Date(), - username: adjusted_username, - discriminator, - id: Snowflake.generate(), - bot: false, - system: false, - desktop: false, - mobile: false, - premium: true, - premium_type: 2, - bio: "", - mfa_enabled: false, - verified: false, - disabled: false, - deleted: false, - email: adjusted_email, - nsfw_allowed: true, // TODO: depending on age - public_flags: "0", - flags: "0", // TODO: generate - data: { - hash: adjusted_password, - valid_tokens_since: new Date() - }, - settings: { ...defaultSettings, locale: req.language || "en-US" }, - fingerprints: [] - }).save(); - - return res.json({ token: await generateToken(user.id) }); + } else if (register.email.necessary) { + throw FieldErrors({ + email: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + }); } -); + + if (register.dateOfBirth.necessary && !date_of_birth) { + throw FieldErrors({ + date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + }); + } else if (register.dateOfBirth.minimum) { + const minimum = new Date(); + minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); + + // higher is younger + if (date_of_birth > minimum) { + throw FieldErrors({ + date_of_birth: { + code: "DATE_OF_BIRTH_UNDERAGE", + message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { years: register.dateOfBirth.minimum }) + } + }); + } + } + + if (!register.allowMultipleAccounts) { + // TODO: check if fingerprint was eligible generated + const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } }); + + if (exists) { + throw FieldErrors({ + email: { + code: "EMAIL_ALREADY_REGISTERED", + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED") + } + }); + } + } + + if (register.requireCaptcha && security.captcha.enabled) { + if (!captcha_key) { + const { sitekey, service } = security.captcha; + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service + }); + } + + // TODO: check captcha + } + + // the salt is saved in the password refer to bcrypt docs + adjusted_password = await bcrypt.hash(password, 12); + + let exists; + // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists + // if it all five times already exists, abort with USERNAME_TOO_MANY_USERS error + // else just continue + // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? + for (let tries = 0; tries < 5; tries++) { + discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); + exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] }); + if (!exists) break; + } + + if (exists) { + throw FieldErrors({ + username: { + code: "USERNAME_TOO_MANY_USERS", + message: req.t("auth:register.USERNAME_TOO_MANY_USERS") + } + }); + } + + // TODO: save date_of_birth + // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed + // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false + + const user = await new User({ + created_at: new Date(), + username: adjusted_username, + discriminator, + id: Snowflake.generate(), + bot: false, + system: false, + desktop: false, + mobile: false, + premium: true, + premium_type: 2, + bio: "", + mfa_enabled: false, + verified: false, + disabled: false, + deleted: false, + email: adjusted_email, + nsfw_allowed: true, // TODO: depending on age + public_flags: "0", + flags: "0", // TODO: generate + data: { + hash: adjusted_password, + valid_tokens_since: new Date() + }, + settings: { ...defaultSettings, locale: req.language || "en-US" }, + fingerprints: [] + }).save(); + + return res.json({ token: await generateToken(user.id) }); +}); export function adjustEmail(email: string): string | undefined { // body parser already checked if it is a valid email diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 46554d70..02ac9884 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,48 +1,59 @@ -import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission } from "@fosscord/util"; +import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, ChannelType, ChannelPermissionOverwriteType } from "@fosscord/util"; import { Router, Response, Request } from "express"; -import { HTTPError } from "lambert-server"; -import { ChannelModifySchema } from "../../../schema/Channel"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel // TODO: Get channel -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - permission.hasThrow("VIEW_CHANNEL"); - return res.send(channel); }); -router.delete("/", async (req: Request, res: Response) => { +router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); - permission.hasThrow("MANAGE_CHANNELS"); - // TODO: Dm channel "close" not delete const data = channel; - await emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent); - - await Channel.delete({ id: channel_id }); + await Promise.all([emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent), Channel.delete({ id: channel_id })]); res.send(data); }); -router.patch("/", check(ChannelModifySchema), async (req: Request, res: Response) => { +export interface ChannelModifySchema { + /** + * @maxLength 100 + */ + name: string; + type: ChannelType; + topic?: string; + bitrate?: number; + user_limit?: number; + rate_limit_per_user?: number; + position?: number; + permission_overwrites?: { + id: string; + type: ChannelPermissionOverwriteType; + allow: bigint; + deny: bigint; + }[]; + parent_id?: string; + id?: string; // is not used (only for guild create) + nsfw?: boolean; + rtc_region?: string; + default_auto_archive_duration?: number; +} + +router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { var payload = req.body as ChannelModifySchema; const { channel_id } = req.params; - const permission = await getPermission(req.user_id, undefined, channel_id); - permission.hasThrow("MANAGE_CHANNELS"); - const channel = await Channel.findOneOrFail({ id: channel_id }); channel.assign(payload); diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index c6909fd0..39263185 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -1,14 +1,25 @@ import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { random } from "@fosscord/api"; -import { InviteCreateSchema } from "../../../schema/Invite"; import { getPermission, Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util"; import { isTextChannel } from "./messages"; const router: Router = Router(); -router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) => { +export interface InviteCreateSchema { + target_user_id?: string; + target_type?: string; + validate?: string; //? wtf is this + max_age?: number; + max_uses?: number; + temporary?: boolean; + unique?: boolean; + target_user?: string; + target_user_type?: number; +} + +router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE" }), async (req: Request, res: Response) => { const { user_id } = req; const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] }); @@ -19,23 +30,6 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) } const { guild_id } = channel; - const permission = await getPermission(user_id, guild_id, undefined, { - guild_select: [ - "banner", - "description", - "features", - "icon", - "id", - "name", - "nsfw", - "nsfw_level", - "splash", - "vanity_url_code", - "verification_level" - ] as (keyof Guild)[] - }); - permission.hasThrow("CREATE_INSTANT_INVITE"); - const expires_at = new Date(req.body.max_age * 1000 + Date.now()); const invite = await new Invite({ @@ -52,14 +46,14 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) }).save(); const data = invite.toJSON(); data.inviter = await User.getPublicUser(req.user_id); - data.guild = permission.cache.guild; + data.guild = await Guild.findOne({ id: guild_id }); data.channel = channel; await emitEvent({ event: "INVITE_CREATE", data, guild_id } as InviteCreateEvent); res.status(201).send(data); }); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { const { user_id } = req; const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); @@ -68,8 +62,6 @@ router.get("/", async (req: Request, res: Response) => { throw new HTTPError("This channel doesn't exist", 404); } const { guild_id } = channel; - const permission = await getPermission(user_id, guild_id); - permission.hasThrow("MANAGE_CHANNELS"); const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation }); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index aab51484..97d1d19e 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -1,14 +1,18 @@ import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util"; import { Request, Response, Router } from "express"; - -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router = Router(); // TODO: check if message exists // TODO: send read state event to all channel members -router.post("/", check({ $manual: Boolean, $mention_count: Number }), async (req: Request, res: Response) => { +export interface MessageAcknowledgeSchema { + manual?: boolean; + mention_count?: number; +} + +router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; const permission = await getPermission(req.user_id, undefined, channel_id); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index e9588bd3..d0f780db 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -1,12 +1,13 @@ import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util"; import { Router, Response, Request } from "express"; -import { MessageCreateSchema } from "../../../../../schema/Message"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { handleMessage, postHandleMessage } from "@fosscord/api"; +import { MessageCreateSchema } from "../index"; const router = Router(); +// TODO: message content/embed string length limit -router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; var body = req.body as MessageCreateSchema; @@ -47,14 +48,17 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response // TODO: delete attachments in message -router.delete("/", async (req: Request, res: Response) => { +// permission check only if deletes messagr from other user +router.delete("/", route({}), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); const message = await Message.findOneOrFail({ id: message_id }); - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - if (message.author_id !== req.user_id) permission.hasThrow("MANAGE_MESSAGES"); + if (message.author_id !== req.user_id) { + const permission = await getPermission(req.user_id, channel.guild_id, channel_id); + permission.hasThrow("MANAGE_MESSAGES"); + } await Message.delete({ id: message_id }); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts index f60484b5..f2b83d40 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts @@ -13,6 +13,7 @@ import { PublicUserProjection, User } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { In } from "typeorm"; @@ -35,14 +36,11 @@ function getEmoji(emoji: string): PartialEmoji { }; } -router.delete("/", async (req: Request, res: Response) => { +router.delete("/", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - await Message.update({ id: message_id, channel_id }, { reactions: [] }); await emitEvent({ @@ -58,13 +56,10 @@ router.delete("/", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/:emoji", async (req: Request, res: Response) => { +router.delete("/:emoji", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - const message = await Message.findOneOrFail({ id: message_id, channel_id }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); @@ -88,7 +83,7 @@ router.delete("/:emoji", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.get("/:emoji", async (req: Request, res: Response) => { +router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); @@ -96,9 +91,6 @@ router.get("/:emoji", async (req: Request, res: Response) => { const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!reaction) throw new HTTPError("Reaction not found", 404); - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("VIEW_CHANNEL"); - const users = await User.find({ where: { id: In(reaction.user_ids) @@ -109,7 +101,7 @@ router.get("/:emoji", async (req: Request, res: Response) => { res.json(users); }); -router.put("/:emoji/:user_id", async (req: Request, res: Response) => { +router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY" }), async (req: Request, res: Response) => { const { message_id, channel_id, user_id } = req.params; if (user_id !== "@me") throw new HTTPError("Invalid user"); const emoji = getEmoji(req.params.emoji); @@ -118,13 +110,11 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { const message = await Message.findOneOrFail({ id: message_id, channel_id }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("READ_MESSAGE_HISTORY"); - if (!already_added) permissions.hasThrow("ADD_REACTIONS"); + if (!already_added) req.permission!.hasThrow("ADD_REACTIONS"); if (emoji.id) { const external_emoji = await Emoji.findOneOrFail({ id: emoji.id }); - if (!already_added) permissions.hasThrow("USE_EXTERNAL_EMOJIS"); + if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS"); emoji.animated = external_emoji.animated; emoji.name = external_emoji.name; } @@ -154,7 +144,7 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/:emoji/:user_id", async (req: Request, res: Response) => { +router.delete("/:emoji/:user_id", route({}), async (req: Request, res: Response) => { var { message_id, channel_id, user_id } = req.params; const emoji = getEmoji(req.params.emoji); @@ -162,10 +152,11 @@ router.delete("/:emoji/:user_id", async (req: Request, res: Response) => { const channel = await Channel.findOneOrFail({ id: channel_id }); const message = await Message.findOneOrFail({ id: message_id, channel_id }); - const permissions = await getPermission(req.user_id, undefined, channel_id); - if (user_id === "@me") user_id = req.user_id; - else permissions.hasThrow("MANAGE_MESSAGES"); + else { + const permissions = await getPermission(req.user_id, undefined, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + } const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts index 5d7566e1..a0fe7cc0 100644 --- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts @@ -1,18 +1,21 @@ import { Router, Response, Request } from "express"; import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { In } from "typeorm"; const router: Router = Router(); export default router; +export interface BulkDeleteSchema { + messages: string[]; +} + // TODO: should users be able to bulk delete messages or only bots? // TODO: should this request fail, if you provide messages older than 14 days/invalid ids? // https://discord.com/developers/docs/resources/channel#bulk-delete-messages -router.post("/", check({ messages: [String] }), async (req: Request, res: Response) => { +router.post("/", route({ body: "BulkDeleteSchema" }), async (req: Request, res: Response) => { const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 591ebbbe..11334367 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,10 +1,8 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, getPermission, Message } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { MessageCreateSchema } from "../../../../schema/Message"; -import { check, instanceOf, Length } from "@fosscord/api"; +import { instanceOf, Length, route } from "@fosscord/api"; import multer from "multer"; -import { Query } from "mongoose"; import { sendMessage } from "@fosscord/api"; import { uploadFile } from "@fosscord/api"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; @@ -31,6 +29,30 @@ export function isTextChannel(type: ChannelType): boolean { } } +export interface MessageCreateSchema { + content?: string; + nonce?: string; + tts?: boolean; + flags?: string; + embeds?: Embed[]; + embed?: Embed; + // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object) + allowed_mentions?: { + parse?: string[]; + roles?: string[]; + users?: string[]; + replied_user?: boolean; + }; + message_reference?: { + message_id: string; + channel_id: string; + guild_id?: string; + fail_if_not_exists?: boolean; + }; + payload_json?: string; + file?: any; +} + // https://discord.com/developers/docs/resources/channel#create-message // get messages router.get("/", async (req: Request, res: Response) => { @@ -109,39 +131,44 @@ const messageUpload = multer({ // TODO: check allowed_mentions // Send message -router.post("/", messageUpload.single("file"), async (req: Request, res: Response) => { - const { channel_id } = req.params; - var body = req.body as MessageCreateSchema; - const attachments: Attachment[] = []; - - if (req.file) { - try { - const file = await uploadFile(`/attachments/${channel_id}`, req.file); - attachments.push({ ...file, proxy_url: file.url }); - } catch (error) { - return res.status(400).json(error); +router.post( + "/", + messageUpload.single("file"), + async (req, res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); } + + next(); + }, + route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + var body = req.body as MessageCreateSchema; + const attachments: Attachment[] = []; + + if (req.file) { + try { + const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); + attachments.push({ ...file, proxy_url: file.url }); + } catch (error) { + return res.status(400).json(error); + } + } + + const embeds = []; + if (body.embed) embeds.push(body.embed); + const data = await sendMessage({ + ...body, + type: 0, + pinned: false, + author_id: req.user_id, + embeds, + channel_id, + attachments, + edited_timestamp: undefined + }); + + return res.json(data); } - - if (body.payload_json) { - body = JSON.parse(body.payload_json); - } - - const errors = instanceOf(MessageCreateSchema, body, { req }); - if (errors !== true) throw errors; - - const embeds = []; - if (body.embed) embeds.push(body.embed); - const data = await sendMessage({ - ...body, - type: 0, - pinned: false, - author_id: req.user_id, - embeds, - channel_id, - attachments, - edited_timestamp: undefined - }); - - return res.json(data); -}); +); diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 0465ca31..827e46f2 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -2,61 +2,61 @@ import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, get import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; const router: Router = Router(); // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) -router.put("/:overwrite_id", check({ allow: String, deny: String, type: Number, id: String }), async (req: Request, res: Response) => { - const { channel_id, overwrite_id } = req.params; - const body = req.body as { allow: bigint; deny: bigint; type: number; id: string }; +export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite {} - var channel = await Channel.findOneOrFail({ id: channel_id }); - if (!channel.guild_id) throw new HTTPError("Channel not found", 404); +router.put( + "/:overwrite_id", + route({ body: "ChannelPermissionOverwriteSchema", permission: "MANAGE_ROLES" }), + async (req: Request, res: Response) => { + const { channel_id, overwrite_id } = req.params; + const body = req.body as { allow: bigint; deny: bigint; type: number; id: string }; - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); - permissions.hasThrow("MANAGE_ROLES"); + var channel = await Channel.findOneOrFail({ id: channel_id }); + if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - if (body.type === 0) { - if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404); - } else if (body.type === 1) { - if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404); - } else throw new HTTPError("type not supported", 501); + if (body.type === 0) { + if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404); + } else if (body.type === 1) { + if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404); + } else throw new HTTPError("type not supported", 501); - // @ts-ignore - var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id); - if (!overwrite) { // @ts-ignore - overwrite = { - id: overwrite_id, - type: body.type, - allow: body.allow, - deny: body.deny - }; - channel.permission_overwrites.push(overwrite); + var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id); + if (!overwrite) { + // @ts-ignore + overwrite = { + id: overwrite_id, + type: body.type, + allow: body.allow, + deny: body.deny + }; + channel.permission_overwrites.push(overwrite); + } + overwrite.allow = body.allow; + overwrite.deny = body.deny; + + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel + } as ChannelUpdateEvent) + ]); + + return res.sendStatus(204); } - overwrite.allow = body.allow; - overwrite.deny = body.deny; - - // @ts-ignore - channel = await Channel.findOneOrFailAndUpdate({ id: channel_id }, channel, { new: true }); - - await emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel - } as ChannelUpdateEvent); - - return res.sendStatus(204); -}); +); // TODO: check permission hierarchy -router.delete("/:overwrite_id", async (req: Request, res: Response) => { +router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { channel_id, overwrite_id } = req.params; - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_ROLES"); - const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); diff --git a/api/src/routes/channels/#channel_id/pins.ts b/api/src/routes/channels/#channel_id/pins.ts index 33309c86..e71e659f 100644 --- a/api/src/routes/channels/#channel_id/pins.ts +++ b/api/src/routes/channels/#channel_id/pins.ts @@ -1,19 +1,26 @@ -import { Channel, ChannelPinsUpdateEvent, Config, emitEvent, getPermission, Message, MessageUpdateEvent } from "@fosscord/util"; +import { + Channel, + ChannelPinsUpdateEvent, + Config, + emitEvent, + getPermission, + Message, + MessageUpdateEvent, + DiscordApiErrors +} from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { DiscordApiErrors } from "@fosscord/util"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.put("/:message_id", async (req: Request, res: Response) => { +router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; const message = await Message.findOneOrFail({ id: message_id }); - const permission = await getPermission(req.user_id, message.guild_id, channel_id); - permission.hasThrow("VIEW_CHANNEL"); // * in dm channels anyone can pin messages -> only check for guilds - if (message.guild_id) permission.hasThrow("MANAGE_MESSAGES"); + if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); const pinned_count = await Message.count({ channel: { id: channel_id }, pinned: true }); const { maxPins } = Config.get().limits.channel; @@ -26,7 +33,6 @@ router.put("/:message_id", async (req: Request, res: Response) => { channel_id, data: message } as MessageUpdateEvent), - emitEvent({ event: "CHANNEL_PINS_UPDATE", channel_id, @@ -41,14 +47,11 @@ router.put("/:message_id", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/:message_id", async (req: Request, res: Response) => { +router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); - - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - permission.hasThrow("VIEW_CHANNEL"); - if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); + if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); const message = await Message.findOneOrFail({ id: message_id }); message.pinned = false; @@ -76,13 +79,9 @@ router.delete("/:message_id", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: ["READ_MESSAGE_HISTORY"] }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - permission.hasThrow("VIEW_CHANNEL"); - let pins = await Message.find({ channel_id: channel_id, pinned: true }); res.send(pins); diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index f1fb3c86..ad973bca 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -1,11 +1,10 @@ import { Channel, emitEvent, Member, TypingStartEvent } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Router, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; - const router: Router = Router(); -router.post("/", async (req: Request, res: Response) => { +router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => { const { channel_id } = req.params; const user_id = req.user_id; const timestamp = Date.now(); @@ -24,6 +23,7 @@ router.post("/", async (req: Request, res: Response) => { guild_id: channel.guild_id } } as TypingStartEvent); + res.sendStatus(204); }); diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 821a62db..f84dfcc5 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { check, Length } from "@fosscord/api"; +import { check, Length, route } from "@fosscord/api"; import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; @@ -7,9 +7,16 @@ import { DiscordApiErrors } from "@fosscord/util"; const router: Router = Router(); // TODO: webhooks +export interface WebhookCreateSchema { + /** + * @maxLength 80 + */ + name: string; + avatar: string; +} // TODO: use Image Data Type for avatar instead of String -router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), async (req: Request, res: Response) => { +router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { const channel_id = req.params.channel_id; const channel = await Channel.findOneOrFail({ id: channel_id }); @@ -20,12 +27,11 @@ router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), as const { maxWebhooks } = Config.get().limits.channel; if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - const permission = await getPermission(req.user_id, channel.guild_id); - permission.hasThrow("MANAGE_WEBHOOKS"); - var { avatar, name } = req.body as { name: string; avatar?: string }; name = trimSpecial(name); if (name === "clyde") throw new HTTPError("Invalid name", 400); + + // TODO: save webhook in database and send response }); export default router; diff --git a/api/src/routes/discoverable-guilds.ts b/api/src/routes/discoverable-guilds.ts index 0808727f..f667eb2a 100644 --- a/api/src/routes/discoverable-guilds.ts +++ b/api/src/routes/discoverable-guilds.ts @@ -1,10 +1,10 @@ import { Guild } from "@fosscord/util"; import { Router, Request, Response } from "express"; -import { In } from "typeorm"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { limit } = req.params; // ! this only works using SQL querys diff --git a/api/src/routes/experiments.ts b/api/src/routes/experiments.ts index 3bdbed62..966ed99c 100644 --- a/api/src/routes/experiments.ts +++ b/api/src/routes/experiments.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { // TODO: res.send({ fingerprint: "", assignments: [] }); }); diff --git a/api/src/routes/gateway.ts b/api/src/routes/gateway.ts index 3120718e..d4208341 100644 --- a/api/src/routes/gateway.ts +++ b/api/src/routes/gateway.ts @@ -1,9 +1,10 @@ import { Config } from "@fosscord/util"; import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { const { endpoint } = Config.get().gateway; res.json({ url: endpoint || process.env.GATEWAY || "ws://localhost:3002" }); }); diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index 41a97b6a..c7fda9ad 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -2,7 +2,11 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { getIpAdress, check, route } from "@fosscord/api"; -import { BanCreateSchema } from "@fosscord/api/schema/Ban"; + +export interface BanCreateSchema { + delete_message_days?: string; + reason?: string; +} const router: Router = Router(); router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { @@ -27,7 +31,7 @@ router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBER const banned_user = await User.getPublicUser(banned_user_id); if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400); - if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); + if (req.permission!.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); const ban = new Ban({ user_id: banned_user_id, diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index faeecb76..e21327d1 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -1,9 +1,8 @@ import { Router, Response, Request } from "express"; import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { ChannelModifySchema } from "../../../schema/Channel"; - -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; +import { ChannelModifySchema } from "../../channels/#channel_id"; const router = Router(); router.get("/", async (req: Request, res: Response) => { @@ -13,10 +12,7 @@ router.get("/", async (req: Request, res: Response) => { res.json(channels); }); -// TODO: check if channel type is permitted -// TODO: check if parent_id exists - -router.post("/", check(ChannelModifySchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel const { guild_id } = req.params; const body = req.body as ChannelModifySchema; @@ -26,45 +22,39 @@ router.post("/", check(ChannelModifySchema), async (req: Request, res: Response) res.status(201).json(channel); }); -// TODO: check if parent_id exists -router.patch( - "/", - check([{ id: String, $position: Number, $lock_permissions: Boolean, $parent_id: String }]), - async (req: Request, res: Response) => { - // changes guild channel position - const { guild_id } = req.params; - const body = req.body as { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[]; +export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[]; - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_CHANNELS"); +router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { + // changes guild channel position + const { guild_id } = req.params; + const body = req.body as ChannelReorderSchema; - await Promise.all([ - body.map(async (x) => { - if (!x.position && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); + await Promise.all([ + body.map(async (x) => { + if (!x.position && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); - const opts: any = {}; - if (x.position) opts.position = x.position; + const opts: any = {}; + if (x.position) opts.position = x.position; - if (x.parent_id) { - opts.parent_id = x.parent_id; - const parent_channel = await Channel.findOneOrFail({ - where: { id: x.parent_id, guild_id }, - select: ["permission_overwrites"] - }); - if (x.lock_permissions) { - opts.permission_overwrites = parent_channel.permission_overwrites; - } + if (x.parent_id) { + opts.parent_id = x.parent_id; + const parent_channel = await Channel.findOneOrFail({ + where: { id: x.parent_id, guild_id }, + select: ["permission_overwrites"] + }); + if (x.lock_permissions) { + opts.permission_overwrites = parent_channel.permission_overwrites; } + } - await Channel.update({ guild_id, id: x.id }, opts); - const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); + await Channel.update({ guild_id, id: x.id }, opts); + const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); - await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); - }) - ]); + await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); + }) + ]); - res.sendStatus(204); - } -); + res.sendStatus(204); +}); export default router; diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts index bbbd1fa4..7c3c5530 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/api/src/routes/guilds/#guild_id/delete.ts @@ -1,12 +1,13 @@ import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; +import { route } from "@fosscord/api"; const router = Router(); // discord prefixes this route with /delete instead of using the delete method // docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild -router.post("/", async (req: Request, res: Response) => { +router.post("/", route({}), async (req: Request, res: Response) => { var { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 244900ec..690d4103 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,23 +1,36 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { GuildUpdateSchema } from "../../../schema/Guild"; - -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; +import { GuildCreateSchema } from "../index"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface GuildUpdateSchema extends Omit { + banner?: string; + splash?: string; + description?: string; + features?: string[]; + verification_level?: number; + default_message_notifications?: number; + system_channel_flags?: number; + explicit_content_filter?: number; + public_updates_channel_id?: string; + afk_timeout?: number; + afk_channel_id?: string; + preferred_locale?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const [guild, member_count, member] = await Promise.all([ + const [guild, member] = await Promise.all([ Guild.findOneOrFail({ id: guild_id }), - Member.count({ guild_id: guild_id, id: req.user_id }), - Member.findOneOrFail({ id: req.user_id }) + Member.findOne({ guild_id: guild_id, id: req.user_id }) ]); - if (!member_count) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); // @ts-ignore guild.joined_at = member?.joined_at; @@ -25,14 +38,11 @@ router.get("/", async (req: Request, res: Response) => { return res.json(guild); }); -router.patch("/", check(GuildUpdateSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "GuildUpdateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const body = req.body as GuildUpdateSchema; const { guild_id } = req.params; // TODO: guild update check image - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/api/src/routes/guilds/#guild_id/invites.ts index 39a934ee..b7534e31 100644 --- a/api/src/routes/guilds/#guild_id/invites.ts +++ b/api/src/routes/guilds/#guild_id/invites.ts @@ -1,14 +1,12 @@ import { getPermission, Invite, PublicInviteRelation } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const permissions = await getPermission(req.user_id, guild_id); - permissions.hasThrow("MANAGE_GUILD"); - const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation }); return res.json(invites); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 8b04a508..1708b7eb 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -1,23 +1,15 @@ import { Request, Response, Router } from "express"; -import { - Guild, - Member, - User, - GuildMemberAddEvent, - getPermission, - PermissionResolvable, - Role, - GuildMemberUpdateEvent, - emitEvent -} from "@fosscord/util"; +import { Member, getPermission, Role, GuildMemberUpdateEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { MemberChangeSchema } from "@fosscord/api/schema/Member"; -import { In } from "typeorm"; +import { check, route } from "@fosscord/api"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface MemberChangeSchema { + roles?: string[]; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); @@ -26,8 +18,9 @@ router.get("/", async (req: Request, res: Response) => { return res.json(member); }); -router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) => { - const { guild_id, member_id } = req.params; +router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => { + let { guild_id, member_id } = req.params; + if (member_id === "@me") member_id = req.user_id; const body = req.body as MemberChangeSchema; const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] }); @@ -39,7 +32,7 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) } await member.save(); - // do not use promise.all as we have to first write to db before emitting the event + // do not use promise.all as we have to first write to db before emitting the event to catch errors await emitEvent({ event: "GUILD_MEMBER_UPDATE", guild_id, @@ -49,7 +42,7 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) res.json(member); }); -router.put("/", async (req: Request, res: Response) => { +router.put("/", route({}), async (req: Request, res: Response) => { let { guild_id, member_id } = req.params; if (member_id === "@me") member_id = req.user_id; @@ -59,12 +52,9 @@ router.put("/", async (req: Request, res: Response) => { res.sendStatus(204); }); -router.delete("/", async (req: Request, res: Response) => { +router.delete("/", route({ permission: "KICK_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("KICK_MEMBERS"); - await Member.removeFromGuild(member_id, guild_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts index 3f2975e6..27f7f65d 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts @@ -1,11 +1,14 @@ import { getPermission, Member, PermissionResolvable } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; -import { check } from "lambert-server"; -import { MemberNickChangeSchema } from "../../../../../schema/Member"; const router = Router(); -router.patch("/", check(MemberNickChangeSchema), async (req: Request, res: Response) => { +export interface MemberNickChangeSchema { + nick: string; +} + +router.patch("/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => { var { guild_id, member_id } = req.params; var permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; if (member_id === "@me") { diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index cb9bad9a..ae10be82 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -1,24 +1,19 @@ import { getPermission, Member } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); -router.delete("/:member_id/roles/:role_id", async (req: Request, res: Response) => { +router.delete("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - await Member.removeRole(member_id, guild_id, role_id); res.sendStatus(204); }); -router.put("/:member_id/roles/:role_id", async (req: Request, res: Response) => { +router.put("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - await Member.addRole(member_id, guild_id, role_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 198d6946..335f21c7 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,13 +1,15 @@ import { Request, Response, Router } from "express"; import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; -import { instanceOf, Length } from "@fosscord/api"; +import { instanceOf, Length, route } from "@fosscord/api"; import { MoreThan } from "typeorm"; const router = Router(); // TODO: not allowed for user -> only allowed for bots with privileged intents // TODO: send over websocket -router.get("/", async (req: Request, res: Response) => { +// TODO: check for GUILD_MEMBERS intent + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); await Member.IsInGuildOrFail(req.user_id, guild_id); diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/api/src/routes/guilds/#guild_id/regions.ts index 86208b79..75d24fd1 100644 --- a/api/src/routes/guilds/#guild_id/regions.ts +++ b/api/src/routes/guilds/#guild_id/regions.ts @@ -1,11 +1,11 @@ import { Config, Guild, Member } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { getVoiceRegions } from "@fosscord/api"; +import { getVoiceRegions, route } from "@fosscord/api"; import { getIpAdress } from "@fosscord/api"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); //TODO we should use an enum for guild's features and not hardcoded strings diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 76dd47c5..6b2902d9 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -2,23 +2,34 @@ import { Request, Response, Router } from "express"; import { Role, getPermission, - Snowflake, Member, GuildRoleCreateEvent, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, - Config + Config, + DiscordApiErrors } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { check } from "@fosscord/api"; -import { RoleModifySchema, RolePositionUpdateSchema } from "../../../schema/Roles"; -import { DiscordApiErrors } from "@fosscord/util"; +import { check, route } from "@fosscord/api"; import { In } from "typeorm"; const router: Router = Router(); +export interface RoleModifySchema { + name?: string; + permissions?: bigint; + color?: number; + hoist?: boolean; // whether the role should be displayed separately in the sidebar + mentionable?: boolean; // whether the role should be mentionable + position?: number; +} + +export type RolePositionUpdateSchema = { + id: string; + position: number; +}[]; + router.get("/", async (req: Request, res: Response) => { const guild_id = req.params.guild_id; @@ -29,13 +40,10 @@ router.get("/", async (req: Request, res: Response) => { return res.json(roles); }); -router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const body = req.body as RoleModifySchema; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - const role_count = await Role.count({ guild_id }); const { maxRoles } = Config.get().limits.guild; @@ -50,7 +58,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => ...body, guild_id: guild_id, managed: false, - permissions: String(perms.bitfield & (body.permissions || 0n)), + permissions: String(req.permission!.bitfield & (body.permissions || 0n)), tags: undefined }); @@ -69,14 +77,11 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => res.json(role); }); -router.delete("/:role_id", async (req: Request, res: Response) => { +router.delete("/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const { role_id } = req.params; if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); - const permissions = await getPermission(req.user_id, guild_id); - permissions.hasThrow("MANAGE_ROLES"); - await Promise.all([ Role.delete({ id: role_id, @@ -97,14 +102,11 @@ router.delete("/:role_id", async (req: Request, res: Response) => { // TODO: check role hierarchy -router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Response) => { +router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { role_id, guild_id } = req.params; const body = req.body as RoleModifySchema; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - - const role = new Role({ ...body, id: role_id, guild_id, permissions: String(perms.bitfield & (body.permissions || 0n)) }); + const role = new Role({ ...body, id: role_id, guild_id, permissions: String(req.permission!.bitfield & (body.permissions || 0n)) }); await Promise.all([ role.save(), @@ -121,7 +123,7 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res res.json(role); }); -router.patch("/", check(RolePositionUpdateSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const body = req.body as RolePositionUpdateSchema; diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index e9304e11..5179e761 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; -import { Guild, getPermission, Template } from "@fosscord/util"; +import { Guild, Template } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { generateCode } from "@fosscord/api"; const router: Router = Router(); @@ -24,7 +23,17 @@ const TemplateGuildProjection: (keyof Guild)[] = [ "icon" ]; -router.get("/", async (req: Request, res: Response) => { +export interface TemplateCreateSchema { + name: string; + description?: string; +} + +export interface TemplateModifySchema { + name: string; + description?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; var templates = await Template.find({ source_guild_id: guild_id }); @@ -32,12 +41,9 @@ router.get("/", async (req: Request, res: Response) => { return res.json(templates); }); -router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {}); if (exists) throw new HTTPError("Template already exists", 400); @@ -54,44 +60,31 @@ router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response res.json(template); }); -router.delete("/:code", async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const { code } = req.params; - - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); +router.delete("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { + const { code, guild_id } = req.params; const template = await Template.delete({ - code + code, + source_guild_id: guild_id }); res.json(template); }); -router.put("/:code", async (req: Request, res: Response) => { - // synchronizes the template +router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { code, guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const template = await new Template({ code, serialized_source_guild: guild }).save(); res.json(template); }); -router.patch("/:code", check(TemplateModifySchema), async (req: Request, res: Response) => { - // updates the template description - const { guild_id } = req.params; - const { code } = req.params; +router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { + const { code, guild_id } = req.params; const { name, description } = req.body; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - - const template = await new Template({ code, name: name, description: description }).save(); + const template = await new Template({ code, name: name, description: description, source_guild_id: guild_id }).save(); res.json(template); }); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index f1887cc0..9c0989cc 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,35 +1,37 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; -import { check, Length } from "@fosscord/api"; +import { check, Length, route } from "@fosscord/api"; const router = Router(); const InviteRegex = /\W/g; -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_GUILD"); - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, relations: ["vanity_url"] }); if (!guild.vanity_url) return res.json({ code: null }); return res.json({ code: guild.vanity_url_code, uses: guild.vanity_url.uses }); }); +export interface VanityUrlSchema { + /** + * @minLength 1 + * @maxLength 20 + */ + code?: string; +} + // TODO: check if guild is elgible for vanity url -router.patch("/", check({ code: new Length(String, 0, 20) }), async (req: Request, res: Response) => { +router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const code = req.body.code.replace(InviteRegex); + const body = req.body as VanityUrlSchema; + const code = body.code?.replace(InviteRegex, ""); await Invite.findOneOrFail({ code }); const guild = await Guild.findOneOrFail({ id: guild_id }); - const permission = await getPermission(req.user_id, guild_id); - permission.hasThrow("MANAGE_GUILD"); - const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); guild.vanity_url_code = code; diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 447e15c1..3d76938b 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -1,14 +1,59 @@ -import { check } from "@fosscord/api"; -import { VoiceStateUpdateSchema } from "../../../../../schema"; +import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; +import { check, route } from "@fosscord/api"; import { Request, Response, Router } from "express"; -import { updateVoiceState } from "@fosscord/api"; const router = Router(); +//TODO need more testing when community guild and voice stage channel are working -router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { +export interface VoiceStateUpdateSchema { + channel_id: string; + guild_id?: string; + suppress?: boolean; + request_to_speak_timestamp?: Date; + self_mute?: boolean; + self_deaf?: boolean; + self_video?: boolean; +} + +router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; - const { guild_id, user_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id, user_id); + var { guild_id, user_id } = req.params; + if (user_id === "@me") user_id = req.user_id; + + const perms = await getPermission(req.user_id, guild_id, body.channel_id); + + /* + From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state + You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself. + You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak. + */ + if (body.suppress && user_id !== req.user_id) { + perms.hasThrow("MUTE_MEMBERS"); + } + if (!body.suppress) body.request_to_speak_timestamp = new Date(); + if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); + + const voice_state = await VoiceState.findOne({ + guild_id, + channel_id: body.channel_id, + user_id + }); + if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; + + voice_state.assign(body); + const channel = await Channel.findOneOrFail({ guild_id, id: body.channel_id }); + if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { + throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; + } + + await Promise.all([ + voice_state.save(), + emitEvent({ + event: "VOICE_STATE_UPDATE", + data: voice_state, + guild_id + } as VoiceStateUpdateEvent) + ]); return res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts b/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts deleted file mode 100644 index b637ff66..00000000 --- a/api/src/routes/guilds/#guild_id/voice-states/@me/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { check } from "@fosscord/api"; -import { VoiceStateUpdateSchema } from "../../../../../schema"; -import { Request, Response, Router } from "express"; -import { updateVoiceState } from "@fosscord/api"; - -const router = Router(); - -router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => { - const body = req.body as VoiceStateUpdateSchema; - const { guild_id } = req.params; - await updateVoiceState(body, guild_id, req.user_id); - return res.sendStatus(204); -}); - -export default router; diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts index 7ca49b4e..7141f17e 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts @@ -1,31 +1,36 @@ import { Request, Response, Router } from "express"; import { Guild, getPermission, Snowflake, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { check } from "@fosscord/api"; -import { GuildUpdateWelcomeScreenSchema } from "../../../schema/Guild"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface GuildUpdateWelcomeScreenSchema { + welcome_channels?: { + channel_id: string; + description: string; + emoji_id?: string; + emoji_name: string; + }[]; + enabled?: boolean; + description?: string; +} + +router.get("/", route({}), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const guild = await Guild.findOneOrFail({ id: guild_id }); - await Member.IsInGuildOrFail(req.user_id, guild_id); res.json(guild.welcome_screen); }); -router.patch("/", check(GuildUpdateWelcomeScreenSchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const body = req.body as GuildUpdateWelcomeScreenSchema; const guild = await Guild.findOneOrFail({ id: guild_id }); - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400); if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid if (body.description) guild.welcome_screen.description = body.description; diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts index f871fac8..c31519fa 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/api/src/routes/guilds/#guild_id/widget.json.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { random } from "@fosscord/api"; +import { random, route } from "@fosscord/api"; const router: Router = Router(); @@ -14,7 +14,7 @@ const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget // TODO: Cache the response for a guild for 5 minutes regardless of response -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); diff --git a/api/src/routes/guilds/#guild_id/widget.png.ts b/api/src/routes/guilds/#guild_id/widget.png.ts index 89b31153..4c82b740 100644 --- a/api/src/routes/guilds/#guild_id/widget.png.ts +++ b/api/src/routes/guilds/#guild_id/widget.png.ts @@ -1,6 +1,7 @@ import { Request, Response, Router } from "express"; import { Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; +import { route } from "@fosscord/api"; import fs from "fs"; import path from "path"; @@ -10,7 +11,7 @@ const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget-image // TODO: Cache the response -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts index d9ce817e..c8caae14 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/api/src/routes/guilds/#guild_id/widget.ts @@ -1,31 +1,29 @@ import { Request, Response, Router } from "express"; import { getPermission, Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { WidgetModifySchema } from "../../../schema/Widget"; +import { check, route } from "@fosscord/api"; + +export interface WidgetModifySchema { + enabled: boolean; // whether the widget is enabled + channel_id: string; // the widget channel id +} const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget-settings -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - const guild = await Guild.findOneOrFail({ id: guild_id }); return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null }); }); // https://discord.com/developers/docs/resources/guild#modify-guild-widget -router.patch("/", check(WidgetModifySchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const body = req.body as WidgetModifySchema; const { guild_id } = req.params; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_GUILD"); - await Guild.update({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id }); // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index a1b199e7..ba951f96 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,15 +1,28 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check } from "@fosscord/api"; -import { GuildCreateSchema } from "../../schema/Guild"; +import { check, route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; +import { ChannelModifySchema } from "../channels/#channel_id"; const router: Router = Router(); +export interface GuildCreateSchema { + /** + * @maxLength 100 + */ + name: string; + region?: string; + icon?: string; + channels?: ChannelModifySchema[]; + guild_template_code?: string; + system_channel_id?: string; + rules_channel_id?: string; +} + //TODO: create default channel -router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "GuildCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as GuildCreateSchema; const { maxGuilds } = Config.get().limits.user; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index 1d0f2716..d7a42044 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -2,11 +2,15 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { GuildTemplateCreateSchema } from "../../../schema/Guild"; -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; -router.get("/:code", async (req: Request, res: Response) => { +export interface GuildTemplateCreateSchema { + name: string; + avatar?: string; +} + +router.get("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const template = await Template.findOneOrFail({ code: code }); @@ -14,7 +18,7 @@ router.get("/:code", async (req: Request, res: Response) => { res.json(template); }); -router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res: Response) => { +router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => { const { code } = req.params; const body = req.body as GuildTemplateCreateSchema; diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index b8c24c1f..6e77a853 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,9 +1,10 @@ import { Router, Request, Response } from "express"; import { getPermission, Guild, Invite, Member, PublicInviteRelation } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { HTTPError } from "lambert-server"; const router: Router = Router(); -router.get("/:code", async (req: Request, res: Response) => { +router.get("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const invite = await Invite.findOneOrFail({ where: { code }, relations: PublicInviteRelation }); @@ -11,7 +12,7 @@ router.get("/:code", async (req: Request, res: Response) => { res.status(200).send(invite); }); -router.post("/:code", async (req: Request, res: Response) => { +router.post("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const invite = await Invite.findOneOrFail({ code }); @@ -23,7 +24,8 @@ router.post("/:code", async (req: Request, res: Response) => { res.status(200).send(invite); }); -router.delete("/:code", async (req: Request, res: Response) => { +// * cant use permission of route() function because path doesn't have guild_id/channel_id +router.delete("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; const invite = await Invite.findOneOrFail({ code }); const { guild_id, channel_id } = invite; diff --git a/api/src/routes/ping.ts b/api/src/routes/ping.ts index 38daf81e..5cdea705 100644 --- a/api/src/routes/ping.ts +++ b/api/src/routes/ping.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { res.send("pong"); }); diff --git a/api/src/routes/science.ts b/api/src/routes/science.ts index b16ef783..8556a3ad 100644 --- a/api/src/routes/science.ts +++ b/api/src/routes/science.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.post("/", (req: Request, res: Response) => { +router.post("/", route({}), (req: Request, res: Response) => { // TODO: res.sendStatus(204); }); diff --git a/api/src/routes/users/#id/index.ts b/api/src/routes/users/#id/index.ts index 07956360..bdb1060f 100644 --- a/api/src/routes/users/#id/index.ts +++ b/api/src/routes/users/#id/index.ts @@ -1,9 +1,10 @@ import { Router, Request, Response } from "express"; import { User } from "@fosscord/util"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { id } = req.params; res.json(await User.getPublicUser(id)); diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 0f43a82f..d60c4f86 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -1,9 +1,17 @@ import { Router, Request, Response } from "express"; import { PublicConnectedAccount, PublicUser, User, UserPublic } from "@fosscord/util"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +export interface UserProfileResponse { + user: UserPublic; + connected_accounts: PublicConnectedAccount; + premium_guild_since?: Date; + premium_since?: Date; +} + +router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req: Request, res: Response) => { if (req.params.id === "@me") req.params.id = req.user_id; const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); @@ -25,11 +33,4 @@ router.get("/", async (req: Request, res: Response) => { }); }); -export interface UserProfileResponse { - user: UserPublic; - connected_accounts: PublicConnectedAccount; - premium_guild_since?: Date; - premium_since?: Date; -} - export default router; diff --git a/api/src/routes/users/@me/affinities/guilds.ts b/api/src/routes/users/@me/affinities/guilds.ts index fa6be0e7..8d744744 100644 --- a/api/src/routes/users/@me/affinities/guilds.ts +++ b/api/src/routes/users/@me/affinities/guilds.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { // TODO: res.status(200).send({ guild_affinities: [] }); }); diff --git a/api/src/routes/users/@me/affinities/user.ts b/api/src/routes/users/@me/affinities/user.ts index 0790a8a4..6d4e4991 100644 --- a/api/src/routes/users/@me/affinities/user.ts +++ b/api/src/routes/users/@me/affinities/user.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { // TODO: res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); }); diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 77fc8296..5515a217 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,19 +1,23 @@ import { Router, Request, Response } from "express"; import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent, Recipient } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { DmChannelCreateSchema } from "../../../schema/Channel"; -import { check } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { In } from "typeorm"; +export interface DmChannelCreateSchema { + name?: string; + recipients: string[]; +} + const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel"] }); res.json(recipients.map((x) => x.channel)); }); -router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Response) => { +router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); diff --git a/api/src/routes/users/@me/delete.ts b/api/src/routes/users/@me/delete.ts index e5fda948..39ceefd9 100644 --- a/api/src/routes/users/@me/delete.ts +++ b/api/src/routes/users/@me/delete.ts @@ -1,10 +1,12 @@ import { Router, Request, Response } from "express"; import { Guild, Member, User } from "@fosscord/util"; +import { route } from "@fosscord/api"; import bcrypt from "bcrypt"; import { HTTPError } from "lambert-server"; + const router = Router(); -router.post("/", async (req: Request, res: Response) => { +router.post("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ id: req.user_id }); //User object let correctpass = true; diff --git a/api/src/routes/users/@me/devices.ts b/api/src/routes/users/@me/devices.ts index b16ef783..8556a3ad 100644 --- a/api/src/routes/users/@me/devices.ts +++ b/api/src/routes/users/@me/devices.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.post("/", (req: Request, res: Response) => { +router.post("/", route({}), (req: Request, res: Response) => { // TODO: res.sendStatus(204); }); diff --git a/api/src/routes/users/@me/disable.ts b/api/src/routes/users/@me/disable.ts index 7b8a130c..259ced96 100644 --- a/api/src/routes/users/@me/disable.ts +++ b/api/src/routes/users/@me/disable.ts @@ -1,10 +1,11 @@ import { User } from "@fosscord/util"; import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; import bcrypt from "bcrypt"; const router = Router(); -router.post("/", async (req: Request, res: Response) => { +router.post("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ id: req.user_id }); //User object let correctpass = true; diff --git a/api/src/routes/users/@me/guilds.ts b/api/src/routes/users/@me/guilds.ts index fb88281b..4ba03cec 100644 --- a/api/src/routes/users/@me/guilds.ts +++ b/api/src/routes/users/@me/guilds.ts @@ -1,18 +1,18 @@ import { Router, Request, Response } from "express"; import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { In } from "typeorm"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } }); res.json(members.map((x) => x.guild)); }); // user send to leave a certain guild -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", route({}), async (req: Request, res: Response) => { const guild_id = req.params.id; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 451a657c..68723374 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,16 +1,33 @@ import { Router, Request, Response } from "express"; import { User, PrivateUserProjection } from "@fosscord/util"; -import { UserModifySchema } from "../../../schema/User"; -import { check } from "@fosscord/api"; +import { check, route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; const router: Router = Router(); +export interface UserModifySchema { + /** + * @minLength 1 + * @maxLength 100 + */ + username?: string; + avatar?: string | null; + /** + * @maxLength 1024 + */ + bio?: string; + accent_color?: number | null; + banner?: string | null; + password?: string; + new_password?: string; + code?: string; +} + router.get("/", async (req: Request, res: Response) => { - res.json(await User.getPublicUser(req.user_id, { select: PrivateUserProjection })); + res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } })); }); -router.patch("/", check(UserModifySchema), async (req: Request, res: Response) => { +router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: Response) => { const body = req.body as UserModifySchema; if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); diff --git a/api/src/routes/users/@me/library.ts b/api/src/routes/users/@me/library.ts index d771cb5e..7ac13bae 100644 --- a/api/src/routes/users/@me/library.ts +++ b/api/src/routes/users/@me/library.ts @@ -1,8 +1,9 @@ import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; const router = Router(); -router.get("/", (req: Request, res: Response) => { +router.get("/", route({}), (req: Request, res: Response) => { // TODO: res.status(200).send([]); }); diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 67ca2f35..cc264f3f 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -11,19 +11,95 @@ import { import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { DiscordApiErrors } from "@fosscord/util"; - -import { check, Length } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router = Router(); const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships"] }); return res.json(user.relationships); }); +export interface RelationshipPutSchema { + type: RelationshipType; +} + +router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }), + req.body.type + ); +}); + +export interface RelationshipPostSchema { + discriminator: string; + username: string; +} + +router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ + relations: ["relationships"], + select: userProjection, + where: req.body as { discriminator: string; username: string } + }), + req.body.type + ); +}); + +router.delete("/:id", route({}), async (req: Request, res: Response) => { + const { id } = req.params; + if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); + + const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); + const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); + + const relationship = user.relationships.find((x) => x.id === id); + const friendRequest = friend.relationships.find((x) => x.id === req.user_id); + + if (relationship?.type === RelationshipType.blocked) { + // unblock user + user.relationships.remove(relationship); + + await Promise.all([ + user.save(), + emitEvent({ event: "RELATIONSHIP_REMOVE", user_id: req.user_id, data: relationship } as RelationshipRemoveEvent) + ]); + return res.sendStatus(204); + } + if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); + if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); + + user.relationships.remove(relationship); + friend.relationships.remove(friendRequest); + + await Promise.all([ + user.save(), + friend.save(), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: relationship, + user_id: req.user_id + } as RelationshipRemoveEvent), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: friendRequest, + user_id: id + } as RelationshipRemoveEvent) + ]); + + return res.sendStatus(204); +}); + +export default router; + async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) { const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); @@ -114,71 +190,3 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ return res.sendStatus(204); } - -router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }), - req.body.type - ); -}); - -router.post("/", check({ discriminator: String, username: String }), async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ - relations: ["relationships"], - select: userProjection, - where: req.body as { discriminator: string; username: string } - }), - req.body.type - ); -}); - -router.delete("/:id", async (req: Request, res: Response) => { - const { id } = req.params; - if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); - - const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); - const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); - - const relationship = user.relationships.find((x) => x.id === id); - const friendRequest = friend.relationships.find((x) => x.id === req.user_id); - - if (relationship?.type === RelationshipType.blocked) { - // unblock user - user.relationships.remove(relationship); - - await Promise.all([ - user.save(), - emitEvent({ event: "RELATIONSHIP_REMOVE", user_id: req.user_id, data: relationship } as RelationshipRemoveEvent) - ]); - return res.sendStatus(204); - } - if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); - if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - - user.relationships.remove(relationship); - friend.relationships.remove(friendRequest); - - await Promise.all([ - user.save(), - friend.save(), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: relationship, - user_id: req.user_id - } as RelationshipRemoveEvent), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: friendRequest, - user_id: id - } as RelationshipRemoveEvent) - ]); - - return res.sendStatus(204); -}); - -export default router; diff --git a/api/src/routes/users/@me/settings.ts b/api/src/routes/users/@me/settings.ts index e7db85ed..9d7a2545 100644 --- a/api/src/routes/users/@me/settings.ts +++ b/api/src/routes/users/@me/settings.ts @@ -1,11 +1,12 @@ import { Router, Response, Request } from "express"; import { User, UserSettings } from "@fosscord/util"; -import { check } from "@fosscord/api"; -import { UserSettingsSchema } from "../../../schema/User"; +import { route } from "@fosscord/api"; const router = Router(); -router.patch("/", check(UserSettingsSchema), async (req: Request, res: Response) => { +export interface UserSettingsSchema extends UserSettings {} + +router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => { const body = req.body as UserSettings; // only users can update user settings diff --git a/api/src/routes/voice/regions.ts b/api/src/routes/voice/regions.ts index da1aaade..4de304ee 100644 --- a/api/src/routes/voice/regions.ts +++ b/api/src/routes/voice/regions.ts @@ -1,10 +1,10 @@ import { Router, Request, Response } from "express"; -import { getIpAdress } from "@fosscord/api"; +import { getIpAdress, route } from "@fosscord/api"; import { getVoiceRegions } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true? }); diff --git a/api/src/schema/Ban.ts b/api/src/schema/Ban.ts deleted file mode 100644 index 947a60ea..00000000 --- a/api/src/schema/Ban.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const BanCreateSchema = { - $delete_message_days: String, - $reason: String, -}; - -export interface BanCreateSchema { - delete_message_days?: string; - reason?: string; -} diff --git a/api/src/schema/Channel.ts b/api/src/schema/Channel.ts deleted file mode 100644 index cfbc7205..00000000 --- a/api/src/schema/Channel.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { ChannelType } from "@fosscord/util"; -import { Length } from "../util/instanceOf"; - -export const ChannelModifySchema = { - name: new Length(String, 2, 100), - type: new Length(Number, 0, 13), - $topic: new Length(String, 0, 1024), - $bitrate: Number, - $user_limit: Number, - $rate_limit_per_user: new Length(Number, 0, 21600), - $position: Number, - $permission_overwrites: [ - { - id: String, - type: new Length(Number, 0, 1), // either 0 (role) or 1 (member) - allow: BigInt, - deny: BigInt - } - ], - $parent_id: String, - $rtc_region: String, - $default_auto_archive_duration: Number, - $id: String, // kept for backwards compatibility does nothing (need for guild create) - $nsfw: Boolean -}; - -export const DmChannelCreateSchema = { - $name: String, - recipients: new Length([String], 1, 10) -}; - -export interface DmChannelCreateSchema { - name?: string; - recipients: string[]; -} - -export interface ChannelModifySchema { - name: string; - type: number; - topic?: string; - bitrate?: number; - user_limit?: number; - rate_limit_per_user?: number; - position?: number; - permission_overwrites?: { - id: string; - type: number; - allow: bigint; - deny: bigint; - }[]; - parent_id?: string; - id?: string; // is not used (only for guild create) - nsfw?: boolean; - rtc_region?: string; - default_auto_archive_duration?: number; -} - -export const ChannelGuildPositionUpdateSchema = [ - { - id: String, - $position: Number - } -]; - -export type ChannelGuildPositionUpdateSchema = { - id: string; - position?: number; -}[]; diff --git a/api/src/schema/Emoji.ts b/api/src/schema/Emoji.ts deleted file mode 100644 index 0406919c..00000000 --- a/api/src/schema/Emoji.ts +++ /dev/null @@ -1,13 +0,0 @@ -// https://discord.com/developers/docs/resources/emoji - -export const EmojiCreateSchema = { - name: String, //name of the emoji - image: String, // image data the 128x128 emoji image uri - $roles: Array //roles allowed to use this emoji -}; - -export interface EmojiCreateSchema { - name: string; // name of the emoji - image: string; // image data the 128x128 emoji image uri - roles?: string[]; //roles allowed to use this emoji -} diff --git a/api/src/schema/Guild.ts b/api/src/schema/Guild.ts deleted file mode 100644 index 29c78ab0..00000000 --- a/api/src/schema/Guild.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Channel } from "@fosscord/util"; -import { Length } from "../util/instanceOf"; -import { ChannelModifySchema } from "./Channel"; - -export const GuildCreateSchema = { - name: new Length(String, 2, 100), - $region: String, // auto complete voice region of the user - $icon: String, - $channels: [ChannelModifySchema], - $guild_template_code: String, - $system_channel_id: String, - $rules_channel_id: String -}; - -export interface GuildCreateSchema { - name: string; - region?: string; - icon?: string; - channels?: ChannelModifySchema[]; - guild_template_code?: string; - system_channel_id?: string; - rules_channel_id?: string; -} - -export const GuildUpdateSchema = { - ...GuildCreateSchema, - name: undefined, - $name: new Length(String, 2, 100), - $banner: String, - $splash: String, - $description: String, - $features: [String], - $icon: String, - $verification_level: Number, - $default_message_notifications: Number, - $system_channel_flags: Number, - $system_channel_id: String, - $explicit_content_filter: Number, - $public_updates_channel_id: String, - $afk_timeout: Number, - $afk_channel_id: String, - $preferred_locale: String -}; -// @ts-ignore -delete GuildUpdateSchema.$channels; - -export interface GuildUpdateSchema extends Omit { - banner?: string; - splash?: string; - description?: string; - features?: string[]; - verification_level?: number; - default_message_notifications?: number; - system_channel_flags?: number; - explicit_content_filter?: number; - public_updates_channel_id?: string; - afk_timeout?: number; - afk_channel_id?: string; - preferred_locale?: string; -} - -export const GuildTemplateCreateSchema = { - name: String, - $avatar: String -}; - -export interface GuildTemplateCreateSchema { - name: string; - avatar?: string; -} - -export const GuildUpdateWelcomeScreenSchema = { - $welcome_channels: [ - { - channel_id: String, - description: String, - $emoji_id: String, - emoji_name: String - } - ], - $enabled: Boolean, - $description: new Length(String, 0, 140) -}; - -export interface GuildUpdateWelcomeScreenSchema { - welcome_channels?: { - channel_id: string; - description: string; - emoji_id?: string; - emoji_name: string; - }[]; - enabled?: boolean; - description?: string; -} - -export const VoiceStateUpdateSchema = { - channel_id: String, // Snowflake - $suppress: Boolean, - $request_to_speak_timestamp: String // ISO8601 timestamp -}; - -export interface VoiceStateUpdateSchema { - channel_id: string; // Snowflake - suppress?: boolean; - request_to_speak_timestamp?: string // ISO8601 timestamp -} diff --git a/api/src/schema/Invite.ts b/api/src/schema/Invite.ts deleted file mode 100644 index da6192bc..00000000 --- a/api/src/schema/Invite.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const InviteCreateSchema = { - $target_user_id: String, - $target_type: String, - $validate: String, //? wtf is this - $max_age: Number, - $max_uses: Number, - $temporary: Boolean, - $unique: Boolean, - $target_user: String, - $target_user_type: Number -}; -export interface InviteCreateSchema { - target_user_id?: string; - target_type?: string; - validate?: string; //? wtf is this - max_age?: number; - max_uses?: number; - temporary?: boolean; - unique?: boolean; - target_user?: string; - target_user_type?: number; -} diff --git a/api/src/schema/Member.ts b/api/src/schema/Member.ts deleted file mode 100644 index 607d0a06..00000000 --- a/api/src/schema/Member.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const MemberCreateSchema = { - id: String, - nick: String, - guild_id: String, - joined_at: Date -}; - -export interface MemberCreateSchema { - id: string; - nick: string; - guild_id: string; - joined_at: Date; -} - -export const MemberNickChangeSchema = { - nick: String -}; - -export interface MemberNickChangeSchema { - nick: string; -} - -export const MemberChangeSchema = { - $roles: [String] -}; - -export interface MemberChangeSchema { - roles?: string[]; -} diff --git a/api/src/schema/Message.ts b/api/src/schema/Message.ts deleted file mode 100644 index d39f685a..00000000 --- a/api/src/schema/Message.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Embed } from "@fosscord/util"; -import { Length } from "../util/instanceOf"; - -export const EmbedImage = { - $url: String, - $width: Number, - $height: Number -}; - -const embed = { - $title: new Length(String, 0, 256), //title of embed - $type: String, // type of embed (always "rich" for webhook embeds) - $description: new Length(String, 0, 2048), // description of embed - $url: String, // url of embed - $timestamp: String, // ISO8601 timestamp - $color: Number, // color code of the embed - $footer: { - text: new Length(String, 0, 2048), - 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: new Length(String, 0, 256), - url: String, - icon_url: String, - proxy_icon_url: String - }, // author object author information - $fields: new Length( - [ - { - name: new Length(String, 0, 256), - value: new Length(String, 0, 1024), - $inline: Boolean - } - ], - 0, - 25 - ) -}; - -export const MessageCreateSchema = { - $content: new Length(String, 0, 2000), - $nonce: String, - $tts: Boolean, - $flags: String, - $embed: embed, - // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object) - // $embeds: [embed], - $allowed_mentions: { - $parse: [String], - $roles: [String], - $users: [String], - $replied_user: Boolean - }, - $message_reference: { - message_id: String, - channel_id: String, - $guild_id: String, - $fail_if_not_exists: Boolean - }, - $payload_json: String, - $file: Object -}; - -export interface MessageCreateSchema { - content?: string; - nonce?: string; - tts?: boolean; - flags?: string; - embed?: Embed & { timestamp?: string }; - allowed_mentions?: { - parse?: string[]; - roles?: string[]; - users?: string[]; - replied_user?: boolean; - }; - message_reference?: { - message_id: string; - channel_id: string; - guild_id?: string; - fail_if_not_exists?: boolean; - }; - payload_json?: string; - file?: any; -} diff --git a/api/src/schema/Roles.ts b/api/src/schema/Roles.ts deleted file mode 100644 index e1a34ae8..00000000 --- a/api/src/schema/Roles.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const RoleModifySchema = { - $name: String, - $permissions: BigInt, - $color: Number, - $hoist: Boolean, // whether the role should be displayed separately in the sidebar - $mentionable: Boolean, // whether the role should be mentionable - $position: Number -}; - -export interface RoleModifySchema { - name?: string; - permissions?: bigint; - color?: number; - hoist?: boolean; // whether the role should be displayed separately in the sidebar - mentionable?: boolean; // whether the role should be mentionable - position?: number; -} - -export const RolePositionUpdateSchema = [ - { - id: String, - position: Number - } -]; - -export type RolePositionUpdateSchema = { - id: string; - position: number; -}[]; diff --git a/api/src/schema/Template.ts b/api/src/schema/Template.ts deleted file mode 100644 index 88e36c53..00000000 --- a/api/src/schema/Template.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const TemplateCreateSchema = { - name: String, - $description: String, -}; - -export interface TemplateCreateSchema { - name: string; - description?: string; -} - -export const TemplateModifySchema = { - name: String, - $description: String, -}; - -export interface TemplateModifySchema { - name: string; - description?: string; -} diff --git a/api/src/schema/User.ts b/api/src/schema/User.ts deleted file mode 100644 index 0d094b9e..00000000 --- a/api/src/schema/User.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { UserSettings } from "../../../util/dist"; -import { Length } from "../util/instanceOf"; - -export const UserModifySchema = { - $username: new Length(String, 2, 32), - $avatar: String, - $bio: new Length(String, 0, 190), - $accent_color: Number, - $banner: String, - $password: String, - $new_password: String, - $code: String // 2fa code -}; - -export interface UserModifySchema { - username?: string; - avatar?: string | null; - bio?: string; - accent_color?: number | null; - banner?: string | null; - password?: string; - new_password?: string; - code?: string; -} - -export const UserSettingsSchema = { - $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, - $guild_folders: [ - { - color: Number, - guild_ids: [String], - id: Number, - name: String - } - ], - $guild_positions: [String], - $inline_attachment_media: Boolean, - $inline_embed_media: Boolean, - $locale: String, - $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, - $timezone_offset: Number -}; - -export interface UserSettingsSchema extends UserSettings {} diff --git a/api/src/schema/Widget.ts b/api/src/schema/Widget.ts deleted file mode 100644 index d37a38de..00000000 --- a/api/src/schema/Widget.ts +++ /dev/null @@ -1,10 +0,0 @@ -// https://discord.com/developers/docs/resources/guild#guild-widget-object -export const WidgetModifySchema = { - enabled: Boolean, // whether the widget is enabled - channel_id: String // the widget channel id -}; - -export interface WidgetModifySchema { - enabled: boolean; // whether the widget is enabled - channel_id: string; // the widget channel id -} diff --git a/api/src/schema/index.ts b/api/src/schema/index.ts deleted file mode 100644 index b5f38a2f..00000000 --- a/api/src/schema/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from "./Ban"; -export * from "./Channel"; -export * from "./Emoji"; -export * from "./Guild"; -export * from "./Invite"; -export * from "./Member"; -export * from "./Message"; -export * from "./Roles"; -export * from "./Template"; -export * from "./User"; -export * from "./Widget"; diff --git a/api/src/util/Message.ts b/api/src/util/Message.ts index fea553bc..f8230124 100644 --- a/api/src/util/Message.ts +++ b/api/src/util/Message.ts @@ -22,7 +22,7 @@ import { import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; import cheerio from "cheerio"; -import { MessageCreateSchema } from "../schema/Message"; +import { MessageCreateSchema } from "../routes/channels/#channel_id/messages"; // TODO: check webhook, application, system author diff --git a/api/src/util/Voice.ts b/api/src/util/Voice.ts index 087bdfa8..f06b1aaa 100644 --- a/api/src/util/Voice.ts +++ b/api/src/util/Voice.ts @@ -1,32 +1,32 @@ -import {Config} from "@fosscord/util"; -import {distanceBetweenLocations, IPAnalysis} from "./ipAddress"; +import { Config } from "@fosscord/util"; +import { distanceBetweenLocations, IPAnalysis } from "./ipAddress"; export async function getVoiceRegions(ipAddress: string, vip: boolean) { - const regions = Config.get().regions; - const availableRegions = regions.available.filter(ar => vip ? true : !ar.vip); - let optimalId = regions.default + const regions = Config.get().regions; + const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip)); + let optimalId = regions.default; - if(!regions.useDefaultAsOptimal) { - const clientIpAnalysis = await IPAnalysis(ipAddress) + if (!regions.useDefaultAsOptimal) { + const clientIpAnalysis = await IPAnalysis(ipAddress); - let min = Number.POSITIVE_INFINITY + let min = Number.POSITIVE_INFINITY; - for (let ar of availableRegions) { - //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call - const dist = distanceBetweenLocations(clientIpAnalysis, ar.location || (await IPAnalysis(ar.endpoint))) + for (let ar of availableRegions) { + //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call + const dist = distanceBetweenLocations(clientIpAnalysis, ar.location || (await IPAnalysis(ar.endpoint))); - if(dist < min) { - min = dist - optimalId = ar.id - } - } - } + if (dist < min) { + min = dist; + optimalId = ar.id; + } + } + } - return availableRegions.map(ar => ({ - id: ar.id, - name: ar.name, - custom: ar.custom, - deprecated: ar.deprecated, - optimal: ar.id === optimalId - })) -} \ No newline at end of file + return availableRegions.map((ar) => ({ + id: ar.id, + name: ar.name, + custom: ar.custom, + deprecated: ar.deprecated, + optimal: ar.id === optimalId + })); +} diff --git a/api/src/util/VoiceState.ts b/api/src/util/VoiceState.ts deleted file mode 100644 index 07022ec9..00000000 --- a/api/src/util/VoiceState.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; -import { VoiceStateUpdateSchema } from "../schema"; - - -//TODO need more testing when community guild and voice stage channel are working -export async function updateVoiceState(vsuSchema: VoiceStateUpdateSchema, guildId: string, userId: string, targetUserId?: string) { - const perms = await getPermission(userId, guildId, vsuSchema.channel_id); - - /* - From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state - You must have the MUTE_MEMBERS permission to unsuppress yourself. You can always suppress yourself. - You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak. - */ - if (targetUserId !== undefined || (vsuSchema.suppress !== undefined && !vsuSchema.suppress)) { - perms.hasThrow("MUTE_MEMBERS"); - } - if (vsuSchema.request_to_speak_timestamp !== undefined && vsuSchema.request_to_speak_timestamp !== "") { - perms.hasThrow("REQUEST_TO_SPEAK") - } - - if (!targetUserId) { - targetUserId = userId; - } else { - if (vsuSchema.suppress !== undefined && vsuSchema.suppress) - vsuSchema.request_to_speak_timestamp = "" //Need to check if empty string is the right value - } - - //TODO assumed that empty string means clean, need to test if it's right - let voiceState - try { - voiceState = await VoiceState.findOneOrFail({ - guild_id: guildId, - channel_id: vsuSchema.channel_id, - user_id: targetUserId - }); - } catch (error) { - throw DiscordApiErrors.UNKNOWN_VOICE_STATE; - } - - voiceState.assign(vsuSchema); - const channel = await Channel.findOneOrFail({ guild_id: guildId, id: vsuSchema.channel_id }) - if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { - throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; - } - - await Promise.all([ - voiceState.save(), - emitEvent({ - event: "VOICE_STATE_UPDATE", - data: voiceState, - guild_id: guildId - } as VoiceStateUpdateEvent)]); - return; -} \ No newline at end of file diff --git a/api/src/util/index.ts b/api/src/util/index.ts index 43481289..c98784a4 100644 --- a/api/src/util/index.ts +++ b/api/src/util/index.ts @@ -8,4 +8,3 @@ export * from "./RandomInviteID"; export * from "./route"; export * from "./String"; export * from "./Voice"; -export * from "./VoiceState"; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 0302f3ec..f618a630 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -5,10 +5,20 @@ import path from "path"; import Ajv from "ajv"; import { AnyValidateFunction } from "ajv/dist/core"; import { FieldErrors } from ".."; +import addFormats from "ajv-formats"; const SchemaPath = path.join(__dirname, "..", "..", "assets", "schemas.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); -export const ajv = new Ajv({ allErrors: true, parseDate: true, allowDate: true, schemas, messages: true }); +export const ajv = new Ajv({ + allErrors: true, + parseDate: true, + allowDate: true, + schemas, + messages: true, + strict: true, + strictRequired: true +}); +addFormats(ajv); declare global { namespace Express { @@ -19,7 +29,7 @@ declare global { } export type RouteSchema = string; // typescript interface name -export type RouteResponse = { status: number; body?: RouteSchema; headers?: Record }; +export type RouteResponse = { status?: number; body?: RouteSchema; headers?: Record }; export interface RouteOptions { permission?: PermissionResolvable; From e32bb24fa84e5942168433b16f3b0d86aa455a99 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 00:00:11 +0200 Subject: [PATCH 15/90] :bug: fix invites: ajv doesn't treat null as undefined --- api/assets/schemas.json | 592 ++++++++---------- api/scripts/generate_body_schema.ts | 2 +- .../routes/channels/#channel_id/invites.ts | 4 +- api/src/util/route.ts | 1 - 4 files changed, 266 insertions(+), 333 deletions(-) diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 59ab4c04..ac7df859 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -177,7 +177,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -274,17 +282,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -499,7 +496,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -596,17 +601,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -718,10 +712,16 @@ "type": "string" }, "target_type": { - "type": "string" + "type": [ + "null", + "string" + ] }, "validate": { - "type": "string" + "type": [ + "null", + "string" + ] }, "max_age": { "type": "integer" @@ -774,7 +774,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -871,17 +879,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -1028,7 +1025,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -1125,17 +1130,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -1285,7 +1279,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -1382,17 +1384,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -1551,7 +1542,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -1648,17 +1647,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -1810,7 +1798,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -1907,17 +1903,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -2064,7 +2049,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -2161,17 +2154,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -2330,7 +2312,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -2427,17 +2417,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -2606,7 +2585,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -2703,17 +2690,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -2915,7 +2891,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -3012,17 +2996,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -3169,7 +3142,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -3266,17 +3247,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -3423,7 +3393,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -3520,17 +3498,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -3689,7 +3656,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -3786,17 +3761,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -3950,7 +3914,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -4047,17 +4019,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -4207,7 +4168,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -4304,17 +4273,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -4464,7 +4422,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -4561,17 +4527,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -4717,7 +4672,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -4814,17 +4777,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -4990,7 +4942,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -5087,17 +5047,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -5270,7 +5219,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -5367,17 +5324,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -5528,7 +5474,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -5625,17 +5579,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -5785,7 +5728,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -5882,17 +5833,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -6045,7 +5985,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -6142,17 +6090,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -6266,17 +6203,26 @@ "type": "string" }, "avatar": { - "type": "string" + "type": [ + "null", + "string" + ] }, "bio": { "maxLength": 1024, "type": "string" }, "accent_color": { - "type": "integer" + "type": [ + "null", + "integer" + ] }, "banner": { - "type": "string" + "type": [ + "null", + "string" + ] }, "password": { "type": "string" @@ -6320,7 +6266,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -6417,17 +6371,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -6574,7 +6517,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -6671,17 +6622,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -6832,7 +6772,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -6929,17 +6877,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { @@ -7273,7 +7210,15 @@ "type": "string" }, "type": { - "$ref": "#/definitions/EmbedType" + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" }, "description": { "type": "string" @@ -7370,17 +7315,6 @@ }, "additionalProperties": false }, - "EmbedType": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, "EmbedImage": { "type": "object", "properties": { diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts index c406cab8..316e5a69 100644 --- a/api/scripts/generate_body_schema.ts +++ b/api/scripts/generate_body_schema.ts @@ -15,7 +15,7 @@ const settings: TJS.PartialArgs = { defaultProps: false }; const compilerOptions: TJS.CompilerOptions = { - strictNullChecks: false + strictNullChecks: true }; const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"]; diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index 39263185..2edb4fc2 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -9,8 +9,8 @@ const router: Router = Router(); export interface InviteCreateSchema { target_user_id?: string; - target_type?: string; - validate?: string; //? wtf is this + target_type?: string | null; + validate?: string | null; // ? what is this max_age?: number; max_uses?: number; temporary?: boolean; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index f618a630..6cd8f622 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -55,7 +55,6 @@ export function route(opts: RouteOptions) { if (opts.permission) { const required = new Permissions(opts.permission); const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); - console.log(required.bitfield, permission.bitfield, permission.bitfield & required.bitfield); // bitfield comparison: check if user lacks certain permission if (!permission.has(required)) { From 6b21e107dd00b59da3bd53badfe46878cde81179 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 00:17:56 +0200 Subject: [PATCH 16/90] :bug: fix channel events + message send --- bundle/package.json | 3 ++- gateway/src/listener/listener.ts | 26 ++++++++++++++------------ util/src/entities/Channel.ts | 1 + 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/bundle/package.json b/bundle/package.json index 996ab30b..83f26116 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -11,7 +11,8 @@ "build:api": "cd ../api/ && npm run build", "build:cdn": "cd ../cdn/ && npm run build", "build:gateway": "cd ../gateway/ && npm run build", - "start": "npm run build && node -r ./tsconfig-paths-bootstrap.js dist/start.js", + "start": "npm run build && npm run start:bundle", + "start:bundle": "node -r ./tsconfig-paths-bootstrap.js dist/start.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index e5630a43..7152c74b 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -26,16 +26,16 @@ import { Recipient } from "@fosscord/util"; // TODO: use already queried guilds/channels of Identify and don't fetch them again export async function setupListener(this: WebSocket) { - const members = await Member.find({ id: this.user_id }); - const guild_ids = members.map((x) => x.guild_id); - const user = await User.findOneOrFail({ id: this.user_id }); + const members = await Member.find({ + where: { id: this.user_id }, + relations: ["guild", "guild.channels"], + }); + const guilds = members.map((x) => x.guild); const recipients = await Recipient.find({ where: { user_id: this.user_id }, relations: ["channel"], }); - const channels = await Channel.find({ guild_id: In(guild_ids) }); const dm_channels = recipients.map((x) => x.channel); - const guild_channels = channels.filter((x) => x.guild_id); const opts: { acknowledge: boolean; channel?: AMQChannel } = { acknowledge: true, @@ -54,18 +54,20 @@ export async function setupListener(this: WebSocket) { this.events[channel.id] = await listenEvent(channel.id, consumer, opts); } - for (const guild of guild_ids) { + for (const guild of guilds) { // contains guild and dm channels - getPermission(this.user_id, guild) + getPermission(this.user_id, guild.id) .then(async (x) => { - this.permissions[guild] = x; + this.permissions[guild.id] = x; this.listeners; - this.events[guild] = await listenEvent(guild, consumer, opts); + this.events[guild.id] = await listenEvent( + guild.id, + consumer, + opts + ); - for (const channel of guild_channels.filter( - (c) => c.guild_id === guild - )) { + for (const channel of guild.channels) { if ( x .overwriteChannel(channel.permission_overwrites) diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index fce85e3f..592b0b83 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -61,6 +61,7 @@ export class Channel extends BaseClass { @ManyToOne(() => Channel) parent?: Channel; + // only for group dms @Column({ nullable: true }) @RelationId((channel: Channel) => channel.owner) owner_id: string; From 4f1b926d097c68b6c73677b710c9731ddc307336 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 01:11:03 +0200 Subject: [PATCH 17/90] :bug: fix dm #321 --- .../channels/#channel_id/permissions.ts | 4 +- api/src/routes/users/@me/channels.ts | 16 +++---- api/tests/automatic.test.js | 2 + gateway/src/listener/listener.ts | 2 +- gateway/src/opcodes/Identify.ts | 43 +++++++++++++------ util/src/entities/Channel.ts | 12 +++--- util/src/util/Permissions.ts | 2 +- 7 files changed, 50 insertions(+), 31 deletions(-) diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 827e46f2..959ab8e0 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -35,7 +35,7 @@ router.put( allow: body.allow, deny: body.deny }; - channel.permission_overwrites.push(overwrite); + channel.permission_overwrites!.push(overwrite); } overwrite.allow = body.allow; overwrite.deny = body.deny; @@ -60,7 +60,7 @@ router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (re const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - channel.permission_overwrites = channel.permission_overwrites.filter((x) => x.id === overwrite_id); + channel.permission_overwrites = channel.permission_overwrites!.filter((x) => x.id === overwrite_id); await Promise.all([ channel.save(), diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 5515a217..da33f204 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -4,11 +4,6 @@ import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; import { In } from "typeorm"; -export interface DmChannelCreateSchema { - name?: string; - recipients: string[]; -} - const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { @@ -17,12 +12,17 @@ router.get("/", route({}), async (req: Request, res: Response) => { res.json(recipients.map((x) => x.channel)); }); +export interface DmChannelCreateSchema { + name?: string; + recipients: string[]; +} + router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); - const recipients = await User.find({ id: In(body.recipients) }); + const recipients = await User.find({ where: body.recipients.map((x) => ({ id: x })) }); if (recipients.length !== body.recipients.length) { throw new HTTPError("Recipient/s not found"); @@ -34,10 +34,10 @@ router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, const channel = await new Channel({ name, type, - owner_id: req.user_id, + // owner_id only for group dm channels created_at: new Date(), last_message_id: null, - recipients: [...body.recipients.map((x) => new Recipient({ id: x })), new Recipient({ id: req.user_id })] + recipients: [...body.recipients.map((x) => new Recipient({ user_id: x })), new Recipient({ user_id: req.user_id })] }).save(); await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent); diff --git a/api/tests/automatic.test.js b/api/tests/automatic.test.js index e69de29b..2d0a9fcb 100644 --- a/api/tests/automatic.test.js +++ b/api/tests/automatic.test.js @@ -0,0 +1,2 @@ +// TODO: check every route based on route() paramters: https://github.com/fosscord/fosscord-server/issues/308 +// TODO: check every route with different database engine diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index 7152c74b..ef3dd890 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -70,7 +70,7 @@ export async function setupListener(this: WebSocket) { for (const channel of guild.channels) { if ( x - .overwriteChannel(channel.permission_overwrites) + .overwriteChannel(channel.permission_overwrites!) .has("VIEW_CHANNEL") ) { this.events[channel.id] = await listenEvent( diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 04a6c84c..88c9b942 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -14,6 +14,8 @@ import { dbConnection, PublicMemberProjection, PublicMember, + ChannelType, + PublicUser, } from "@fosscord/util"; import { setupListener } from "../listener/listener"; import { IdentifySchema } from "../schema/Identify"; @@ -57,6 +59,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { return this.close(CLOSECODES.Invalid_shard); } } + var users: PublicUser[] = []; const members = await Member.find({ where: { id: this.user_id }, @@ -85,12 +88,36 @@ export async function onIdentify(this: WebSocket, data: Payload) { const recipients = await Recipient.find({ where: { user_id: this.user_id }, - relations: ["channel", "channel.recipients"], + relations: ["channel", "channel.recipients", "channel.recipients.user"], + // TODO: public user selection + }); + const channels = recipients.map((x) => { + // @ts-ignore + x.channel.recipients = x.channel.recipients?.map((x) => x.user); + // @ts-ignore + users = users.concat(x.channel.recipients); + if (x.channel.type === ChannelType.DM) { + x.channel.recipients = [ + // @ts-ignore + x.channel.recipients.find((x) => x.id !== this.user_id), + ]; + } + return x.channel; }); - const channels = recipients.map((x) => x.channel); const user = await User.findOneOrFail({ id: this.user_id }); if (!user) return this.close(CLOSECODES.Authentication_failed); + const public_user = { + username: user.username, + discriminator: user.discriminator, + id: user.id, + public_flags: user.public_flags, + avatar: user.avatar, + bot: user.bot, + bio: user.bio, + }; + users.push(public_user); + const session_id = genSessionId(); this.session_id = session_id; //Set the session of the WebSocket object const session = new Session({ @@ -108,16 +135,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { //We save the session and we delete it when the websocket is closed await session.save(); - const public_user = { - username: user.username, - discriminator: user.discriminator, - id: user.id, - public_flags: user.public_flags, - avatar: user.avatar, - bot: user.bot, - bio: user.bio, - }; - const privateUser = { avatar: user.avatar, mobile: user.mobile, @@ -180,7 +197,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { // @ts-ignore experiments: experiments, // TODO guild_join_requests: [], // TODO what is this? - users: [public_user].unique(), // TODO + users: users.unique(), // TODO merged_members: merged_members, // shard // TODO: only for bots sharding // application // TODO for applications diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index 592b0b83..fc954f63 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -28,8 +28,8 @@ export class Channel extends BaseClass { @Column() created_at: Date; - @Column() - name: string; + @Column({ nullable: true }) + name?: string; @Column({ type: "simple-enum", enum: ChannelType }) type: ChannelType; @@ -76,11 +76,11 @@ export class Channel extends BaseClass { @Column({ nullable: true }) default_auto_archive_duration?: number; - @Column() - position: number; + @Column({ nullable: true }) + position?: number; - @Column({ type: "simple-json" }) - permission_overwrites: ChannelPermissionOverwrite[]; + @Column({ type: "simple-json", nullable: true }) + permission_overwrites?: ChannelPermissionOverwrite[]; @Column({ nullable: true }) video_quality_mode?: number; diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts index 628a495d..9d87253a 100644 --- a/util/src/util/Permissions.ts +++ b/util/src/util/Permissions.ts @@ -242,7 +242,7 @@ export async function getPermission( }); } - let recipient_ids: any = channel?.recipients?.map((x) => x.id); + let recipient_ids: any = channel?.recipients?.map((x) => x.user_id); if (!recipient_ids?.length) recipient_ids = null; // TODO: remove guild.roles and convert recipient_ids to recipients From 9e3bc94e9de3ed0487f27658a90468ecddb6b926 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 12:22:41 +0200 Subject: [PATCH 18/90] :bug: fix relationship --- api/src/routes/users/@me/relationships.ts | 108 +++++++++++----------- gateway/src/opcodes/Identify.ts | 7 +- util/src/entities/Relationship.ts | 30 ++++-- util/src/interfaces/Event.ts | 14 ++- 4 files changed, 94 insertions(+), 65 deletions(-) diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index cc264f3f..58d2e481 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -31,7 +31,7 @@ router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request return await updateRelationship( req, res, - await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }), + await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships", "relationships.to"], select: userProjection }), req.body.type ); }); @@ -46,7 +46,7 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, req, res, await User.findOneOrFail({ - relations: ["relationships"], + relations: ["relationships", "relationships.to"], select: userProjection, where: req.body as { discriminator: string; username: string } }), @@ -61,37 +61,40 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); - const relationship = user.relationships.find((x) => x.id === id); - const friendRequest = friend.relationships.find((x) => x.id === req.user_id); + const relationship = user.relationships.find((x) => x.to_id === id); + const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); + if (!relationship) throw new HTTPError("You are not friends with the user", 404); if (relationship?.type === RelationshipType.blocked) { // unblock user - user.relationships.remove(relationship); await Promise.all([ - user.save(), - emitEvent({ event: "RELATIONSHIP_REMOVE", user_id: req.user_id, data: relationship } as RelationshipRemoveEvent) + Relationship.delete({ id: relationship.id }), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + user_id: req.user_id, + data: relationship.toPublicRelationship() + } as RelationshipRemoveEvent) ]); return res.sendStatus(204); } - if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); - if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - - user.relationships.remove(relationship); - friend.relationships.remove(friendRequest); + if (friendRequest && friendRequest.type !== RelationshipType.blocked) { + await Promise.all([ + Relationship.delete({ id: friendRequest.id }), + await emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: friendRequest.toPublicRelationship(), + user_id: id + } as RelationshipRemoveEvent) + ]); + } await Promise.all([ - user.save(), - friend.save(), + Relationship.delete({ id: relationship.id }), emitEvent({ event: "RELATIONSHIP_REMOVE", - data: relationship, + data: relationship.toPublicRelationship(), user_id: req.user_id - } as RelationshipRemoveEvent), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: friendRequest, - user_id: id } as RelationshipRemoveEvent) ]); @@ -104,44 +107,40 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await User.findOneOrFail({ id: req.user_id }, { relations: ["relationships"], select: userProjection }); + const user = await User.findOneOrFail( + { id: req.user_id }, + { relations: ["relationships", "relationships.to"], select: userProjection } + ); - var relationship = user.relationships.find((x) => x.id === id); - const friendRequest = friend.relationships.find((x) => x.id === req.user_id); + var relationship = user.relationships.find((x) => x.to_id === id); + const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); // TODO: you can add infinitely many blocked users (should this be prevented?) if (type === RelationshipType.blocked) { if (relationship) { if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user"); relationship.type = RelationshipType.blocked; + await relationship.save(); } else { - relationship = new Relationship({ id, type: RelationshipType.blocked }); - user.relationships.push(relationship); + relationship = await new Relationship({ to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save(); } if (friendRequest && friendRequest.type !== RelationshipType.blocked) { - friend.relationships.remove(friendRequest); await Promise.all([ - user.save(), + Relationship.delete({ id: friendRequest.id }), emitEvent({ event: "RELATIONSHIP_REMOVE", - data: friendRequest, + data: friendRequest.toPublicRelationship(), user_id: id } as RelationshipRemoveEvent) ]); } - await Promise.all([ - user.save(), - emitEvent({ - event: "RELATIONSHIP_ADD", - data: { - ...relationship, - user: { ...friend } - }, - user_id: req.user_id - } as RelationshipAddEvent) - ]); + await emitEvent({ + event: "RELATIONSHIP_ADD", + data: relationship.toPublicRelationship(), + user_id: req.user_id + } as RelationshipAddEvent); return res.sendStatus(204); } @@ -149,40 +148,43 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const { maxFriends } = Config.get().limits.user; if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); - var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, id: req.user_id }); - var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing, id }); + var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend }); + var outgoing_relationship = new Relationship({ + nickname: undefined, + type: RelationshipType.outgoing, + to: friend, + from: user + }); if (friendRequest) { if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); + if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); // accept friend request incoming_relationship = friendRequest; incoming_relationship.type = RelationshipType.friends; - outgoing_relationship.type = RelationshipType.friends; - } else friend.relationships.push(incoming_relationship); + } if (relationship) { if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request"); if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request"); if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); - } else user.relationships.push(outgoing_relationship); + outgoing_relationship = relationship; + outgoing_relationship.type = RelationshipType.friends; + } await Promise.all([ - user.save(), - friend.save(), + incoming_relationship.save(), + outgoing_relationship.save(), emitEvent({ event: "RELATIONSHIP_ADD", - data: { - ...outgoing_relationship, - user: { ...friend } - }, + data: outgoing_relationship.toPublicRelationship(), user_id: req.user_id } as RelationshipAddEvent), emitEvent({ event: "RELATIONSHIP_ADD", data: { - ...incoming_relationship, - should_notify: true, - user: { ...user } + ...incoming_relationship.toPublicRelationship(), + should_notify: true }, user_id: id } as RelationshipAddEvent) diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 88c9b942..9eb4cd32 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -104,7 +104,10 @@ export async function onIdentify(this: WebSocket, data: Payload) { } return x.channel; }); - const user = await User.findOneOrFail({ id: this.user_id }); + const user = await User.findOneOrFail({ + where: { id: this.user_id }, + relations: ["relationships", "relationships.to"], + }); if (!user) return this.close(CLOSECODES.Authentication_failed); const public_user = { @@ -171,7 +174,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { }), guild_experiments: [], // TODO geo_ordered_rtc_regions: [], // TODO - relationships: user.relationships, + relationships: user.relationships.map((x) => x.toPublicRelationship()), read_state: { // TODO entries: [], diff --git a/util/src/entities/Relationship.ts b/util/src/entities/Relationship.ts index 5935f5b6..61b3ac82 100644 --- a/util/src/entities/Relationship.ts +++ b/util/src/entities/Relationship.ts @@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { User } from "./User"; @@ -10,18 +10,36 @@ export enum RelationshipType { } @Entity("relationships") +@Index(["from_id", "to_id"], { unique: true }) export class Relationship extends BaseClass { - @Column({ nullable: true }) - @RelationId((relationship: Relationship) => relationship.user) - user_id: string; + @Column({}) + @RelationId((relationship: Relationship) => relationship.from) + from_id: string; - @JoinColumn({ name: "user_id" }) + @JoinColumn({ name: "from_id" }) @ManyToOne(() => User) - user: User; + from: User; + + @Column({}) + @RelationId((relationship: Relationship) => relationship.to) + to_id: string; + + @JoinColumn({ name: "to_id" }) + @ManyToOne(() => User) + to: User; @Column({ nullable: true }) nickname?: string; @Column({ type: "simple-enum", enum: RelationshipType }) type: RelationshipType; + + toPublicRelationship() { + return { + id: this.to?.id || this.to_id, + type: this.type, + nickname: this.nickname, + user: this.to?.toPublicUser(), + }; + } } diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts index 5c2a01b0..aff50300 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts @@ -10,7 +10,7 @@ import { VoiceState } from "../entities/VoiceState"; import { ApplicationCommand } from "../entities/Application"; import { Interaction } from "./Interaction"; import { ConnectedAccount } from "../entities/ConnectedAccount"; -import { Relationship } from "../entities/Relationship"; +import { Relationship, RelationshipType } from "../entities/Relationship"; import { Presence } from "./Presence"; export interface Event { @@ -28,6 +28,12 @@ export interface InvalidatedEvent extends Event { event: "INVALIDATED"; } +export interface PublicRelationship { + id: string; + user: PublicUser; + type: RelationshipType; +} + // ! END Custom Events that shouldn't get sent to the client but processed by the server export interface ReadyEventData { @@ -72,7 +78,7 @@ export interface ReadyEventData { guild_join_requests?: any[]; // ? what is this? this is new shard?: [number, number]; user_settings?: UserSettings; - relationships?: Relationship[]; // TODO + relationships?: PublicRelationship[]; // TODO read_state: { entries: any[]; // TODO partial: boolean; @@ -412,7 +418,7 @@ export interface MessageAckEvent extends Event { export interface RelationshipAddEvent extends Event { event: "RELATIONSHIP_ADD"; - data: Relationship & { + data: PublicRelationship & { should_notify?: boolean; user: PublicUser; }; @@ -420,7 +426,7 @@ export interface RelationshipAddEvent extends Event { export interface RelationshipRemoveEvent extends Event { event: "RELATIONSHIP_REMOVE"; - data: Omit; + data: Omit; } export type EventData = From 9b4457424df8258d7ded0bebe1b321f5e69b7b8e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 12:22:59 +0200 Subject: [PATCH 19/90] :bug: fix In() query --- api/src/routes/auth/register.ts | 2 +- api/src/routes/guilds/#guild_id/roles.ts | 2 +- util/src/entities/User.ts | 14 +++++++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index e0af1d6d..e70e01ed 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -137,7 +137,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re if (!register.allowMultipleAccounts) { // TODO: check if fingerprint was eligible generated - const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } }); + const exists = await User.findOne({ where: { fingerprints: fingerprint } }); if (exists) { throw FieldErrors({ diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 6b2902d9..5c549262 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -132,7 +132,7 @@ router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Reque await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); - const roles = await Role.find({ guild_id, id: In(body.map((x) => x.id)) }); + const roles = await Role.find({ where: body.map((x) => ({ id: x.id, guild_id })) }); await Promise.all( roles.map((x) => diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 9394f9e8..736704f8 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -106,7 +106,7 @@ export class User extends BaseClass { mfa_enabled: boolean; // if multi factor authentication is enabled @Column() - created_at: Date = new Date(); // registration date + created_at: Date; // registration date @Column() verified: boolean; // if the user is offically verified @@ -127,7 +127,7 @@ export class User extends BaseClass { public_flags: string; @JoinColumn({ name: "relationship_ids" }) - @OneToMany(() => Relationship, (relationship: Relationship) => relationship.user, { cascade: true }) + @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from) relationships: Relationship[]; @JoinColumn({ name: "connected_account_ids" }) @@ -146,6 +146,14 @@ export class User extends BaseClass { @Column({ type: "simple-json" }) settings: UserSettings; + toPublicUser() { + const user: any = {}; + PublicUserProjection.forEach((x) => { + user[x] = this[x]; + }); + return user as PublicUser; + } + static async getPublicUser(user_id: string, opts?: FindOneOptions) { const user = await User.findOne( { id: user_id }, @@ -261,7 +269,7 @@ export class UserFlags extends BitField { SYSTEM: BigInt(1) << BigInt(12), HAS_UNREAD_URGENT_MESSAGES: BigInt(1) << BigInt(13), BUGHUNTER_LEVEL_2: BigInt(1) << BigInt(14), - UNDERAGE_DELETED: BigInt(1) << BigInt(15), + UNDERAGE_DELETED: BigInt(1) << BigInt(15), VERIFIED_BOT: BigInt(1) << BigInt(16), EARLY_VERIFIED_BOT_DEVELOPER: BigInt(1) << BigInt(17), }; From 12c92a29500d8a48ba7373f17eeaa2c1fceee3be Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Mon, 13 Sep 2021 17:31:07 +0200 Subject: [PATCH 20/90] Fix attachments not being saved to db --- util/src/entities/Message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts index f4c7fdc7..506db71a 100644 --- a/util/src/entities/Message.ts +++ b/util/src/entities/Message.ts @@ -128,7 +128,7 @@ export class Message extends BaseClass { sticker_items?: Sticker[]; @JoinColumn({ name: "attachment_ids" }) - @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message) + @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { cascade: true }) attachments?: Attachment[]; @Column({ type: "simple-json" }) From aa1f1a22a4cce0c51d84c10d5a741699f7bbfff2 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Mon, 13 Sep 2021 17:32:31 +0200 Subject: [PATCH 21/90] Delete attachments of deleted messages, fix #273 --- api/assets/schemas.json | 6 +++++- .../#channel_id/messages/#message_id/index.ts | 7 ++++--- .../routes/channels/#channel_id/messages/index.ts | 1 + api/src/util/Attachments.ts | 12 ++++++++++++ api/src/util/cdn.ts | 13 +++++++++++++ cdn/src/util/FileStorage.ts | 1 + 6 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 api/src/util/Attachments.ts diff --git a/api/assets/schemas.json b/api/assets/schemas.json index ac7df859..94aa0660 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -462,7 +462,11 @@ "payload_json": { "type": "string" }, - "file": {} + "file": {}, + "attachments": { + "type": "array", + "items": {} + } }, "additionalProperties": false, "definitions": { diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index d0f780db..b5220fab 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -3,6 +3,7 @@ import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; import { handleMessage, postHandleMessage } from "@fosscord/api"; import { MessageCreateSchema } from "../index"; +import { deleteMessageAttachments } from "@fosscord/api/util/Attachments"; const router = Router(); // TODO: message content/embed string length limit @@ -11,7 +12,7 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE const { message_id, channel_id } = req.params; var body = req.body as MessageCreateSchema; - const message = await Message.findOneOrFail({ id: message_id, channel_id }); + const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] }); const permissions = await getPermission(req.user_id, undefined, channel_id); @@ -33,6 +34,7 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE }); await Promise.all([ + await deleteMessageAttachments(message_id, new_message.attachments), //This delete all the attachments not in the array new_message!.save(), await emitEvent({ event: "MESSAGE_UPDATE", @@ -46,8 +48,6 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE return res.json(message); }); -// TODO: delete attachments in message - // permission check only if deletes messagr from other user router.delete("/", route({}), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; @@ -60,6 +60,7 @@ router.delete("/", route({}), async (req: Request, res: Response) => { permission.hasThrow("MANAGE_MESSAGES"); } + await deleteMessageAttachments(message_id); await Message.delete({ id: message_id }); await emitEvent({ diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 11334367..25ecc1c7 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -51,6 +51,7 @@ export interface MessageCreateSchema { }; payload_json?: string; file?: any; + attachments?: any[]; //TODO we should create an interface for attachments } // https://discord.com/developers/docs/resources/channel#create-message diff --git a/api/src/util/Attachments.ts b/api/src/util/Attachments.ts new file mode 100644 index 00000000..addda97f --- /dev/null +++ b/api/src/util/Attachments.ts @@ -0,0 +1,12 @@ +import { Attachment } from "@fosscord/util"; +import { deleteFile } from "@fosscord/api"; +import { URL } from "url"; + +export async function deleteMessageAttachments(messageId: string, keep?: Attachment[]) { + let attachments = await Attachment.find({ message_id: messageId }); + if (keep) + attachments = attachments.filter(x => !keep.map(k => k.id).includes(x.id)); + await Promise.all(attachments.map(a => a.remove())); + + attachments.forEach(a => deleteFile((new URL(a.url)).pathname)); //We don't need to await since this is done on the cdn +} diff --git a/api/src/util/cdn.ts b/api/src/util/cdn.ts index 3c71d980..88b0ea0d 100644 --- a/api/src/util/cdn.ts +++ b/api/src/util/cdn.ts @@ -38,3 +38,16 @@ export async function handleFile(path: string, body?: string): Promise Date: Mon, 13 Sep 2021 17:51:16 +0200 Subject: [PATCH 22/90] :art: remove unused imports --- api/src/routes/auth/register.ts | 1 - .../channels/#channel_id/messages/index.ts | 17 +- .../channels/#channel_id/permissions.ts | 2 +- api/src/routes/channels/#channel_id/typing.ts | 4 +- .../routes/channels/#channel_id/webhooks.ts | 2 +- api/src/routes/guilds/#guild_id/bans.ts | 2 +- api/src/routes/guilds/#guild_id/channels.ts | 2 +- api/src/routes/guilds/#guild_id/index.ts | 2 +- .../#guild_id/members/#member_id/index.ts | 2 +- .../routes/guilds/#guild_id/members/index.ts | 24 +- api/src/routes/guilds/#guild_id/roles.ts | 3 +- api/src/routes/guilds/#guild_id/vanity-url.ts | 2 +- .../#guild_id/voice-states/#user_id/index.ts | 2 +- api/src/routes/guilds/#guild_id/widget.ts | 5 +- api/src/routes/guilds/index.ts | 3 +- api/src/routes/guilds/templates/index.ts | 3 +- api/src/routes/users/@me/index.ts | 2 +- api/src/util/FieldError.ts | 25 ++ api/src/util/String.ts | 8 +- api/src/util/index.ts | 2 +- api/src/util/instanceOf.ts | 214 ------------------ 21 files changed, 61 insertions(+), 266 deletions(-) create mode 100644 api/src/util/FieldError.ts delete mode 100644 api/src/util/instanceOf.ts diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index e70e01ed..33f089b2 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -6,7 +6,6 @@ import "missing-native-js-functions"; import { generateToken } from "./login"; import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; import { HTTPError } from "lambert-server"; -import { In } from "typeorm"; const router: Router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 11334367..be9a41b1 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,7 +1,7 @@ import { Router, Response, Request } from "express"; import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { instanceOf, Length, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import multer from "multer"; import { sendMessage } from "@fosscord/api"; import { uploadFile } from "@fosscord/api"; @@ -61,17 +61,12 @@ router.get("/", async (req: Request, res: Response) => { if (!channel) throw new HTTPError("Channel not found", 404); isTextChannel(channel.type); + const around = `${req.query.around}`; + const before = `${req.query.before}`; + const after = `${req.query.after}`; + const limit = Number(req.query.limit) || 50; + if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100"); - try { - instanceOf({ $around: String, $after: String, $before: String, $limit: new Length(Number, 1, 100) }, req.query, { - path: "query", - req - }); - } catch (error) { - return res.status(400).json({ code: 50035, message: "Invalid Query", success: false, errors: error }); - } - var { around, after, before, limit }: { around?: string; after?: string; before?: string; limit?: number } = req.query; - if (!limit) limit = 50; var halfLimit = Math.floor(limit / 2); const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 959ab8e0..bc7ad5b8 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -2,7 +2,7 @@ import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, get import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router: Router = Router(); // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index ad973bca..a9dcb315 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -9,14 +9,14 @@ router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, re const user_id = req.user_id; const timestamp = Date.now(); const channel = await Channel.findOneOrFail({ id: channel_id }); - const member = await Member.findOneOrFail({ id: user_id }); + const member = await Member.findOneOrFail({ where: { id: user_id }, relations: ["roles"] }); await emitEvent({ event: "TYPING_START", channel_id: channel_id, data: { // this is the paylod - member: { ...member, roles: member.roles.map((x) => x.id) }, + member: { ...member, roles: member.roles?.map((x) => x.id) }, channel_id, timestamp, user_id, diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index f84dfcc5..7b894455 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { check, Length, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index c7fda9ad..e7d46898 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { getIpAdress, check, route } from "@fosscord/api"; +import { getIpAdress, route } from "@fosscord/api"; export interface BanCreateSchema { delete_message_days?: string; diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index e21327d1..13c6b515 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -1,7 +1,7 @@ import { Router, Response, Request } from "express"; import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { ChannelModifySchema } from "../../channels/#channel_id"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 690d4103..a75d1138 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; import { GuildCreateSchema } from "../index"; diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 1708b7eb..ab489743 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from "express"; import { Member, getPermission, Role, GuildMemberUpdateEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 335f21c7..386276c8 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,7 +1,8 @@ import { Request, Response, Router } from "express"; import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; -import { instanceOf, Length, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { MoreThan } from "typeorm"; +import { HTTPError } from "lambert-server"; const router = Router(); @@ -11,26 +12,17 @@ const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }); - await Member.IsInGuildOrFail(req.user_id, guild_id); - - try { - instanceOf({ $limit: new Length(Number, 1, 1000), $after: String }, req.query, { - path: "query", - req, - ref: { obj: null, key: "" } - }); - } catch (error) { - return res.status(400).json({ code: 50035, message: "Invalid Query", success: false, errors: error }); - } - - const { limit, after } = (req.query) as { limit?: number; after?: string }; + const limit = Number(req.query.limit) || 1; + if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000"); + const after = `${req.query.after}`; const query = after ? { id: MoreThan(after) } : {}; + await Member.IsInGuildOrFail(req.user_id, guild_id); + const members = await Member.find({ where: { guild_id, ...query }, select: PublicMemberProjection, - take: limit || 1, + take: limit, order: { id: "ASC" } }); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 5c549262..bac63bd4 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -11,8 +11,7 @@ import { DiscordApiErrors } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; -import { In } from "typeorm"; +import { route } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 9c0989cc..801768fb 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,6 +1,6 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; -import { check, Length, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 3d76938b..f9fbea54 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -1,5 +1,5 @@ import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts index c8caae14..2640618d 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/api/src/routes/guilds/#guild_id/widget.ts @@ -1,7 +1,6 @@ import { Request, Response, Router } from "express"; -import { getPermission, Guild } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { Guild } from "@fosscord/util"; +import { route } from "@fosscord/api"; export interface WidgetModifySchema { enabled: boolean; // whether the widget is enabled diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index ba951f96..082f8539 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,7 +1,6 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; import { ChannelModifySchema } from "../channels/#channel_id"; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index d7a42044..eb3867c8 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; export interface GuildTemplateCreateSchema { diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 68723374..abab9f5f 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; import { User, PrivateUserProjection } from "@fosscord/util"; -import { check, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; const router: Router = Router(); diff --git a/api/src/util/FieldError.ts b/api/src/util/FieldError.ts new file mode 100644 index 00000000..0b3f93d2 --- /dev/null +++ b/api/src/util/FieldError.ts @@ -0,0 +1,25 @@ +import "missing-native-js-functions"; + +export function FieldErrors(fields: Record) { + return new FieldError( + 50035, + "Invalid Form Body", + fields.map(({ message, code }) => ({ + _errors: [ + { + message, + code: code || "BASE_TYPE_INVALID" + } + ] + })) + ); +} + +// TODO: implement Image data type: Data URI scheme that supports JPG, GIF, and PNG formats. An example Data URI format is: data:image/jpeg;base64,BASE64_ENCODED_JPEG_IMAGE_DATA +// Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided. + +export class FieldError extends Error { + constructor(public code: string | number, public message: string, public errors?: any) { + super(message); + } +} diff --git a/api/src/util/String.ts b/api/src/util/String.ts index 49fba237..2fe32d2c 100644 --- a/api/src/util/String.ts +++ b/api/src/util/String.ts @@ -1,14 +1,16 @@ import { Request } from "express"; import { ntob } from "./Base64"; -import { FieldErrors } from "./instanceOf"; +import { FieldErrors } from "./FieldError"; +export const EMAIL_REGEX = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export function checkLength(str: string, min: number, max: number, key: string, req: Request) { if (str.length < min || str.length > max) { throw FieldErrors({ [key]: { code: "BASE_TYPE_BAD_LENGTH", - message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { length: `${min} - ${max}` }), - }, + message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { length: `${min} - ${max}` }) + } }); } } diff --git a/api/src/util/index.ts b/api/src/util/index.ts index c98784a4..4b1e8e77 100644 --- a/api/src/util/index.ts +++ b/api/src/util/index.ts @@ -1,6 +1,6 @@ export * from "./Base64"; export * from "./cdn"; -export * from "./instanceOf"; +export * from "./FieldError"; export * from "./ipAddress"; export * from "./Message"; export * from "./passwordStrength"; diff --git a/api/src/util/instanceOf.ts b/api/src/util/instanceOf.ts deleted file mode 100644 index 4d9034e5..00000000 --- a/api/src/util/instanceOf.ts +++ /dev/null @@ -1,214 +0,0 @@ -// different version of lambert-server instanceOf with discord error format - -import { NextFunction, Request, Response } from "express"; -import { Tuple } from "lambert-server"; -import "missing-native-js-functions"; - -export const OPTIONAL_PREFIX = "$"; -export const EMAIL_REGEX = - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - -export function check(schema: any) { - return (req: Request, res: Response, next: NextFunction) => { - try { - const result = instanceOf(schema, req.body, { path: "body", req, ref: { obj: null, key: "" } }); - if (result === true) return next(); - throw result; - } catch (error) { - return res.status(400).json({ code: 50035, message: "Invalid Form Body", success: false, errors: error }); - } - }; -} - -export function FieldErrors(fields: Record) { - return new FieldError( - 50035, - "Invalid Form Body", - fields.map(({ message, code }) => ({ - _errors: [ - { - message, - code: code || "BASE_TYPE_INVALID" - } - ] - })) - ); -} - -// TODO: implement Image data type: Data URI scheme that supports JPG, GIF, and PNG formats. An example Data URI format is: data:image/jpeg;base64,BASE64_ENCODED_JPEG_IMAGE_DATA -// Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided. - -export class FieldError extends Error { - constructor(public code: string | number, public message: string, public errors?: any) { - super(message); - } -} - -export class Email { - constructor(public email: string) {} - check() { - return !!this.email.match(EMAIL_REGEX); - } -} - -export class Length { - constructor(public type: any, public min: number, public max: number) {} - - check(value: string) { - if (typeof value === "string" || Array.isArray(value)) return value.length >= this.min && value.length <= this.max; - if (typeof value === "number" || typeof value === "bigint") return value >= this.min && value <= this.max; - return false; - } -} - -export function instanceOf( - type: any, - value: any, - { - path = "", - optional = false, - errors = {}, - req, - ref - }: { path?: string; optional?: boolean; errors?: any; req: Request; ref?: { key: string | number; obj: any } } -): Boolean { - if (!ref) ref = { obj: null, key: "" }; - if (!path) path = "body"; - if (!type) return true; // no type was specified - - try { - if (value == null) { - if (optional) return true; - throw new FieldError("BASE_TYPE_REQUIRED", req.t("common:field.BASE_TYPE_REQUIRED")); - } - - switch (type) { - case String: - value = `${value}`; - ref.obj[ref.key] = value; - if (typeof value === "string") return true; - throw new FieldError("BASE_TYPE_STRING", req.t("common:field.BASE_TYPE_STRING")); - case Number: - value = Number(value); - ref.obj[ref.key] = value; - if (typeof value === "number" && !isNaN(value)) return true; - throw new FieldError("BASE_TYPE_NUMBER", req.t("common:field.BASE_TYPE_NUMBER")); - case BigInt: - try { - value = BigInt(value); - ref.obj[ref.key] = value; - if (typeof value === "bigint") return true; - } catch (error) {} - throw new FieldError("BASE_TYPE_BIGINT", req.t("common:field.BASE_TYPE_BIGINT")); - case Boolean: - if (value == "true") value = true; - if (value == "false") value = false; - ref.obj[ref.key] = value; - if (typeof value === "boolean") return true; - throw new FieldError("BASE_TYPE_BOOLEAN", req.t("common:field.BASE_TYPE_BOOLEAN")); - - case Email: - if (new Email(value).check()) return true; - throw new FieldError("EMAIL_TYPE_INVALID_EMAIL", req.t("common:field.EMAIL_TYPE_INVALID_EMAIL")); - case Date: - value = new Date(value); - ref.obj[ref.key] = value; - // value.getTime() can be < 0, if it is before 1970 - if (!isNaN(value)) return true; - throw new FieldError("DATE_TYPE_PARSE", req.t("common:field.DATE_TYPE_PARSE")); - } - - if (typeof type === "object") { - if (Array.isArray(type)) { - if (!Array.isArray(value)) throw new FieldError("BASE_TYPE_ARRAY", req.t("common:field.BASE_TYPE_ARRAY")); - if (!type.length) return true; // type array didn't specify any type - - return ( - value.every((val, i) => { - errors[i] = {}; - - if ( - instanceOf(type[0], val, { - path: `${path}[${i}]`, - optional, - errors: errors[i], - req, - ref: { key: i, obj: value } - }) === true - ) { - delete errors[i]; - return true; - } - - return false; - }) || errors - ); - } else if (type?.constructor?.name != "Object") { - if (type instanceof Tuple) { - if ((type).types.some((x) => instanceOf(x, value, { path, optional, errors, req, ref }))) return true; - throw new FieldError("BASE_TYPE_CHOICES", req.t("common:field.BASE_TYPE_CHOICES", { types: type.types })); - } else if (type instanceof Length) { - let length = type; - if (instanceOf(length.type, value, { path, optional, req, ref, errors }) !== true) return errors; - let val = ref.obj[ref.key]; - if ((type).check(val)) return true; - throw new FieldError( - "BASE_TYPE_BAD_LENGTH", - req.t("common:field.BASE_TYPE_BAD_LENGTH", { - length: `${type.min} - ${type.max}` - }) - ); - } - try { - if (value instanceof type) return true; - } catch (error) { - throw new FieldError("BASE_TYPE_CLASS", req.t("common:field.BASE_TYPE_CLASS", { type })); - } - } - - if (typeof value !== "object") throw new FieldError("BASE_TYPE_OBJECT", req.t("common:field.BASE_TYPE_OBJECT")); - - const diff = Object.keys(value).missing( - Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x)) - ); - - if (diff.length) throw new FieldError("UNKOWN_FIELD", req.t("common:field.UNKOWN_FIELD", { key: diff })); - - return ( - Object.keys(type).every((key) => { - let newKey = key; - const OPTIONAL = key.startsWith(OPTIONAL_PREFIX); - if (OPTIONAL) newKey = newKey.slice(OPTIONAL_PREFIX.length); - errors[newKey] = {}; - - if ( - instanceOf(type[key], value[newKey], { - path: `${path}.${newKey}`, - optional: OPTIONAL, - errors: errors[newKey], - req, - ref: { key: newKey, obj: value } - }) === true - ) { - delete errors[newKey]; - return true; - } - - return false; - }) || errors - ); - } else if (typeof type === "number" || typeof type === "string" || typeof type === "boolean") { - if (value === type) return true; - throw new FieldError("BASE_TYPE_CONSTANT", req.t("common:field.BASE_TYPE_CONSTANT", { value: type })); - } else if (typeof type === "bigint") { - if (BigInt(value) === type) return true; - throw new FieldError("BASE_TYPE_CONSTANT", req.t("common:field.BASE_TYPE_CONSTANT", { value: type })); - } - - return type == value; - } catch (error) { - let e = error as FieldError; - errors._errors = [{ message: e.message, code: e.code }]; - return errors; - } -} From 8568b3cdce917731f99456601b31228b933fa9ed Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 17:51:37 +0200 Subject: [PATCH 23/90] :bug: fix fosscord.js --- api/src/routes/gateway.ts | 14 ++++++++++++++ gateway/src/opcodes/Identify.ts | 5 +++-- gateway/src/schema/Identify.ts | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/api/src/routes/gateway.ts b/api/src/routes/gateway.ts index d4208341..88d9dfda 100644 --- a/api/src/routes/gateway.ts +++ b/api/src/routes/gateway.ts @@ -9,4 +9,18 @@ router.get("/", route({}), (req: Request, res: Response) => { res.json({ url: endpoint || process.env.GATEWAY || "ws://localhost:3002" }); }); +router.get("/bot", route({}), (req: Request, res: Response) => { + const { endpoint } = Config.get().gateway; + res.json({ + url: endpoint || process.env.GATEWAY || "ws://localhost:3002", + shards: 1, + session_start_limit: { + total: 1000, + remaining: 999, + reset_after: 14400000, + max_concurrency: 1 + } + }); +}); + export default router; diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 9eb4cd32..f6a4478f 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -50,12 +50,13 @@ export async function onIdentify(this: WebSocket, data: Payload) { this.shard_id = identify.shard[0]; this.shard_count = identify.shard[1]; if ( - !this.shard_count || - !this.shard_id || + this.shard_count == null || + this.shard_id == null || this.shard_id >= this.shard_count || this.shard_id < 0 || this.shard_count <= 0 ) { + console.log(identify.shard); return this.close(CLOSECODES.Invalid_shard); } } diff --git a/gateway/src/schema/Identify.ts b/gateway/src/schema/Identify.ts index 0835ddc7..6054f2e8 100644 --- a/gateway/src/schema/Identify.ts +++ b/gateway/src/schema/Identify.ts @@ -43,6 +43,7 @@ export const IdentifySchema = { $user_guild_settings_version: Number, }, $v: Number, + $version: Number, }; export interface IdentifySchema { From b15a93d120e5998eccedba99086c1e539ba5983f Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Mon, 13 Sep 2021 19:09:01 +0200 Subject: [PATCH 24/90] Emit USER_UPDATE, fix #214 --- api/src/routes/users/@me/index.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 68723374..a8c0edc4 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; -import { User, PrivateUserProjection } from "@fosscord/util"; -import { check, route } from "@fosscord/api"; +import { User, PrivateUserProjection, emitEvent, UserUpdateEvent } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { handleFile } from "@fosscord/api"; const router: Router = Router(); @@ -33,8 +33,16 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await new User({ ...body, id: req.user_id }).save(); - // TODO: dispatch user update event + await new User({ ...body, id: req.user_id }).save(); + + //Need to reload user from db due to https://github.com/typeorm/typeorm/issues/3490 + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: PrivateUserProjection }); + + await emitEvent({ + event: "USER_UPDATE", + user_id: req.user_id, + data: user + } as UserUpdateEvent); res.json(user); }); From 68eae50a63df0a84d75c05abce5142a33f4c5bd3 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 13 Sep 2021 19:12:19 +0200 Subject: [PATCH 25/90] Update index.ts --- api/src/routes/users/@me/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index a8c0edc4..c0002d79 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -37,7 +37,7 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: //Need to reload user from db due to https://github.com/typeorm/typeorm/issues/3490 const user = await User.findOneOrFail({ where: { id: req.user_id }, select: PrivateUserProjection }); - + // TODO: send update member list event in gateway await emitEvent({ event: "USER_UPDATE", user_id: req.user_id, From 7cdcb82ecdf51b2dab5808a89ed50b96af0a60cc Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Mon, 13 Sep 2021 19:25:44 +0200 Subject: [PATCH 26/90] Fix #356 --- api/assets/schemas.json | 35 +++++++++++++++++++----- api/src/routes/guilds/#guild_id/index.ts | 10 +++---- api/src/routes/guilds/index.ts | 2 +- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 94aa0660..3f760c35 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -2548,7 +2548,10 @@ "type": "string" }, "system_channel_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "rules_channel_id": { "type": "string" @@ -2802,13 +2805,22 @@ "type": "object", "properties": { "banner": { - "type": "string" + "type": [ + "null", + "string" + ] }, "splash": { - "type": "string" + "type": [ + "null", + "string" + ] }, "description": { - "type": "string" + "type": [ + "null", + "string" + ] }, "features": { "type": "array", @@ -2829,13 +2841,19 @@ "type": "integer" }, "public_updates_channel_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "afk_timeout": { "type": "integer" }, "afk_channel_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "preferred_locale": { "type": "string" @@ -2854,7 +2872,10 @@ "type": "string" }, "system_channel_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "rules_channel_id": { "type": "string" diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 690d4103..9c67798d 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -9,17 +9,17 @@ import { GuildCreateSchema } from "../index"; const router = Router(); export interface GuildUpdateSchema extends Omit { - banner?: string; - splash?: string; - description?: string; + banner?: string | null; + splash?: string | null; + description?: string | null; features?: string[]; verification_level?: number; default_message_notifications?: number; system_channel_flags?: number; explicit_content_filter?: number; - public_updates_channel_id?: string; + public_updates_channel_id?: string | null; afk_timeout?: number; - afk_channel_id?: string; + afk_channel_id?: string | null; preferred_locale?: string; } diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index ba951f96..674dc16b 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -16,7 +16,7 @@ export interface GuildCreateSchema { icon?: string; channels?: ChannelModifySchema[]; guild_template_code?: string; - system_channel_id?: string; + system_channel_id?: string | null; rules_channel_id?: string; } From 9b10ab2d0b7a7a1c3c9a8b65a02518dffdbf04ba Mon Sep 17 00:00:00 2001 From: uurgothat Date: Mon, 13 Sep 2021 21:22:14 +0300 Subject: [PATCH 27/90] add store endpoints --- api/src/routes/store/applications.ts | 11 +++++++++++ api/src/routes/store/skus.ts | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 api/src/routes/store/applications.ts create mode 100644 api/src/routes/store/skus.ts diff --git a/api/src/routes/store/applications.ts b/api/src/routes/store/applications.ts new file mode 100644 index 00000000..69cd716d --- /dev/null +++ b/api/src/routes/store/applications.ts @@ -0,0 +1,11 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/applications/:id", async (req: Request, res: Response) => { + //TODO + const { id } = req.params; + res.json([]).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/store/skus.ts b/api/src/routes/store/skus.ts new file mode 100644 index 00000000..5c37850d --- /dev/null +++ b/api/src/routes/store/skus.ts @@ -0,0 +1,11 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/skus/:id", async (req: Request, res: Response) => { + //TODO + const { id } = req.params; + res.json([]).status(200); +}); + +export default router; \ No newline at end of file From 8f68c4abcf8cb1e15a694795a3a13538bc4f1dcc Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Tue, 14 Sep 2021 09:38:21 +0200 Subject: [PATCH 28/90] Fix invites creation --- api/assets/schemas.json | 5 ++++- api/src/routes/channels/#channel_id/invites.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 3f760c35..9c34f968 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -713,7 +713,10 @@ "type": "object", "properties": { "target_user_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, "target_type": { "type": [ diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index 2edb4fc2..71612e31 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -8,7 +8,7 @@ import { isTextChannel } from "./messages"; const router: Router = Router(); export interface InviteCreateSchema { - target_user_id?: string; + target_user_id?: string | null; target_type?: string | null; validate?: string | null; // ? what is this max_age?: number; From b17f3c053d0449823892aa08172fd6fe4d465e78 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Tue, 14 Sep 2021 09:38:52 +0200 Subject: [PATCH 29/90] Emit INVITE_DELETE --- api/src/routes/invites/index.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index 6e77a853..ae8a5944 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,7 +1,8 @@ import { Router, Request, Response } from "express"; -import { getPermission, Guild, Invite, Member, PublicInviteRelation } from "@fosscord/util"; +import { emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, Member, PublicInviteRelation } from "@fosscord/util"; import { route } from "@fosscord/api"; import { HTTPError } from "lambert-server"; + const router: Router = Router(); router.get("/:code", route({}), async (req: Request, res: Response) => { @@ -35,7 +36,19 @@ router.delete("/:code", route({}), async (req: Request, res: Response) => { if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401); - await Promise.all([Invite.delete({ code }), Guild.update({ vanity_url_code: code }, { vanity_url_code: undefined })]); + await Promise.all([ + Invite.delete({ code }), + Guild.update({ vanity_url_code: code }, { vanity_url_code: undefined }), + emitEvent({ + event: "INVITE_DELETE", + guild_id: guild_id, + data: { + channel_id: channel_id, + guild_id: guild_id, + code: code + } + } as InviteDeleteEvent) + ]); res.json({ invite: invite }); }); From 8893fd16d90ad599538ccb62f8f77711aa891bbd Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 14 Sep 2021 21:16:04 +0200 Subject: [PATCH 30/90] fix #128 --- .../routes/channels/#channel_id/webhooks.ts | 61 ++++++++++++++++--- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 7b894455..9c8df3fb 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,9 +1,21 @@ import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; -import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; +import { handleFile, route } from "@fosscord/api"; +import { + Channel, + Config, + emitEvent, + getPermission, + Snowflake, + trimSpecial, + User, + Webhook, + WebhooksUpdateEvent, + WebhookType +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; import { DiscordApiErrors } from "@fosscord/util"; +import { generateToken } from "../../auth/login"; const router: Router = Router(); // TODO: webhooks @@ -11,13 +23,26 @@ export interface WebhookCreateSchema { /** * @maxLength 80 */ - name: string; - avatar: string; + name?: string; + avatar?: string; } +router.get("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req, res) => { + const webhooks = await Webhook.find({ + where: { channel_id: req.params.channel_id }, + select: ["application", "avatar", "channel_id", "guild_id", "id", "token", "type", "user", "source_guild", "name"], + relations: ["user", "application", "source_guild"] + }); + + res.json(webhooks); +}); + // TODO: use Image Data Type for avatar instead of String router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - const channel_id = req.params.channel_id; + var { avatar, name } = req.body as WebhookCreateSchema; + name = trimSpecial(name) || "Webhook"; + if (name === "clyde") throw new HTTPError("Invalid name", 400); + const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ id: channel_id }); isTextChannel(channel.type); @@ -27,11 +52,29 @@ router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOO const { maxWebhooks } = Config.get().limits.channel; if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - var { avatar, name } = req.body as { name: string; avatar?: string }; - name = trimSpecial(name); - if (name === "clyde") throw new HTTPError("Invalid name", 400); - + const id = Snowflake.generate(); // TODO: save webhook in database and send response + const webhook = await new Webhook({ + id, + name, + avatar: await handleFile(`/icons/${id}`, avatar), + user: await User.getPublicUser(req.user_id), + guild_id: channel.guild_id, + channel_id, + token: await generateToken(id), + type: WebhookType.Incoming + }).save(); + + await emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: channel.guild_id + } + } as WebhooksUpdateEvent); + + return res.json(webhook); }); export default router; From 8f862f0e5dba3985b4f38406fc19b5c5350324b9 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 14 Sep 2021 21:16:15 +0200 Subject: [PATCH 31/90] fix #129 --- api/src/routes/guilds/#guild_id/webhooks.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 api/src/routes/guilds/#guild_id/webhooks.ts diff --git a/api/src/routes/guilds/#guild_id/webhooks.ts b/api/src/routes/guilds/#guild_id/webhooks.ts new file mode 100644 index 00000000..a9dd164a --- /dev/null +++ b/api/src/routes/guilds/#guild_id/webhooks.ts @@ -0,0 +1,17 @@ +import { Router, Response, Request } from "express"; +import { route } from "@fosscord/api"; +import { Webhook } from "@fosscord/util"; + +const router: Router = Router(); + +router.get("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { + const webhooks = await Webhook.find({ + where: { guild_id: req.params.guild_id }, + select: ["application", "avatar", "channel_id", "guild_id", "id", "token", "type", "user", "source_guild", "name"], + relations: ["user", "application", "source_guild"] + }); + + return res.json(webhooks); +}); + +export default router; From df2b83ac158be1e7233d8edce59033c15c193599 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:15:55 +0200 Subject: [PATCH 32/90] :construction: webhook --- api/assets/schemas.json | 255 +++++++++++++++++- api/client_test/index.html | 2 +- api/src/middlewares/Authentication.ts | 4 +- api/src/middlewares/ErrorHandler.ts | 7 +- api/src/routes/discoverable-guilds.ts | 2 +- .../routes/guilds/#guild_id/integrations.ts | 10 + api/src/routes/template.ts.disabled | 2 +- api/src/routes/webhooks/#webhook_id/index.ts | 89 ++++++ api/src/util/route.ts | 8 +- util/src/entities/Webhook.ts | 6 +- util/src/util/Regex.ts | 2 +- 11 files changed, 369 insertions(+), 18 deletions(-) create mode 100644 api/src/routes/guilds/#guild_id/integrations.ts create mode 100644 api/src/routes/webhooks/#webhook_id/index.ts diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 9c34f968..88558cfa 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -1770,10 +1770,6 @@ } }, "additionalProperties": false, - "required": [ - "avatar", - "name" - ], "definitions": { "ChannelType": { "enum": [ @@ -7446,5 +7442,256 @@ } }, "$schema": "http://json-schema.org/draft-07/schema#" + }, + "WebhookModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "ChannelType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelType" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" } } \ No newline at end of file diff --git a/api/client_test/index.html b/api/client_test/index.html index ac66df06..335b477c 100644 --- a/api/client_test/index.html +++ b/api/client_test/index.html @@ -11,7 +11,7 @@ window.__OVERLAY__ = /overlay/.test(location.pathname); window.__BILLING_STANDALONE__ = /^\/billing/.test(location.pathname); window.GLOBAL_ENV = { - API_ENDPOINT: "/api", + API_ENDPOINT: `//${location.host}/api`, API_VERSION: 9, GATEWAY_ENDPOINT: `${location.protocol === "https:" ? "wss://" : "ws://"}${location.hostname}:3002`, WEBAPP_ENDPOINT: "", diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index a300c786..32307f42 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -5,11 +5,11 @@ import { checkToken, Config } from "@fosscord/util"; export const NO_AUTHORIZATION_ROUTES = [ "/auth/login", "/auth/register", - "/webhooks/", "/ping", "/gateway", "/experiments", - /\/guilds\/\d+\/widget\.(json|png)/ + /\/guilds\/\d+\/widget\.(json|png)/, + /\/webhooks\/\d+\/\w+/ // only exclude webhook calls with webhook token ]; export const API_PREFIX = /^\/api(\/v\d+)?/; diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index d288f3fb..338da8d5 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,9 +1,10 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; -import { EntityNotFoundError } from "typeorm"; import { FieldError } from "@fosscord/api"; import { ApiError } from "@fosscord/util"; +const EntityNotFoundErrorRegex = /"(\w+)"/; + export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { if (!error) return next(); @@ -18,8 +19,8 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne code = error.code; message = error.message; httpcode = error.httpStatus; - } else if (error instanceof EntityNotFoundError) { - message = `${(error as any).stringifyTarget || "Item"} could not be found`; + } else if (error.name === "EntityNotFoundError") { + message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; code = 404; } else if (error instanceof FieldError) { code = Number(error.code); diff --git a/api/src/routes/discoverable-guilds.ts b/api/src/routes/discoverable-guilds.ts index f667eb2a..71789123 100644 --- a/api/src/routes/discoverable-guilds.ts +++ b/api/src/routes/discoverable-guilds.ts @@ -10,7 +10,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { // ! this only works using SQL querys // TODO: implement this with default typeorm query // const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) }); - const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit)) }); + const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit) || 50) }); res.send({ guilds: guilds }); }); diff --git a/api/src/routes/guilds/#guild_id/integrations.ts b/api/src/routes/guilds/#guild_id/integrations.ts new file mode 100644 index 00000000..f6b8e99d --- /dev/null +++ b/api/src/routes/guilds/#guild_id/integrations.ts @@ -0,0 +1,10 @@ +import { route } from "@fosscord/api"; +import { Router, Request, Response } from "express"; +const router = Router(); + +router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { + // TODO: integrations (followed channels, youtube, twitch) + res.send([]); +}); + +export default router; diff --git a/api/src/routes/template.ts.disabled b/api/src/routes/template.ts.disabled index ad785f10..524e981b 100644 --- a/api/src/routes/template.ts.disabled +++ b/api/src/routes/template.ts.disabled @@ -4,7 +4,7 @@ import { Router, Request, Response } from "express"; const router = Router(); router.get("/", async (req: Request, res: Response) => { - res.send({}); + res.json({}); }); export default router; diff --git a/api/src/routes/webhooks/#webhook_id/index.ts b/api/src/routes/webhooks/#webhook_id/index.ts new file mode 100644 index 00000000..e9b40ebf --- /dev/null +++ b/api/src/routes/webhooks/#webhook_id/index.ts @@ -0,0 +1,89 @@ +import { Channel, Config, emitEvent, JWTOptions, Webhook, WebhooksUpdateEvent } from "@fosscord/util"; +import { route, Authentication, handleFile } from "@fosscord/api"; +import { Router, Request, Response, NextFunction } from "express"; +import jwt from "jsonwebtoken"; +import { HTTPError } from "lambert-server"; +const router = Router(); + +export interface WebhookModifySchema { + name?: string; + avatar?: string; + // channel_id?: string; // TODO +} + +function validateWebhookToken(req: Request, res: Response, next: NextFunction) { + const { jwtSecret } = Config.get().security; + + jwt.verify(req.params.token, jwtSecret, JWTOptions, async (err, decoded: any) => { + if (err) return next(new HTTPError("Invalid Token", 401)); + next(); + }); +} + +router.get("/", route({}), async (req: Request, res: Response) => { + res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); +}); + +router.get("/:token", route({}), validateWebhookToken, async (req: Request, res: Response) => { + res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); +}); + +router.patch("/", route({ body: "WebhookModifySchema", permission: "MANAGE_WEBHOOKS" }), (req: Request, res: Response) => { + return updateWebhook(req, res); +}); + +router.patch("/:token", route({ body: "WebhookModifySchema" }), validateWebhookToken, (req: Request, res: Response) => { + return updateWebhook(req, res); +}); + +async function updateWebhook(req: Request, res: Response) { + const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); + if (req.body.channel_id) await Channel.findOneOrFail({ id: req.body.channel_id, guild_id: webhook.guild_id }); + + webhook.assign({ + ...req.body, + avatar: await handleFile(`/icons/${req.params.webhook_id}`, req.body.avatar) + }); + + await Promise.all([ + emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id: webhook.channel_id, + data: { + channel_id: webhook.channel_id, + guild_id: webhook.guild_id + } + } as WebhooksUpdateEvent), + webhook.save() + ]); + + res.json(webhook); +} + +router.delete("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { + return deleteWebhook(req, res); +}); + +router.delete("/:token", route({}), validateWebhookToken, (req: Request, res: Response) => { + return deleteWebhook(req, res); +}); + +async function deleteWebhook(req: Request, res: Response) { + const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); + + await Promise.all([ + emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id: webhook.channel_id, + data: { + channel_id: webhook.channel_id, + guild_id: webhook.guild_id + } + } as WebhooksUpdateEvent), + webhook.remove() + ]); + + res.sendStatus(204); +} + +export default router; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 6cd8f622..1e2beb5d 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -1,4 +1,4 @@ -import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; +import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions, Webhook } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; import fs from "fs"; import path from "path"; @@ -54,9 +54,13 @@ export function route(opts: RouteOptions) { return async (req: Request, res: Response, next: NextFunction) => { if (opts.permission) { const required = new Permissions(opts.permission); + if (req.params.webhook_id) { + const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); + req.params.channel_id = webhook.channel_id; + req.params.guild_id = webhook.guild_id; + } const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); - // bitfield comparison: check if user lacks certain permission if (!permission.has(required)) { throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); } diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts index 12ba0d08..d0d98804 100644 --- a/util/src/entities/Webhook.ts +++ b/util/src/entities/Webhook.ts @@ -18,13 +18,13 @@ export class Webhook extends BaseClass { @Column({ type: "simple-enum", enum: WebhookType }) type: WebhookType; - @Column({ nullable: true }) - name?: string; + @Column() + name: string; @Column({ nullable: true }) avatar?: string; - @Column({ nullable: true }) + @Column({ nullable: true, select: false }) token?: string; @Column({ nullable: true }) diff --git a/util/src/util/Regex.ts b/util/src/util/Regex.ts index 83fc9fe8..b5d23b7f 100644 --- a/util/src/util/Regex.ts +++ b/util/src/util/Regex.ts @@ -1,5 +1,5 @@ export const DOUBLE_WHITE_SPACE = /\s\s+/g; -export const SPECIAL_CHAR = /[@#`:\r\n\t\f\v\p{C}]/gu; +export const SPECIAL_CHAR = /[@#\r\n\t\f\v]/gu; export const CHANNEL_MENTION = /<#(\d+)>/g; export const USER_MENTION = /<@!?(\d+)>/g; export const ROLE_MENTION = /<@&(\d+)>/g; From ffa98126ed104dfd66950275c094b64229c9cac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkin=20Alp=20G=C3=BCney?= Date: Tue, 14 Sep 2021 23:50:45 +0300 Subject: [PATCH 33/90] Add licence file into fosscord-server repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Erkin Alp Güney --- COPYING | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 COPYING diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From bd9cc4c537f973422d4076b0c434dfa067827330 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:46:22 +0200 Subject: [PATCH 34/90] :arrow_up: fix + update dependencies --- api/package-lock.json | Bin 779701 -> 807341 bytes bundle/package-lock.json | Bin 69487 -> 72597 bytes cdn/package-lock.json | Bin 350423 -> 354786 bytes gateway/package-lock.json | Bin 153148 -> 156237 bytes util/package-lock.json | Bin 547133 -> 446397 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index c14b92fa729601463402b4691a33a91713d1bb95..e347d85ec6d043b69145ce80503f843b91ccbf5d 100644 GIT binary patch delta 19994 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 cZq#lSx4$y=eaG@L&2eVRv3;+NC diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 0092e0d5a34d5ce712244bc6116730042ee15909..718cc969b5e4ecf07d132ad70a37c1b80c1a96d3 100644 GIT binary patch delta 3931 zcmds3TZ|i58J4%3Y&Ik-LbsbslhkV}C5?CO8G9~vr9ODZ9*?ikWUD0u()*#H7k=N@B|$sT8_ zSeGWbsg4-=Xu4a^_URfDt{SmwgOBxbX^T^_S}|KmkK}40IWr4oBNL_i7&E8|1DMKY zP!a3XS!R1Ok>k1ebO4w~A@`LNzYl<)+`zzF-&+gB{KFb*xK0&k3K|m=EGHA;vWE2? zR>Dw6=%sqqc3f^NHj9-iS<2+{lT3Qrlc}aYHH=z)M1@TQZpLR0lb-Y&IkLzI+h}uI zO@Va;120}d07M)Ge}I?hF%)&w(Qp_mWhm6K8+QMx zr$QTtmzS4AyrAlWS4M#kJsAwu2a*x=eq7Knt!~hC)V5#*c|nw9$&?g166_j^98`5> zBJtjgWEw%mmi<8Cf5ME7prHgE#R~FDP*%*KEC_tioNq20CN~}nd<3jNzPadc2acUR z@BVH5@DljW4=|vfUj)e~PJ@NbAiI^-Oq#*VXf&lp>#V(n<@$8mDoQD(zD*CZYI)Kh zk=s!UDPwA3HjP$^nyAONTAi9rVv~_58yUWfh4FN`(=er$Wb-!V0gQvYub$W|`5`xQ z;pCwO7z3%BNig}r0?`D|C+rmyyBB5@!e)vN;5tdOU!FV?*d1=*T zLZex?EL4j2Y^o(BM^zA56Pi9nPhJo$nV00g$C0Of+AM!={#*|E!287MOXk4p`kVsr zrSlO{&=tc7&4Zqu6YV1_RL;}|Mc!MonzC&>viC01Q?{e`u|-dj4Qp)rdF>v&dUkhJ zMA7?E!%|g6H}{h8Q@IGfh^)B>NMhk3pLVIwu7T?};A1`q{d#(Tn@9wW?Dl(t%#`w~ z-NUspW9o8Dmjx%D7&5htP~!9if{$lV(UDBMy4@Jbsb)f=Tl%bti1i^YI?Zh;6F01p zla9_*m8ZiD3p0Y~7c;ujwL~vZziiK-5e#VUC&0f?tbg5Re`e!@7Ma%IGI?p3!Ok_17(QDkod zj=*uSPMmhBjCDGI`Gp66;U4iuuU`zYWT{;*W%5aBK-w9GiK~O6g6EV%p zRob>kD4R@KQZ(0T_nE3w(psFzY}qKo;OP?F2{)ZgtC`bkR^Gdl3B(|pg-2I)x)CJ}7h>hu~KsTRVNS*kJ(K36JZ(X@yT zjo3`FSVPO=In1D@l9?mJc%fa0B@29Ot4Q-!a#lnPN>&<-tZ|>Xo~HP~f(P7=27#43 zA#e2Ft##I}4YG-efkDH9SZS(Mtk9`)DRW}RxfrySlJRgZj}3~I45`*qM6p*esY1P; zLpmmnGCe}K2zsoAn+8ueof$-sohEKf{BH4X>^1Plorm3ezjw~_B0qi&0`GkF)B+L# z%(Y+xnTkeFm5fGT)6!yv%PZ()#D^I^nlncV*OzJ467{eYV@R4X%562A#o3hNP~!ww zF{cSUX~h$ErGcS($CH+9M;dbDIb<&Q8oOU6=oEPS+GoHkCmwhI34XTV`L*lcczZXura9K_tpgg!>0 z-q^h7`tC>G-1_gne!{yA_e0utu5P%u|6IF&X=5LVw9P3LdgqsceUpO?_l-NRu75-Z R*asp5{^z|sIU4v!;6GKD3%&pV delta 2062 zcmchW&2QUe9LMFhp{3K>ZglHbxV=7#ZE|!X%B2@9nucdIB-BiLjrEFBNAv5Y%7cn4L6PJFP81xR0=bGigAPVMP4Fm%lTkpZSr+#ql9cTq(FjxlX=2GzCg{3j@ z)f5d5Ts{aMx#r+^Gdsxe4oDzVgAY!?b94Uj;c~)hm21Pt;LLnv9Q^((3LbpCjCjHJ z!gErx-Qf}@uU12<*mqaOI!@RTn|DfPvWu>>O+zwRBO1ipgcC00@O*-gFucwR?NXTc z$0RHf)(j>^Vu@NDOZh8CFDS->o7)GFMR3M<6kIv~0xTc9jDS0LQ84G91mAvf6kJf3 zfUrCv5rt4Cx>{h?h^#-JY%pPWkTbdhW;&Fiwbx8vheB&27Vz*Dm2AiQa$4lH`a0Y5 zByD0XMaYWM4EVS(M$%kf&a~PS?{xN=g(kv1Cw8CFrmjl1rzW6#;9Od26kp_A+UW&621TTQAW?HkR=T?I@nB zCzDb>pFv5b`DCX1rNt?P1oytgLE`3NaQoUmW;w)F^fYf*ZBHN;?!*EFp5?L_n`n8p z2C89N3SVz@nQR-iOCe8(Xe14})5oQ}=}yr&+qX1jl~SvIy4oT20 zJ8pD>rKF^EYLrzBVC9y;IhhE`XM~n67jvRJMCXD;%Bx!0Al;0ZHPctIMJ?PVt9x~0 zuvgkbK093-1KS&Jpc*HUVR!#skuz$D6T^0d;UbYWe=$T6Rwo_KCmbB-(sbR@TD6KD zR8ddcT_91qZ!*cQf@^p~5OqsxdD_9KTFg77MVnexr3EEf%kGm#!S4NMps%5w9?CPE zsm(u+A&cYQ$3rEB&6Hj)PfWG&I`GIL`XPZk_cf^x6X=5OK|E^f8Qi%Z%QNc_GNhG{4Zk&1~;}I%#2jq zMey1mhr#=wPr|boCq}C6@%`l4TZe}F9s#Rx8vG+20`n(-`~N=N`T8B#Xq0~9Kk$v~ z-%O5D>m_jG`$-_&a)FKMDZqFi?ooiVw^k2A{~gyD{P5?ukBv_AeHQT1y$k|le_umJ b<@)2;-TOCJM(X-Jc=N5NuYPhI*+u>X&I diff --git a/cdn/package-lock.json b/cdn/package-lock.json index 541ee77c05dcdabfd3f1493205bddea54a1b14f2..7a4edd89674878d41c0794505b4c1b61f482e1dd 100644 GIT binary patch delta 27488 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 HzWx6JkY0u{ delta 26430 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

aVoR}T;C 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 BQ~3Y@ diff --git a/gateway/package-lock.json b/gateway/package-lock.json index 64c542e46bfdc095e8f2b3d08d678bf87c09b735..40db319da8a742b4f6d65a61a87afc99f19909b0 100644 GIT binary patch delta 4873 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 TQpnxf^M3-HhGEk1@wR^fOucPE delta 3621 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 Y4|$<=0r7aZhBbVI2StZGeaDjj3;EAc00000 delta 67910 zcmeFadAK8GnKyh@WjRf+r|G?$?mpen2!uYVsw9;J5y+m(zEowCtx~B}DydZVtsJ0X z+`vJ=QXW^p*>qH(^*BU9u#s_eLz!Ad%fRv4Rd{8 z|I<}ZRg(MJ?)!f3`*;7&y|?bW_X~%P|4a`)uv?3%kL=K^`{uT)ztb%+?M%B$S4Fw0 z@D{&^iOEJ53$&w(QmkWg#lUznabvqd1!|_W+c&67Mk4Bsd%}`rRFYoCRy0b*YB7}Q z(rh&0_nNq3hKh&5p_dQ}eBy4+5%oQ%?gG!H}-JEwl;wDsV?J(~5f zVJDdXu|})juui8jFWSKImox`q>%97_?eprt&z-7%;=rln`z<@Rs9Uc*41Rr&<|g$? z=1ldnxE}oaJ;>IjQ`Uzx;MN`Jp5ec**X%?-Fh8eSHlDh4U>-%(FVP#-^RL^c-lDUs zcWmA_?lK>g%$Y*CnXhNN9j67e1nD+wcBJfrp_f*wDQB9rw=w^&zRVvjs-Z8U+fr(c;4UP)+8bKvzju7he591&3t^@W5G}oyA%$y5{$D)?sw_iPR z&PMfytvkR?JJ7dG21t#ay%`u^(#(UuTSB+0*!4Pa{9(=8MopW5^B*+()Zd7uvWbaXO?RFI1p*+?W^lSRc@HrWTh9;2k(DOw6=1K}W%u9N*jePAW3MMu-y zuylfkNVLQEi%L#XC|t1{9FtL6xL_05{B_M<^_j$}%k#Wt$0qe0DD&lKoH2U11Kjd; z&316d!L=%{>QxIX5?uBT%@%OV zH#EB@62RMY$G@JyV;NB~;S7hR`U7*+6Rl#_K2}SXgN{Nh<|>)mF|jKfJvAHGRY;-B zJNV9kVPZ}K_t!+Rh;?eQcrsZiQL&`SWFh2kVK7@e+hzlxp1zJbcV1&&HYD&-4(nMtE-Dd?uDNBD9SI6!v$?b(pX@n8 z7FVajXB=j$(}+icoc!9pPxXTuK5bR^Cyj>j5F{!-F>OikO>429>DsHf(DDs8kktcGACo^)p|6{Z;X>q+hLABhJwf zfD^xlI%?GSClhPh|N{+;SY%mR4F1DXxsl^5ab$M?^v zx173OJ($}MZhT0y>orPiHm2V?eV=;K!O4;&YUdih8UunQ8yDU$T7xl z^xDKc?(Gw`k|elGeVKAI)~F{X8Y+&wC&vv$uhki1Y-uu9?8ZcUNKxA5q_x83m3%d1 zt@&&fH<=nK1gMXe*A%$rcbYB3k3Ou?B4FOEIjmx5FRGtAyje{hUNz32JACeFjA=4z z;M2`g-{AK&Sz{+4i$zzj)C(6#XWe4R6?1%`mv1n1r%@?UG@0cRB{AoUM!l|5Vo;A# zJjSKWWX0P|IHjtgZRz@59>5m0`_$WZ?w^b^T;u8$ryhbsv3fXHtNYo=jb`AT7bDw8 zstSQ~u;Y9&nJ2BaEQsu8r6^u>DcK|zV8Lk+qa zmL;}jqwP^E&lzeScchib$c1RN*|86*e!JV-^hEo0C00x30e**mB#Vydb6L3bx7V4EYyj5C&yH~QD2!2b?vl^x7a*J zuSAv-4RcmZ#rqT&>2?}qhYk}>yhM{H!KpQ$;UhP2l)2okYaN+HL3;z`46 z4fA-WET=5J@7(SjFaYENhv<>NdeVZ9B|Yw*Q+L|7PXvu( zA+l@qcN_UafbEqr(dj9s;$hBYB-1g2gO9dq-4-P{y@LSjGo*>0m2Y%&PHVkWC^+b9 zy5mSlRXmxox_kYAF%V#6cijUAZtu_oxlj9@N1pX#U5 z1)HDj+tWp{E~NYxilu6yI$<)I%t;RK=7YgB6_LBWL_Kfrw6K0V#=1IHsh6kQR5s$k z++CxIXr)op)=tja1jjqVE{%qmh!DXP_>F26uFQk_sCMK0ouZz;pL# z4uLIy)a+k+k~vSaEV~-bZX0XgsIyya$dVML3K?^)O#AD-T)y5_%w>}&Jt$Xlo=%qy_|n1LXEo~C z7tb%f{EZ-@-gn8y(arE|8TpHqyC0v9-n4SJ(~b65?soqbDzDrPA3)tJcl!~I4_UhJ z5-*}&UpoN8zlL60oz;V{Zq-89O=Yu(7%sRH|Jr*!FijGRd(@(TKiL`-XYso~io%Fk0Vw`5`wQM2Osgw=SmyIXA z&0M)8G44c;kBE#t>GfkBs~8ch1(AX)c_gL|Y}umS_PVnEVqSYd{kF8>4do42r}Exb zJ$))_t5GoY>2y11H%q2L+8vY=bfHW0IfKKPvL=(kX1~>p29u4#pis!$U3?_rwH5Ov z2ZOn-xq!1y(`iD^*U50PT@41@xni0KjTVh-ETZ~eISnGBZeD#*+6cZ~ffo;7x$zAJ zeB!FpmIqWd9@`3zy%jl~PBuC;-eB!9u|kl6pv7ovcI9l|ocFb>c*+~$TmE1*LiegM zN{BYaezz1Ngc8;DCL*4?GB62@p~(nk%4-bP3i*oHAsGy#(FF6yG-s$!4o*?8|G}L4 z>tm;YW4Lw>JhVf53fQ}#Wz<)X9t2OlfcRHU`~5fWpS<16*Cz z#_eCYZP%KXr8EE1vD~D_7mtEVp3rUpi$Bnw1+XnT=;D7(zhijW+mQpv?010S^XSyb|OvI*}&OX&5GC^z8H!vu^z91TyKrlw88*xLh>CC(F zY`AQ}t^RDp)Fqrgl5CqYtZee88(Dua*DDvCRtyV@j(|b*B<(B~Hi+Q)2;A$hdQJh? z-wjV$<(H6y6Y)Rz5_0Y2D!Ws=8(j29_dKuY19ac$kdT6E=v~kIP zyG2B@nWQsK8$5xCQe{%@RH~Q6Ta4Y;^Wg4~tLSUVf?&v{lF$Pi6tG;fY8Y7UINuUG zHA6btt7igni*xO;4*mn#3Tlg59eCwBM6cd^xiDF3e{VtGv3dj#?n0*$KHS&MB*X({ ziKR*g7isYuy;e7l`yv&e+nsB>i(yaL=5c0P-GV_fa{aatC*!4lN2*0xksY*cc6XoY zy4hwvRccUno4+J9!}vfFy(@bgK;eww;g^v8;JJYg1A#Pp@^%6DeHDGn#NXI`0^JPm zjO(vgPh9GrbOAi}77Ye2NW$-U@e=(3Q2L>Efe}JRW3kdNrkNHcc|!y(nF*If8vI^I z$iq5uFJmPsqjzAa<;saBAG1bd;fz=E<@;f4B;KO1T%9TQtYphCcd>LN+oAJTYBZ=A z_~TicgW%jWvIw4YAUnsi`@~gyQS+jD^)JVk1}dB_BiHpZ^mHZ@3|bW)XX_nHARV{x zxw4<4VMc5u`L@=m9=mQ%z3@Fd!8H!ep=7~T_8JQQoGnWRqBXx8cZTJD z3~O{8GS|&D%sq!uN@7+bQqI`Bl#+sx3$9uW4v1*WmW$g`>2%#{4!beCFPu;1gGtip zoottp4kR$|Razn(ODMpI~AlCRyZSL?sq4UWYSEzs7{(a*2j*s96KqsBb^@2DA^ zi0Myfx~Whv%rr{bKr(Jj2RRRk@!keE=%ib`rReh;B4J0y-w8GuN37NyG)xYXsX3>$vT^jy5fqjA-Ll3Y=e2dQDe%MPd8ohmZ2A_ zc2lW-g>)sH8Mw~7k(NE`r`Wb6OWs7ftq zM%Cn2OpVTAYgv^qe^qTQ!6 zEs~3b`biz8KBelxy%z1M)22s%xxl2k0(6U9Y{jc^>9pHk?gVSOe4-ui2dQw@YYb41 zusHxj8qF{#2STKgB_io^rk}PJYxYt9}P{>XkRVb2nv=G#X zKI#XzklNqFqvEcm6CeFo1bp}0@oJq@AN%;Jh+S=5c93y}%R(a3sFkgTHl}T8e1h7(X-W}a;m3Tn0y0|`z%4F$)ydBOobGbg50MnZkg^X z9TRZ;3%YkS5=(#ni7U{h{hvG?(U=x7z@DeIfmbikZh<3#E#m6a>lc`Ys~_;-4M8CW zSjl0oQ~k0tVTkdP%a1V@R}jwxGTxpm(kNCXk!=U-W-H^$WIT5Fpn^v;PIsu=a(OLQ zx)JvTSUyyAXFXBy{Fm3LnRCG17it&A`}g2qZd4!otGVH?F4UsP$gvm+dc$8gbxW7t z8G(DMi5kJ3HzMnAj%c+&eE`Ox?KaGskkSr;wvgr?ZE%8EP>uG?FU zI7(TAMRAi3LK>LL?mF2LbF~KoYC!b!Qger-Yoa*>L(vqSi16yzd*s; z{}u+*>TlDVM$XV~=m*??6zbo6mMoSnWR!_xB?b?cy%Bf36Y%m%BUorU2Hpb2cO7oA z(M2t$*JzG_izLl{kogoe z2FJgm(Sp56?YF^a2QUu)hop8tc(H<<1ulI6dI|U5^TN`3_faVL-YsxUA{pqeZNC*= zU@WC$i5rFNAYskM zF)o-2aG8KCnV>&bcd{~WinZfKcz%cDQM-Sr2bX^hF@Y;DgFe)WYxP^ebLS&Fz`tFq zzZhJ?YPK!;|LH;9(!vif(k{L0i4V_#4QEbm321y2J+c)3sSZ(p@vVt@0gpb4>{mbW z(|PdLgYdBb+`s6w>W{wmeQ@3j=+yqijYq&$i~6JLa}RuZ{Ni^GBG0b+&gCz7@@A;@ zUS7}~0P{gT0p@Sg>(mSX@e?brs?UGxE#Ua4wCg|cjnkLT{`bCCz5JK!5#uoT47v|l zy6xA`XrKf2$a(tx2o9dFqPTkd^Xm>Y`BqKp@*~yVto2%a^XRnYD87i}z;OgQ#PEH$ zh&!`sr;%*3L4VavyF@Ntq$`bH#8MP%VQcTic@L$8jgUIt=WZKxUgn}jK^;CSeB99g+IP%Rj>Z({w*g2Iy34c{K zKl0(!RVm%eT08uSy45o@_=%N~tQi$Tj(Nz<{w1Yba3cG)kTp zjQ`nXuFiSeN;uwDGDRZp^L5}`K04cKG)DoVRX$SOcsjWAgUId!=dM2m{yQx2?W3aH zZi;eC-dq0oXhKe51=s;7_>;>32 z^j{wu@6hZ+hSiP8CCKpUdE~=7a3QV5!JRu09k_ERvJHIgSGvOt&qWiiMn36{S<_YM z&q;$uI8f&M&Uhn}^ukj)&(Z^*uU@JNOxY=UV|KW9Fk5e6BmE(w0 zt-<_4KP=$t)=lROxn0P$8^E!5YmDHzztI~&jDo+3{jGjKQ1>9ah7X>OoQZ%ss?&no zUe?VGZ~Y?r31s-Qvk?MOA3whP<|aIme)%r#3GmiFvP=CRpPw5ZKZ@8j>d!ARf{)#; z-Kkz-D}!L%3-1#OyNc`8!Qu2NGXJqMO%)_d5kdW)EO9@ zf}1Sjq?xw`VOX*t$n~J3>2&i=U$!1LlTd7U$hYyMX0Vrbd`*Ho}cuqtIiOfj~Ko*%)+w^V~qfOU87)9t_Y03HRD8 zT!xZ(gUN0d-45{R2hf9)lf(0e&H^{y4t?Mk&q4OzMIv87hrh8S*Pz41g@{%Ujo1Bq zjo{rL1c#SfcP=nk)sLCDLAgXml`xF*u^pd)(a`O-dYV+AkruQ4Fc%F4i5;|FnE6xT}Q88S0grn{#k#Ua~RN9MNuU&VP&9~Z3mIKJ&>vo~I zO}+nbu>*W-*4%~@x$15-g*Nc?UFiO?8gaV?VGsz_l6Krx36ur46Si>JAQvu+ZoCsT z1v%EmJF3N^ua`4K-DxFSiABuuULxNLTHH}@5hII(UUd-SL{rXfD~EigRCbLTdGz8V z=BX~5P2nPOc&byNXzdzYc4Ad*i(P*D00*cawGJ2k`1w_^llUUG=8aX`Vbvf^ zKEBAdTB0D2KW&Egbh*cqUtNS-yj^P#>ii;r3V*)`5 zF_{eZoMwu)iHUmmz@?wj+&<)E$h(o@p(HYgg0H8Me;*#Z7LF^a=wV#9l7(QozU?cHQF-HjI`?P`)W z`-wy?PB)twv0Gqr)ip=o@tAEsdCb7&OX#8D)&_#F8$PZe|9}92L!SbVe*oP97S4n& z{v`$U?ZcO^K%UfqD?X$#sK;M0ffGk{J@Cp`kS=)e7&L~^*IfX04YcPAc|8jTM>Y3M z9()IfPOyS$b`LN5tVV+jU%m!;H##@^gF?A?|`w+`BODeIlmLp?pgLz;1HeU zw}8uo`Xh8DoomXeu$hRvM2`b55j^j+Ddid!jtc#BFYEH~kwPy^(p@7LP4d1vSId~| z8GkXz^drGk&C!&bo+iQDTTR)~@B64G3&XNUq3eNtPPapSr*%dY69J*n8PBz8{P_IeIWqFZ;^rdu{OoH82z!FtiEN z3In>|R9s@$BlIY65u;yRaf*lUy&n10DVn?}+FCcdz5ZKD7GtJOT$~Kx?q^Vov1sMV2O!J0Yj;8D-_I3 zvV~zup~XacoSDR}0eeNXWZNE>6efzr0(r(mqY zragU2+t z&>vW{-#3mVYu6@P6vSv`55UY&>8);Vv%*y9*^976{rrQ6z(tQh!M$ZJ87awgOLvwhzDX1>^^s4XbSD*zEwuISsxa zu9+2+3VV0bJ=prFc64;n(U{wb2VBNhY><UB_PY_2KM*jx9Ibf7 zNs5V1yH3zGt2J75MLM;9tKOnY3LC3Mab@80l)O1LhCK5{{pHOG;4 z`C3ilS@|e9_}99<%Yo7(BY~$+CRT0QiIdvEv1`#IjNs0~vwxr}m(6UtKPZz~{~(j9 z7vgNMoeKFG3zs+WN;~G`lp@KMJ9MGf{OoOh#+ETrr#>tOHw#Pud`i%5S7YUQCG;m0~EIg5g)z zNQQH*GRAUsPuUU0f?m8&let1jvcmw{$ytWi=!PTUjv>@v7^$;-=vDo`$!YbsUqZh% z)O;Da3DJ%mjqzlSJ%y+4Mzs4@e2+;_RyiZ!ewXG5ZO_KpWWVJQlRd5r;d5bU#nhu+ zDLEZ9_)A_}DwiP2i9TNn<7AvmMGO8$Og5)t{%RsK8Nd_wj!}S(TsX;dp7iOY zCb0Ew+9NcfkPyJw%_Ug7gUCV<3R#H9t7dPKPSP+U9fapdx@{)RBp;>&RjP)?4Y@(I z#gJsiCiR23BkzQ85-%@Eu~4(rtMiFq6l}fVq+Y#tdWLu32T$R1a;KVyF|qMXjQ9M) zVVEj=$f!GI&(sf`$~}2+yotb;FCvG*iTe>9?MX!&VuI_|OiC0t_y{Q#9vFm5#Z$Fs zOIFSuPIOwqV8@b!IaMZ`4D=`$9n3W&LBE6WSLD*U+tbYCIB>vgyP zpO}zQNao1!sjnhM6nyt47+1P-2ZHZ_J7at>nRH-$3Dp*UrtaXx@^ll0zPr_jSO|s5 zVAh-*8WfZyPBTV2AkmE9pEYMgA)A#qS!;~LYDY_462~R7T}sS_ zi_xeY%`)vZJsG)o|C?LrDkE9559>3+3AiY>9F z*sN`Z#_)E>2Ojmo6|j>3oJsz_GyX@04?KY6v^W1420T9WJ!ID?6>r%Hr7rWW_EEyR zI6((_$|)P`p_?r?*7HkNS1>H*gS~hp&Y1)XX4WKHtT?MxKf|ZOK{n>?7dVD;7HjSX zoy@pvz5pe59a&$)l} zpoN$lCN3s2L<3SwA)QV>V{ar3)~H>|@Wpg(6x_`nrOnr=Z@+AYG%1E9%30%*{#ecw=nHAl9Z4I5u`<~* zWD^CfByxF^iE371ev2z-YQ}md*6Vh}ptqV+44F=YGU6SpoM(bKrX*YGkP&Bj0WNoZ z70_f1I@otH9Dy;cXoMyji=5SIcij98BF%BjGX6IJXu&nTVG1UD^MSKggd6^Tr&f!o z^$k1K+j?JKX;&}nuOQ&mFZE{txH4<3JELK`Q!Ho90f^k_g!&#+DDI506p=0ayzLC% zPdLhLoHB;<#fTK{W353unK89xODP#rAWq&C9}rErXgJ)Nu9e$m`xqNDM!?nP(9zOe z3)WMgIkpJCRnr|%pLjf_p15k~DnYONvxV7;)q`_S)9#sW*k;+{?is3Wij&(8Mh;79 zW2)S-RGU^)$2Ndj=BDiLDMDMQ@f69qV$m+si(@b@8ERcMsaKeK z-)|CK@GJ>SKDL6V0=o6!+20~_V9T6#(|ALv?|FO^!1imu2+sWvWCK|E84UbGoYdjv zy{GQs_k+Lzt@N73{N@D5g_k$nT4>w_$aMV2u(+M8?jR)YqVesYhQm!hzVzv@+%)@a z{ITsrAZT|YGqCzzb^d!#j@}!#_d(dHezN&ri=P|40$v9@H~EP-HEwPq;?Oie{&qSH z6a3s%^rs&k#UdVrrtztR+70TjZrnOTTv@>UMY_40?J(#2oxwWr{%;~bUHksN5~GFt%v-KC=vStCE8tmonum$#j#U%KeJR9(7^Wk~5bplxkfnZQ+}O zGb=V5awU|h3_P-_#xP!EJSPlz&Vh4YJnkLP9$P+N+^Q8=ExCs z(&($lnTUfIvzZ20uQr4E7#8QdSg(+i`pIy#s@STrs7G#_OJsvmGTx9|v?Y>U#)z+l z;8`=e7Py|(?FW}Vk1!Lf^H1%~U~d+tt@iMzfp=teJHeLUAfKM?gf}Ig@!iYPy;;zV zVAC&<;!G+CXgr3@ZU6+Q>;Ydv;Hnre$Cr5B-l@JG1gn(LBSHdQvUVJSw$imRUcV2b z8>`k#u^Z)$p}2|ecX14dXJs4LCrWr6g20mj3m!KnUHv*G28$_~^bzH3HtdE+n>PR2 z<+%H14fmG z6v{-W;#enSvrIg|)n-o*ZkR(?{6RgqUDmC>EpOGX2ir%!AM_UW;FX+?oH%r2eEa3w zwPTm>H5N2=wGk}*1RAWTHlmbzdFv23yRJKbm91L#AD02$BlCs7z9YG6+{S+7CpM!W zpAFvdEnCosS7!XD^Dxjlp6uXO^t8!j(-GRwDmFiFaKLP+<2Jh&yUTTrX*t*gf6^*rqQx`~Iq zY}>&=shb#@n*vEE?aki@0m@|hxg1Th#8b}xhNaGL3 zayrpTk0SA_jl*CsdgV&Nz|@pAd(L3DQbYIN#-u52tMIu29c9}6=DGbS_% z59{bOz4=N{iI%LrVvOZIcClbB4a!U+5wA5OMZZ||2ThrBfFZN6PAMLwLOw?B`C{;_ zJM!gbNXM<9zkn_)(%}Q|K@(#G3U1tio;4}q9m)3wxaSL|vj3Ojo+mO4@5eP0_YC~{ z1k_=-9n)e-{c5$O__H|O zz^zKiZm!mvQGe0jF$EzeHz`O4SHkQImFg)Q#L0G|*$$PKz|fD_$30Y7rRS z8hAxAPxSGzO1Ve=K_M+g`q=nQ`jp3TBa5NPLi&k*P|8{$K$5H4Z1tT*Rqn_wWm zT^3W_!N6QgIv`eHw(2_ltF*Bsqi(h{lu)i|@Fhr)9>|0lzVYhoRRW zWGZ~vPum$otY_@vRop?PC7DVa`7n`D;!UjSsn%Zq%&rHY(rdRa+uhZ{tT*yZM#Uz( z!G&|$k56{i6+b~XkFE}_vcb!~$%;z?P4FD}vKHN>Mhff3pHv?d$G-bKc;f)rvfnbV z>#=RF1m?549ZP$7b)@trll$KzwltIVO&MDn2^Vje#_JGZ+q4I#ikXH*AZ^U<$`e8z zYCO{*kja}o9s{3}+G)~Rj#`V&s5Rm#mMI7rp|Uk9DOwUHmNEtF9(P7^;{Be}V7Epx zR70_u8(BWv8aWlX`e#k+ENt|jS#!gWY}a0ltXg1e9lCjSb`S*6zr6umeoUuV7apys z7yV8Lp8Nsy1n&4B$Ok6zz1M`$W^?nV5239Hs#C8}R>Ej%U)c<9*t=Yvo2QaWgmTnb zB^#A!zq?bl$9idkh(rf&mP}D5TqXk@u3n~7VXW*X18$+=$=MA9*+VA;6WkQ(dgH#_*cmilsXKL|EubTAS~Ve~Eqs#2pUL{)GTb*(%%ig6H)o(P1f_f3 z5Z`FbS3-Q#yIF`&t3L3#w?Jx$&%ciVw^Fc<@ixEq6pd}stU8VzV%Qd&XPwE4Cs=D` zgJFv9$O?qWSY4TZ+#I(#lSw5^2i*oQUt~&RJ?^q%LNRSgij2FIjhng^o6lgw{f&6V z>h0R2nO;w}kGL7e6a(smx92AMddc_M+EjHf{oUvOY<9W(4$t@!Mj^K?3B%#+=1)}U zPQPy&k{tCl{@6C~P9Em9M&=Ye0 zFr_c}GA-Jhv~@ge_AU zDc|3|+&?{dR@54&opkX%FCVt2x`Yt6W}9}|!Vd!%gMVn=6yd4*4nVKK|ud<6qk#7;o}H@RPS8dxlq@zw&9=&>BB{ zgLpTb*r7gxT!v16`H48F~E!-d~QV&sv8tDu4fLzjpp%n5}r_t-8(Q zd>VMFg05F%r)a^sZ_~xVtv^EcsE>Sax;uwZm@gvmM|S)WCVnzT1MFeC6Ef!9 zY=>=F3Y}`UWEl{!*2zp!Nf}o3+Z`C~ZuRIsBihW-gs+`qZKR{#arzsSD};6Vdcz^- z#4O2L`+PE%?h;0Luv{Aneaj}ec<;U##<$OZ9D(Ku7@vfR;m$4%V)X<~TU zmEQmgl+FU)e>=Kq_~Q4Ga}o8f;v{&ho+iv9NP%1;VWJ=(LX*h$W6qM$NVMv150SJe z^=4jRO|4!w((GnU{$kwMvDS^g@*os#^qN_=V7In2CdO1%Xp4!C`BT-J!g9r!(KJ#d zc6GD!l$Cs-`iih~oIqGhH$nzyp3Yc@zxou?A9n{lQJLiY=SNo?)kSeM8q-K*8D5OT z4YNq>?2L2T$-(PUYEgY#dHb@*6kKH%9xPuu-3?#*8L~85bZ_pQ&}f%I3D28si^~$a z#i>d1cKLQjuu5_!WH(rCenX#ew3@7m;&TaFcI?Ca`eJs`+%bY z=7EzbT!a6D zL*UGIJcba@|j~oC8CfEOcFb(Ao0W(`Fpf zBq3p5(VE9hc~{2i&n!b)nlrQwz=-9!qHWcad+ z$1@3+-y)35Ou<#ZhsE1#*T$QX+2vhuHN2T}pgdxP+0Uy+D)&vD_x$KYz_(3y(HFF+ zd8(wUg1v8dCr!?nBjHI0U5?s7FvUBZtBJ{E(&qPgT8)G&n2eQUT*vNL5>!Zm+<#e< zSZ#%BxinvEdGdjrLc-GFR+r}#r5>0Z{AUn>`7uUjf>c|(#60?0D(Ex%_e z5Air2sVj6KsR%6I9gHa^Nbo1Ju>YB$(u6hVqIKx|mJR!|IbV}gj7ZR6K$yX5PNi80 zYB_p2OD?X8Mcvhc2;-MdL9c@o_W!4-A?MgiJoq(O$5sB}EJgh>c!Ge*>FCKab9yvb0J#7a6s9doOP%)ONTF_Z?#4yY#hJ7;KW1JzO z+poyhZY$d3-OVVSj47NoWA8)Ka5V|8sNC7c>+B38RX3AlUaHl4)ySNe*yWSAKngWeaA1u zr#pv_@6cE#x3ABQHo^#umezkYMnDT5y;-wkc)me%?QD!@kb%*3%zy!->lrXW3(ork zx;UZcdKQNS6Ce8n%=-SYuU98Au!EH5+DY$*AEPwf>}wBRr@44`{n+)IJ0_!3{TgJB zd*lxN_s0V+KY*SEmTuIr)6dWF2cwbt=|?qhQNN2D!FeCkY@86+R^rBjpHe!ZW|9o> zc0;FY!#jMgR`vA!SR<8fwK+%JY|PnBL7T!?ASq1Sk#DyHX{F|}bSjNZ7LV0ywNA=p zNF}2UkHg!x^ZbONuHYuQ01 zM2KxiF%vA?z2z2NPLi=$t`=jWxrza=^d-?>?zKdz;lSw#A1!s%r$4k8T;@ad!w-7U zxpk*V%?_+Z<4QcZ<(-gOa!j9g*;n-=X33#1ivDmFBg~koW|&B#f4^}!&!P=qmh$B$?&i6kf%aM(K`a+4$7#Y&WCTA1<_JB2zknW? zWbGRwa>npmJbFH)nFja6=rh=V0!B)%i9!@;j&6|YY$akeS#5+LH`z^9-qdNQvV{hb z88jk{7asJR9*-#%cF>ri6f<&EyDn37z0t~&e4QBZUXPh>v>QFnYECs6iOIqun+Fz# zFgACFUcF|~2rh4-IuQLw-7eL6Y$260vsuArFMDdKL_d)yih+VduzCGm%F1~L*+R-u zvYY*o!ix&pYJometdu2pJYrAe!+k^EQ;t~;f)EzHiFz~H7BLeM73=+;irjs8_)HW1 z8Txu<>)_URp}L{5hptD4w-3;tBB)$bpMT3<6vx2UAM4EfVF{^RYl}sZZ^F_T8FCOU z-@~Gw=Woz0fTx~>g<*69*4GVievl{njvUQe%Vvkz{6`%kNJhi3Zr0He z@amNnqz1TpC;Hj(8>h_PFoIwHndS&wH1ogL5}QWf3I}KO-%%TI#I>h0hDf$W#QAV~ zVDP2AQCJ?=mu1rLa@R4*8h5&_RmrA=m}wgFd+{%PMbGe@46zp#A`Q=2-iU zKLB363Y{B%_#Nmv%@DF1#37Ofj>xEW$MCi*(Ka%?;~MlA$ndYUYbLLIRS~d3hR>B6)MrmQGkP7whhqIuW6hBr-mOuUr{~dr_qovE(Xk zs#+|K5bLWepCKej18GLa_1SY12^PVDf6*NPH@qJ;P6u_$g0d{*^d6E_;)k_o(0!T+ zS!jQz<){xtCYB9W(+0xhuV*s6g%V9+L($9|SQv`&vo*-Q=uM=_02YmsoDu$s0Vi*^ zcHrN5AbcSk?dZ&6TNKJozhD zUI8q04w-bj&j*uA(OH(1kil!uTf^28-*YSB-oW6pMG~!+zkp|(5lQhYl)IUMf3+nN z4L5nE;P8aaOh)VrDMPm_51f3&nivQY(Qd!axTW8)9Q-w$RIeRG$RsnRM4IvX;I>8V zsLc1<;E+pmwtDQ>>%lkA*PJ)zEF13qAR5xIF!K!|o8P;1!>c}tei|9R_pedA23*KM zMw0MHvl2NoyPLmkLGBBLz%Zs#{q_G`tJbYytFwo&pQ&Q5JmsNpzq3 z%BA`@DNw>c5%4=V&Y#2-4&A}A;NQ7%ZVZkX!n=Q+8=m!hbOSQ%{2YB~ZsTg`ejzKlj%u9%mEA3C`zykK?N%cj#yb%O6Ebr7?Lx!Zm$DLQMv

0gd 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 From d2d7dd0561e9ccfbf68caccafffa114a45b29fc0 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:49:07 +0200 Subject: [PATCH 35/90] Revert ":construction: webhook" This reverts commit df2b83ac158be1e7233d8edce59033c15c193599. --- api/assets/schemas.json | 255 +----------------- api/client_test/index.html | 2 +- api/src/middlewares/Authentication.ts | 4 +- api/src/middlewares/ErrorHandler.ts | 7 +- api/src/routes/discoverable-guilds.ts | 2 +- .../routes/guilds/#guild_id/integrations.ts | 10 - api/src/routes/template.ts.disabled | 2 +- api/src/routes/webhooks/#webhook_id/index.ts | 89 ------ api/src/util/route.ts | 8 +- util/src/entities/Webhook.ts | 6 +- util/src/util/Regex.ts | 2 +- 11 files changed, 18 insertions(+), 369 deletions(-) delete mode 100644 api/src/routes/guilds/#guild_id/integrations.ts delete mode 100644 api/src/routes/webhooks/#webhook_id/index.ts diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 88558cfa..9c34f968 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -1770,6 +1770,10 @@ } }, "additionalProperties": false, + "required": [ + "avatar", + "name" + ], "definitions": { "ChannelType": { "enum": [ @@ -7442,256 +7446,5 @@ } }, "$schema": "http://json-schema.org/draft-07/schema#" - }, - "WebhookModifySchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "avatar": { - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, - "ChannelPermissionOverwriteType": { - "enum": [ - 0, - 1 - ], - "type": "number" - }, - "Embed": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "type": { - "enum": [ - "article", - "gifv", - "image", - "link", - "rich", - "video" - ], - "type": "string" - }, - "description": { - "type": "string" - }, - "url": { - "type": "string" - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "color": { - "type": "integer" - }, - "footer": { - "type": "object", - "properties": { - "text": { - "type": "string" - }, - "icon_url": { - "type": "string" - }, - "proxy_icon_url": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "text" - ] - }, - "image": { - "$ref": "#/definitions/EmbedImage" - }, - "thumbnail": { - "$ref": "#/definitions/EmbedImage" - }, - "video": { - "$ref": "#/definitions/EmbedImage" - }, - "provider": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "additionalProperties": false - }, - "author": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "url": { - "type": "string" - }, - "icon_url": { - "type": "string" - }, - "proxy_icon_url": { - "type": "string" - } - }, - "additionalProperties": false - }, - "fields": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - }, - "inline": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "name", - "value" - ] - } - } - }, - "additionalProperties": false - }, - "EmbedImage": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "proxy_url": { - "type": "string" - }, - "height": { - "type": "integer" - }, - "width": { - "type": "integer" - } - }, - "additionalProperties": false - }, - "ChannelModifySchema": { - "type": "object", - "properties": { - "name": { - "maxLength": 100, - "type": "string" - }, - "type": { - "$ref": "#/definitions/ChannelType" - }, - "topic": { - "type": "string" - }, - "bitrate": { - "type": "integer" - }, - "user_limit": { - "type": "integer" - }, - "rate_limit_per_user": { - "type": "integer" - }, - "position": { - "type": "integer" - }, - "permission_overwrites": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "$ref": "#/definitions/ChannelPermissionOverwriteType" - }, - "allow": { - "type": "bigint" - }, - "deny": { - "type": "bigint" - } - }, - "additionalProperties": false, - "required": [ - "allow", - "deny", - "id", - "type" - ] - } - }, - "parent_id": { - "type": "string" - }, - "id": { - "type": "string" - }, - "nsfw": { - "type": "boolean" - }, - "rtc_region": { - "type": "string" - }, - "default_auto_archive_duration": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" - } - }, - "$schema": "http://json-schema.org/draft-07/schema#" } } \ No newline at end of file diff --git a/api/client_test/index.html b/api/client_test/index.html index 335b477c..ac66df06 100644 --- a/api/client_test/index.html +++ b/api/client_test/index.html @@ -11,7 +11,7 @@ window.__OVERLAY__ = /overlay/.test(location.pathname); window.__BILLING_STANDALONE__ = /^\/billing/.test(location.pathname); window.GLOBAL_ENV = { - API_ENDPOINT: `//${location.host}/api`, + API_ENDPOINT: "/api", API_VERSION: 9, GATEWAY_ENDPOINT: `${location.protocol === "https:" ? "wss://" : "ws://"}${location.hostname}:3002`, WEBAPP_ENDPOINT: "", diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index 32307f42..a300c786 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -5,11 +5,11 @@ import { checkToken, Config } from "@fosscord/util"; export const NO_AUTHORIZATION_ROUTES = [ "/auth/login", "/auth/register", + "/webhooks/", "/ping", "/gateway", "/experiments", - /\/guilds\/\d+\/widget\.(json|png)/, - /\/webhooks\/\d+\/\w+/ // only exclude webhook calls with webhook token + /\/guilds\/\d+\/widget\.(json|png)/ ]; export const API_PREFIX = /^\/api(\/v\d+)?/; diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index 338da8d5..d288f3fb 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,10 +1,9 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; +import { EntityNotFoundError } from "typeorm"; import { FieldError } from "@fosscord/api"; import { ApiError } from "@fosscord/util"; -const EntityNotFoundErrorRegex = /"(\w+)"/; - export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { if (!error) return next(); @@ -19,8 +18,8 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne code = error.code; message = error.message; httpcode = error.httpStatus; - } else if (error.name === "EntityNotFoundError") { - message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; + } else if (error instanceof EntityNotFoundError) { + message = `${(error as any).stringifyTarget || "Item"} could not be found`; code = 404; } else if (error instanceof FieldError) { code = Number(error.code); diff --git a/api/src/routes/discoverable-guilds.ts b/api/src/routes/discoverable-guilds.ts index 71789123..f667eb2a 100644 --- a/api/src/routes/discoverable-guilds.ts +++ b/api/src/routes/discoverable-guilds.ts @@ -10,7 +10,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { // ! this only works using SQL querys // TODO: implement this with default typeorm query // const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) }); - const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit) || 50) }); + const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit)) }); res.send({ guilds: guilds }); }); diff --git a/api/src/routes/guilds/#guild_id/integrations.ts b/api/src/routes/guilds/#guild_id/integrations.ts deleted file mode 100644 index f6b8e99d..00000000 --- a/api/src/routes/guilds/#guild_id/integrations.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { route } from "@fosscord/api"; -import { Router, Request, Response } from "express"; -const router = Router(); - -router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - // TODO: integrations (followed channels, youtube, twitch) - res.send([]); -}); - -export default router; diff --git a/api/src/routes/template.ts.disabled b/api/src/routes/template.ts.disabled index 524e981b..ad785f10 100644 --- a/api/src/routes/template.ts.disabled +++ b/api/src/routes/template.ts.disabled @@ -4,7 +4,7 @@ import { Router, Request, Response } from "express"; const router = Router(); router.get("/", async (req: Request, res: Response) => { - res.json({}); + res.send({}); }); export default router; diff --git a/api/src/routes/webhooks/#webhook_id/index.ts b/api/src/routes/webhooks/#webhook_id/index.ts deleted file mode 100644 index e9b40ebf..00000000 --- a/api/src/routes/webhooks/#webhook_id/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Channel, Config, emitEvent, JWTOptions, Webhook, WebhooksUpdateEvent } from "@fosscord/util"; -import { route, Authentication, handleFile } from "@fosscord/api"; -import { Router, Request, Response, NextFunction } from "express"; -import jwt from "jsonwebtoken"; -import { HTTPError } from "lambert-server"; -const router = Router(); - -export interface WebhookModifySchema { - name?: string; - avatar?: string; - // channel_id?: string; // TODO -} - -function validateWebhookToken(req: Request, res: Response, next: NextFunction) { - const { jwtSecret } = Config.get().security; - - jwt.verify(req.params.token, jwtSecret, JWTOptions, async (err, decoded: any) => { - if (err) return next(new HTTPError("Invalid Token", 401)); - next(); - }); -} - -router.get("/", route({}), async (req: Request, res: Response) => { - res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); -}); - -router.get("/:token", route({}), validateWebhookToken, async (req: Request, res: Response) => { - res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); -}); - -router.patch("/", route({ body: "WebhookModifySchema", permission: "MANAGE_WEBHOOKS" }), (req: Request, res: Response) => { - return updateWebhook(req, res); -}); - -router.patch("/:token", route({ body: "WebhookModifySchema" }), validateWebhookToken, (req: Request, res: Response) => { - return updateWebhook(req, res); -}); - -async function updateWebhook(req: Request, res: Response) { - const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); - if (req.body.channel_id) await Channel.findOneOrFail({ id: req.body.channel_id, guild_id: webhook.guild_id }); - - webhook.assign({ - ...req.body, - avatar: await handleFile(`/icons/${req.params.webhook_id}`, req.body.avatar) - }); - - await Promise.all([ - emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id: webhook.channel_id, - data: { - channel_id: webhook.channel_id, - guild_id: webhook.guild_id - } - } as WebhooksUpdateEvent), - webhook.save() - ]); - - res.json(webhook); -} - -router.delete("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - return deleteWebhook(req, res); -}); - -router.delete("/:token", route({}), validateWebhookToken, (req: Request, res: Response) => { - return deleteWebhook(req, res); -}); - -async function deleteWebhook(req: Request, res: Response) { - const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); - - await Promise.all([ - emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id: webhook.channel_id, - data: { - channel_id: webhook.channel_id, - guild_id: webhook.guild_id - } - } as WebhooksUpdateEvent), - webhook.remove() - ]); - - res.sendStatus(204); -} - -export default router; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 1e2beb5d..6cd8f622 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -1,4 +1,4 @@ -import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions, Webhook } from "@fosscord/util"; +import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; import fs from "fs"; import path from "path"; @@ -54,13 +54,9 @@ export function route(opts: RouteOptions) { return async (req: Request, res: Response, next: NextFunction) => { if (opts.permission) { const required = new Permissions(opts.permission); - if (req.params.webhook_id) { - const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); - req.params.channel_id = webhook.channel_id; - req.params.guild_id = webhook.guild_id; - } const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); + // bitfield comparison: check if user lacks certain permission if (!permission.has(required)) { throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); } diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts index d0d98804..12ba0d08 100644 --- a/util/src/entities/Webhook.ts +++ b/util/src/entities/Webhook.ts @@ -18,13 +18,13 @@ export class Webhook extends BaseClass { @Column({ type: "simple-enum", enum: WebhookType }) type: WebhookType; - @Column() - name: string; + @Column({ nullable: true }) + name?: string; @Column({ nullable: true }) avatar?: string; - @Column({ nullable: true, select: false }) + @Column({ nullable: true }) token?: string; @Column({ nullable: true }) diff --git a/util/src/util/Regex.ts b/util/src/util/Regex.ts index b5d23b7f..83fc9fe8 100644 --- a/util/src/util/Regex.ts +++ b/util/src/util/Regex.ts @@ -1,5 +1,5 @@ export const DOUBLE_WHITE_SPACE = /\s\s+/g; -export const SPECIAL_CHAR = /[@#\r\n\t\f\v]/gu; +export const SPECIAL_CHAR = /[@#`:\r\n\t\f\v\p{C}]/gu; export const CHANNEL_MENTION = /<#(\d+)>/g; export const USER_MENTION = /<@!?(\d+)>/g; export const ROLE_MENTION = /<@&(\d+)>/g; From 173f03c596c2df23b81d3c7601988f0e090e6e3a Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:49:12 +0200 Subject: [PATCH 36/90] Revert "fix #129" This reverts commit 8f862f0e5dba3985b4f38406fc19b5c5350324b9. --- api/src/routes/guilds/#guild_id/webhooks.ts | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 api/src/routes/guilds/#guild_id/webhooks.ts diff --git a/api/src/routes/guilds/#guild_id/webhooks.ts b/api/src/routes/guilds/#guild_id/webhooks.ts deleted file mode 100644 index a9dd164a..00000000 --- a/api/src/routes/guilds/#guild_id/webhooks.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; -import { Webhook } from "@fosscord/util"; - -const router: Router = Router(); - -router.get("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - const webhooks = await Webhook.find({ - where: { guild_id: req.params.guild_id }, - select: ["application", "avatar", "channel_id", "guild_id", "id", "token", "type", "user", "source_guild", "name"], - relations: ["user", "application", "source_guild"] - }); - - return res.json(webhooks); -}); - -export default router; From 60ee23489125cd48903501fd03e410affde6b7b2 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:49:17 +0200 Subject: [PATCH 37/90] Revert "fix #128" This reverts commit 8893fd16d90ad599538ccb62f8f77711aa891bbd. --- .../routes/channels/#channel_id/webhooks.ts | 61 +++---------------- 1 file changed, 9 insertions(+), 52 deletions(-) diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 9c8df3fb..7b894455 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,21 +1,9 @@ import { Router, Response, Request } from "express"; -import { handleFile, route } from "@fosscord/api"; -import { - Channel, - Config, - emitEvent, - getPermission, - Snowflake, - trimSpecial, - User, - Webhook, - WebhooksUpdateEvent, - WebhookType -} from "@fosscord/util"; +import { route } from "@fosscord/api"; +import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; import { DiscordApiErrors } from "@fosscord/util"; -import { generateToken } from "../../auth/login"; const router: Router = Router(); // TODO: webhooks @@ -23,26 +11,13 @@ export interface WebhookCreateSchema { /** * @maxLength 80 */ - name?: string; - avatar?: string; + name: string; + avatar: string; } -router.get("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req, res) => { - const webhooks = await Webhook.find({ - where: { channel_id: req.params.channel_id }, - select: ["application", "avatar", "channel_id", "guild_id", "id", "token", "type", "user", "source_guild", "name"], - relations: ["user", "application", "source_guild"] - }); - - res.json(webhooks); -}); - // TODO: use Image Data Type for avatar instead of String router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - var { avatar, name } = req.body as WebhookCreateSchema; - name = trimSpecial(name) || "Webhook"; - if (name === "clyde") throw new HTTPError("Invalid name", 400); - const { channel_id } = req.params; + const channel_id = req.params.channel_id; const channel = await Channel.findOneOrFail({ id: channel_id }); isTextChannel(channel.type); @@ -52,29 +27,11 @@ router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOO const { maxWebhooks } = Config.get().limits.channel; if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - const id = Snowflake.generate(); + var { avatar, name } = req.body as { name: string; avatar?: string }; + name = trimSpecial(name); + if (name === "clyde") throw new HTTPError("Invalid name", 400); + // TODO: save webhook in database and send response - const webhook = await new Webhook({ - id, - name, - avatar: await handleFile(`/icons/${id}`, avatar), - user: await User.getPublicUser(req.user_id), - guild_id: channel.guild_id, - channel_id, - token: await generateToken(id), - type: WebhookType.Incoming - }).save(); - - await emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: channel.guild_id - } - } as WebhooksUpdateEvent); - - return res.json(webhook); }); export default router; From b446a3110b86ad48ca6af2533c55831d47e7407d Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:55:50 +0200 Subject: [PATCH 38/90] :arrow_up: update package --- api/package-lock.json | Bin 807341 -> 807339 bytes api/package.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index e347d85ec6d043b69145ce80503f843b91ccbf5d..79889f8ac6d1a7237ccad8f1211fd07922509af8 100644 GIT binary patch delta 297 zcmZ3x*l_h?!wqtbtQLA^dPbX77=ziSpZ~xny1AFV^XYUEO-B7m54adDrcZQdQ4Y1V zFfb3w33p5nF*P$O57BnXO05hv^>_0P$jYs%2+L0@b8*Y{bIgd;Hm!0ob2Q4SG|qA< zFe^^aORID#$?*)-HutbJ&rjBOad)n&N-MQ2$||><&M3yD(EQ?U`-``XK+FWh%s|Wn q#H`z2yk+BUppP>O&vD93e(|=c-8G*bh&h0m6NtICyXJFeJOTh`3~N;Y delta 312 zcmZ3z*l_J)!wqtbY;hKPCVB>&l^KKCrl0x1Cbqeoz4Phx&uz^5lmFf4VziiCxL7$f z*w@QH#U;SZr!XYj#UQlMqR=N(JKeWDxWphcH^L`4%)r8+&@HUo%elbAGStjBNjo^y z-NP}$*TCB^DcLC?y|l6 Date: Thu, 16 Sep 2021 21:30:05 +0200 Subject: [PATCH 39/90] Gateway permission check fix --- gateway/src/listener/listener.ts | 11 ++++++++--- util/src/util/Event.ts | 1 + util/src/util/Permissions.ts | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index ef3dd890..16803639 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -116,7 +116,7 @@ async function consume(this: WebSocket, opts: EventOpts) { .has("VIEW_CHANNEL") ) return; - // TODO: check if user has permission to channel + break; case "GUILD_CREATE": this.events[id] = await listenEvent(id, consumer, listenOpts); break; @@ -193,11 +193,16 @@ async function consume(this: WebSocket, opts: EventOpts) { break; } - Send(this, { + let aa = { op: OPCODES.Dispatch, t: event, d: data, s: this.sequence++, - }); + } + + //TODO remove before PR merge + console.log(aa) + + Send(this, aa); opts.acknowledge?.(); } diff --git a/util/src/util/Event.ts b/util/src/util/Event.ts index 765e5fc7..ae296df9 100644 --- a/util/src/util/Event.ts +++ b/util/src/util/Event.ts @@ -5,6 +5,7 @@ import { EVENT, Event } from "../interfaces"; const events = new EventEmitter(); export async function emitEvent(payload: Omit) { + console.log(payload) //TODO remove before merge const id = (payload.channel_id || payload.user_id || payload.guild_id) as string; if (!id) return console.error("event doesn't contain any id", payload); diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts index 9d87253a..44852f1e 100644 --- a/util/src/util/Permissions.ts +++ b/util/src/util/Permissions.ts @@ -92,6 +92,7 @@ export class Permissions extends BitField { } overwriteChannel(overwrites: ChannelPermissionOverwrite[]) { + if (!overwrites) return this 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; From 0506d40aa13ade16b627808f3ad340cee2a8e888 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Thu, 16 Sep 2021 21:31:39 +0200 Subject: [PATCH 40/90] Dummy sticker-packs routes --- api/src/routes/sticker-packs/#id/index.ts | 18 ++++++++++++++++++ api/src/routes/sticker-packs/index.ts | 10 ++++++++++ 2 files changed, 28 insertions(+) create mode 100644 api/src/routes/sticker-packs/#id/index.ts create mode 100644 api/src/routes/sticker-packs/index.ts diff --git a/api/src/routes/sticker-packs/#id/index.ts b/api/src/routes/sticker-packs/#id/index.ts new file mode 100644 index 00000000..2344a48f --- /dev/null +++ b/api/src/routes/sticker-packs/#id/index.ts @@ -0,0 +1,18 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json({ + id: "", + stickers: [], + name: "", + sku_id: "", + cover_sticker_id: "", + description: "", + banner_asset_id: "" + }).status(200); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/sticker-packs/index.ts b/api/src/routes/sticker-packs/index.ts new file mode 100644 index 00000000..6c4e46d8 --- /dev/null +++ b/api/src/routes/sticker-packs/index.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json({ sticker_packs: [] }).status(200); +}); + +export default router; \ No newline at end of file From d630f09f80b772c3943058405a6ef0edc48b4cba Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Thu, 16 Sep 2021 21:33:36 +0200 Subject: [PATCH 41/90] Implemented DMs and group DMs --- api/src/routes/channels/#channel_id/index.ts | 28 ++++-- .../#channel_id/messages/#message_id/ack.ts | 2 +- .../channels/#channel_id/messages/index.ts | 46 ++++++++-- .../routes/channels/#channel_id/recipients.ts | 56 +++++++++++- api/src/routes/users/#id/profile.ts | 1 + api/src/routes/users/@me/channels.ts | 43 +++------ api/src/routes/users/@me/relationships.ts | 19 +++- gateway/src/opcodes/Identify.ts | 38 ++++---- gateway/src/opcodes/index.ts | 1 + util/src/dtos/DmChannelDTO.ts | 35 ++++++++ util/src/dtos/UserDTO.ts | 17 ++++ util/src/dtos/index.ts | 2 + util/src/entities/Channel.ts | 15 ++-- util/src/entities/Recipient.ts | 3 + util/src/entities/User.ts | 2 +- util/src/index.ts | 2 + util/src/interfaces/Event.ts | 22 +++++ util/src/services/ChannelService.ts | 88 +++++++++++++++++++ util/src/services/index.ts | 1 + 19 files changed, 342 insertions(+), 79 deletions(-) create mode 100644 util/src/dtos/DmChannelDTO.ts create mode 100644 util/src/dtos/UserDTO.ts create mode 100644 util/src/dtos/index.ts create mode 100644 util/src/services/ChannelService.ts create mode 100644 util/src/services/index.ts diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 02ac9884..e836622b 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,6 +1,7 @@ -import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, ChannelType, ChannelPermissionOverwriteType } from "@fosscord/util"; -import { Router, Response, Request } from "express"; +import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelService, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; + const router: Router = Router(); // TODO: delete channel // TODO: Get channel @@ -16,14 +17,27 @@ router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); - // TODO: Dm channel "close" not delete - const data = channel; + if (channel.type === ChannelType.DM) { + const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } }) + recipient.closed = true + await Promise.all([ + recipient.save(), + emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent) + ]); - await Promise.all([emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent), Channel.delete({ id: channel_id })]); + } else if (channel.type === ChannelType.GROUP_DM) { + await ChannelService.removeRecipientFromChannel(channel, req.user_id) + } else { + //TODO messages in this channel should be deleted before deleting the channel + await Promise.all([ + Channel.delete({ id: channel_id }), + emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) + ]); + } - res.send(data); + res.send(channel); }); export interface ChannelModifySchema { diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index 97d1d19e..786e4581 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -26,7 +26,7 @@ router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Reques data: { channel_id, message_id, - version: 496 + version: 3763 } } as MessageAckEvent); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index ec93649e..bb610a6a 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,9 +1,8 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { route } from "@fosscord/api"; +import { handleMessage, postHandleMessage, route } from "@fosscord/api"; import multer from "multer"; -import { sendMessage } from "@fosscord/api"; import { uploadFile } from "@fosscord/api"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; @@ -62,9 +61,9 @@ router.get("/", async (req: Request, res: Response) => { if (!channel) throw new HTTPError("Channel not found", 404); isTextChannel(channel.type); - const around = `${req.query.around}`; - const before = `${req.query.before}`; - const after = `${req.query.after}`; + const around = req.query.around ? `${req.query.around}` : undefined; + const before = req.query.before ? `${req.query.before}` : undefined; + const after = req.query.after ? `${req.query.after}` : undefined; const limit = Number(req.query.limit) || 50; if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100"); @@ -151,10 +150,12 @@ router.post( return res.status(400).json(error); } } + //TODO querying the DB at every message post should be avoided, caching maybe? + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] }) const embeds = []; if (body.embed) embeds.push(body.embed); - const data = await sendMessage({ + let message = await handleMessage({ ...body, type: 0, pinned: false, @@ -162,9 +163,36 @@ router.post( embeds, channel_id, attachments, - edited_timestamp: undefined + edited_timestamp: undefined, + timestamp: new Date() }); - return res.json(data); + message = await message.save() + + await channel.assign({ last_message_id: message.id }).save() + + if (channel.isDm()) { + const channel_dto = await DmChannelDTO.from(channel) + + for (let recipient of channel.recipients!) { + if (recipient.closed) { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id + }) + } + } + + await Promise.all(channel.recipients!.map(async r => { + r.closed = false; + return await r.save() + })); + } + + await emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent) + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error + + return res.json(message); } ); diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts index ea6bc563..d88b38f3 100644 --- a/api/src/routes/channels/#channel_id/recipients.ts +++ b/api/src/routes/channels/#channel_id/recipients.ts @@ -1,5 +1,57 @@ -import { Router, Response, Request } from "express"; +import { Request, Response, Router } from "express"; +import { Channel, ChannelRecipientAddEvent, ChannelService, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util"; + const router: Router = Router(); -// TODO: + +router.put("/:user_id", async (req: Request, res: Response) => { + const { channel_id, user_id } = req.params; + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); + + if (channel.type !== ChannelType.GROUP_DM) { + const recipients = [ + ...channel.recipients!.map(r => r.user_id), + user_id + ].unique() + + const new_channel = await ChannelService.createDMChannel(recipients, req.user_id) + return res.status(201).json(new_channel); + } else { + if (channel.recipients!.map(r => r.user_id).includes(user_id)) { + throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error? + } + + channel.recipients!.push(new Recipient({ channel_id: channel_id, user_id: user_id })); + await channel.save() + + await emitEvent({ + event: "CHANNEL_CREATE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + + await emitEvent({ + event: "CHANNEL_RECIPIENT_ADD", data: { + channel_id: channel_id, + user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) + }, channel_id: channel_id + } as ChannelRecipientAddEvent); + return res.sendStatus(204); + } +}); + +router.delete("/:user_id", async (req: Request, res: Response) => { + const { channel_id, user_id } = req.params; + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); + if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) + throw DiscordApiErrors.MISSING_PERMISSIONS + + if (!channel.recipients!.map(r => r.user_id).includes(user_id)) { + throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error? + } + + await ChannelService.removeRecipientFromChannel(channel, user_id) + + return res.sendStatus(204); +}); export default router; diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index d60c4f86..06d5c38c 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -19,6 +19,7 @@ router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req connected_accounts: user.connected_accounts, premium_guild_since: null, // TODO premium_since: null, // TODO + mutual_guilds: [], // TODO {id: "", nick: null} when ?with_mutual_guilds=true user: { username: user.username, discriminator: user.discriminator, diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index da33f204..bd7af18c 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,15 +1,21 @@ -import { Router, Request, Response } from "express"; -import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent, Recipient } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; +import { Request, Response, Router } from "express"; +import { PublicUserProjection, Recipient, User, ChannelService } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { In } from "typeorm"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel"] }); + const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel", "user"] }); - res.json(recipients.map((x) => x.channel)); + //TODO check if this is right + const aa = await Promise.all(recipients.map(async (x) => { + return { + ...(x.channel), + recipients: await User.findOneOrFail({ where: { id: x.user_id }, select: PublicUserProjection }), + } + })) + + res.json(aa); }); export interface DmChannelCreateSchema { @@ -19,30 +25,7 @@ export interface DmChannelCreateSchema { router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; - - body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); - - const recipients = await User.find({ where: body.recipients.map((x) => ({ id: x })) }); - - if (recipients.length !== body.recipients.length) { - throw new HTTPError("Recipient/s not found"); - } - - const type = body.recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; - const name = trimSpecial(body.name); - - const channel = await new Channel({ - name, - type, - // owner_id only for group dm channels - created_at: new Date(), - last_message_id: null, - recipients: [...body.recipients.map((x) => new Recipient({ user_id: x })), new Recipient({ user_id: req.user_id })] - }).save(); - - await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent); - - res.json(channel); + res.json(await ChannelService.createDMChannel(body.recipients, req.user_id, body.name)); }); export default router; diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 58d2e481..1d72f11a 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -18,9 +18,19 @@ const router = Router(); const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; router.get("/", route({}), async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships"] }); + const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships", "relationships.to"] }); - return res.json(user.relationships); + //TODO DTO + const related_users = user.relationships.map(r => { + return { + id: r.to.id, + type: r.type, + nickname: null, + user: r.to.toPublicUser(), + } + }) + + return res.json(related_users); }); export interface RelationshipPutSchema { @@ -48,7 +58,10 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, await User.findOneOrFail({ relations: ["relationships", "relationships.to"], select: userProjection, - where: req.body as { discriminator: string; username: string } + where: { + discriminator: String(req.body.discriminator,).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes + username: req.body.username + } }), req.body.type ); diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index f6a4478f..d91cd7f2 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -88,20 +88,17 @@ export async function onIdentify(this: WebSocket, data: Payload) { const user_guild_settings_entries = members.map((x) => x.settings); const recipients = await Recipient.find({ - where: { user_id: this.user_id }, + where: { user_id: this.user_id, closed: false }, relations: ["channel", "channel.recipients", "channel.recipients.user"], // TODO: public user selection }); const channels = recipients.map((x) => { // @ts-ignore x.channel.recipients = x.channel.recipients?.map((x) => x.user); - // @ts-ignore - users = users.concat(x.channel.recipients); - if (x.channel.type === ChannelType.DM) { - x.channel.recipients = [ - // @ts-ignore - x.channel.recipients.find((x) => x.id !== this.user_id), - ]; + //TODO is this needed? check if users in group dm that are not friends are sent in the READY event + //users = users.concat(x.channel.recipients); + if (x.channel.isDm()) { + x.channel.recipients = x.channel.recipients!.filter((x) => x.id !== this.user_id); } return x.channel; }); @@ -111,16 +108,19 @@ export async function onIdentify(this: WebSocket, data: Payload) { }); if (!user) return this.close(CLOSECODES.Authentication_failed); - const public_user = { - username: user.username, - discriminator: user.discriminator, - id: user.id, - public_flags: user.public_flags, - avatar: user.avatar, - bot: user.bot, - bio: user.bio, - }; - users.push(public_user); + for (let relation of user.relationships) { + const related_user = relation.to + const public_related_user = { + username: related_user.username, + discriminator: related_user.discriminator, + id: related_user.id, + public_flags: related_user.public_flags, + avatar: related_user.avatar, + bot: related_user.bot, + bio: related_user.bio, + }; + users.push(public_related_user); + } const session_id = genSessionId(); this.session_id = session_id; //Set the session of the WebSocket object @@ -201,7 +201,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { // @ts-ignore experiments: experiments, // TODO guild_join_requests: [], // TODO what is this? - users: users.unique(), // TODO + users: users.unique(), merged_members: merged_members, // shard // TODO: only for bots sharding // application // TODO for applications diff --git a/gateway/src/opcodes/index.ts b/gateway/src/opcodes/index.ts index a6d13bfb..c4069589 100644 --- a/gateway/src/opcodes/index.ts +++ b/gateway/src/opcodes/index.ts @@ -21,5 +21,6 @@ export default { 8: onRequestGuildMembers, // 9: Invalid Session // 10: Hello + // 13: Dm_update 14: onLazyRequest, }; diff --git a/util/src/dtos/DmChannelDTO.ts b/util/src/dtos/DmChannelDTO.ts new file mode 100644 index 00000000..8b7a18fd --- /dev/null +++ b/util/src/dtos/DmChannelDTO.ts @@ -0,0 +1,35 @@ +import { MinimalPublicUserDTO } from "./UserDTO"; +import { Channel, PublicUserProjection, User } from "../entities"; + +export class DmChannelDTO { + icon: string | null; + id: string; + last_message_id: string | null; + name: string | null; + origin_channel_id: string | null; + owner_id?: string; + recipients: MinimalPublicUserDTO[]; + type: number; + + static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) { + const obj = new DmChannelDTO() + obj.icon = channel.icon || null + obj.id = channel.id + obj.last_message_id = channel.last_message_id || null + obj.name = channel.name || null + obj.origin_channel_id = origin_channel_id || null + obj.owner_id = channel.owner_id + obj.type = channel.type + obj.recipients = (await Promise.all(channel.recipients!.filter(r => !excluded_recipients.includes(r.user_id)).map(async r => { + return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection }) + }))).map(u => new MinimalPublicUserDTO(u)) + return obj + } + + excludedRecipients(excluded_recipients: string[]): DmChannelDTO { + return { + ...this, + recipients: this.recipients.filter(r => !excluded_recipients.includes(r.id)) + } + } +} \ No newline at end of file diff --git a/util/src/dtos/UserDTO.ts b/util/src/dtos/UserDTO.ts new file mode 100644 index 00000000..f09b5f4e --- /dev/null +++ b/util/src/dtos/UserDTO.ts @@ -0,0 +1,17 @@ +import { User } from "../entities"; + +export class MinimalPublicUserDTO { + avatar?: string | null; + discriminator: string; + id: string; + public_flags: number; + username: string; + + constructor(user: User) { + this.avatar = user.avatar + this.discriminator = user.discriminator + this.id = user.id + this.public_flags = user.public_flags + this.username = user.username + } +} \ No newline at end of file diff --git a/util/src/dtos/index.ts b/util/src/dtos/index.ts new file mode 100644 index 00000000..13702342 --- /dev/null +++ b/util/src/dtos/index.ts @@ -0,0 +1,2 @@ +export * from "./DmChannelDTO"; +export * from "./UserDTO"; \ No newline at end of file diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index fc954f63..6eac19ca 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -1,7 +1,6 @@ -import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; -import { Message } from "./Message"; import { User } from "./User"; import { HTTPError } from "lambert-server"; import { emitEvent, getPermission, Snowflake } from "../util"; @@ -31,6 +30,9 @@ export class Channel extends BaseClass { @Column({ nullable: true }) name?: string; + @Column({ nullable: true }) + icon?: string; + @Column({ type: "simple-enum", enum: ChannelType }) type: ChannelType; @@ -38,13 +40,8 @@ export class Channel extends BaseClass { recipients?: Recipient[]; @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.last_message) last_message_id: string; - @JoinColumn({ name: "last_message_id" }) - @ManyToOne(() => Message) - last_message?: Message; - @Column({ nullable: true }) @RelationId((channel: Channel) => channel.guild) guild_id?: string; @@ -162,6 +159,10 @@ export class Channel extends BaseClass { return channel; } + + isDm() { + return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM + } } export interface ChannelPermissionOverwrite { diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts index 2a27b29f..bb280588 100644 --- a/util/src/entities/Recipient.ts +++ b/util/src/entities/Recipient.ts @@ -19,5 +19,8 @@ export class Recipient extends BaseClass { @ManyToOne(() => require("./User").User) user: import("./User").User; + @Column({ default: false }) + closed: boolean; + // TODO: settings/mute/nick/added at/encryption keys/read_state } diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 736704f8..cef88777 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -124,7 +124,7 @@ export class User extends BaseClass { flags: string; // UserFlags @Column() - public_flags: string; + public_flags: number; @JoinColumn({ name: "relationship_ids" }) @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from) diff --git a/util/src/index.ts b/util/src/index.ts index f3bd9e9b..538bfdd1 100644 --- a/util/src/index.ts +++ b/util/src/index.ts @@ -4,6 +4,8 @@ import "reflect-metadata"; export * from "./util/index"; export * from "./interfaces/index"; export * from "./entities/index"; +export * from "./services/index"; +export * from "./dtos/index"; // import Config from "../util/Config"; // import db, { MongooseCache, toObject } from "./util/Database"; diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts index aff50300..03099bbb 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts @@ -127,6 +127,22 @@ export interface ChannelPinsUpdateEvent extends Event { }; } +export interface ChannelRecipientAddEvent extends Event { + event: "CHANNEL_RECIPIENT_ADD"; + data: { + channel_id: string; + user: User; + }; +} + +export interface ChannelRecipientRemoveEvent extends Event { + event: "CHANNEL_RECIPIENT_REMOVE"; + data: { + channel_id: string; + user: User; + }; +} + export interface GuildCreateEvent extends Event { event: "GUILD_CREATE"; data: Guild & { @@ -436,6 +452,8 @@ export type EventData = | ChannelUpdateEvent | ChannelDeleteEvent | ChannelPinsUpdateEvent + | ChannelRecipientAddEvent + | ChannelRecipientRemoveEvent | GuildCreateEvent | GuildUpdateEvent | GuildDeleteEvent @@ -482,6 +500,8 @@ export enum EVENTEnum { ChannelUpdate = "CHANNEL_UPDATE", ChannelDelete = "CHANNEL_DELETE", ChannelPinsUpdate = "CHANNEL_PINS_UPDATE", + ChannelRecipientAdd = "CHANNEL_RECIPIENT_ADD", + ChannelRecipientRemove = "CHANNEL_RECIPIENT_REMOVE", GuildCreate = "GUILD_CREATE", GuildUpdate = "GUILD_UPDATE", GuildDelete = "GUILD_DELETE", @@ -525,6 +545,8 @@ export type EVENT = | "CHANNEL_UPDATE" | "CHANNEL_DELETE" | "CHANNEL_PINS_UPDATE" + | "CHANNEL_RECIPIENT_ADD" + | "CHANNEL_RECIPIENT_REMOVE" | "GUILD_CREATE" | "GUILD_UPDATE" | "GUILD_DELETE" diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts new file mode 100644 index 00000000..7cded10f --- /dev/null +++ b/util/src/services/ChannelService.ts @@ -0,0 +1,88 @@ +import { Channel, ChannelType, PublicUserProjection, Recipient, User } from "../entities"; +import { HTTPError } from "lambert-server"; +import { emitEvent, trimSpecial } from "../util"; +import { DmChannelDTO } from "../dtos"; +import { ChannelRecipientRemoveEvent } from "../interfaces"; + +export function checker(arr: any[], target: any[]) { + return target.every(v => arr.includes(v)); +} + +export class ChannelService { + public static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { + recipients = recipients.unique().filter((x) => x !== creator_user_id); + const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); + + if (otherRecipientsUsers.length !== recipients.length) { + throw new HTTPError("Recipient/s not found"); + } + + const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; + + let channel = null; + + const channelRecipients = [...recipients, creator_user_id] + + const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] }) + + for (let ur of userRecipients) { + let re = ur.channel.recipients!.map(r => r.user_id) + if (re.length === channelRecipients.length) { + if (checker(re, channelRecipients)) { + if (channel == null) { + channel = ur.channel + await ur.assign({ closed: false }).save() + } + } + } + } + + if (channel == null) { + name = trimSpecial(name); + + channel = await new Channel({ + name, + type, + owner_id: (type === ChannelType.DM ? undefined : creator_user_id), + created_at: new Date(), + last_message_id: null, + recipients: channelRecipients.map((x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })), + }).save(); + } + + + const channel_dto = await DmChannelDTO.from(channel) + + if (type === ChannelType.GROUP_DM) { + + for (let recipient of channel.recipients!) { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id + }) + } + } else { + await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id }); + } + + return channel_dto.excludedRecipients([creator_user_id]) + } + + public static async removeRecipientFromChannel(channel: Channel, user_id: string) { + await Recipient.delete({ channel_id: channel.id, user_id: user_id }) + + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + + await emitEvent({ + event: "CHANNEL_RECIPIENT_REMOVE", data: { + channel_id: channel.id, + user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) + }, channel_id: channel.id + } as ChannelRecipientRemoveEvent); + } +} \ No newline at end of file diff --git a/util/src/services/index.ts b/util/src/services/index.ts new file mode 100644 index 00000000..c012a208 --- /dev/null +++ b/util/src/services/index.ts @@ -0,0 +1 @@ +export * from "./ChannelService"; From 4003fef534a9b8fb2a075daa05749e3ec16cccec Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 16 Sep 2021 22:03:30 +0200 Subject: [PATCH 42/90] :bug: fix vanity url --- api/package-lock.json | Bin 807339 -> 807368 bytes api/src/routes/guilds/#guild_id/vanity-url.ts | 7 ++++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 79889f8ac6d1a7237ccad8f1211fd07922509af8..fd9c68b4db350559571acf3f3bded2c26f32fb27 100644 GIT binary patch delta 80 zcmZ3z*zm++!ws|9Czc7Zm6l}YDCta3OlOptxKg9}7JK_Gc19p(0%B$$W&vVWAZ7z% VkQ@gPa{@6J5OZ(8#m;ll69DUm9#a4S delta 72 zcmX@H*l_h?!ws|9r~iG+#yx$*ZbqKw^X%>C*%^VD35c12m<5PgftU@5L2?{G%n8I? NK+L`UJUh=%PXJQ)9E1P> diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 801768fb..7f2cea9e 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,6 +1,7 @@ import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; +import { HTTPError } from "lambert-server"; const router = Router(); @@ -29,14 +30,14 @@ router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }) const body = req.body as VanityUrlSchema; const code = body.code?.replace(InviteRegex, ""); - await Invite.findOneOrFail({ code }); + const invite = await Invite.findOne({ code }); + if (invite) throw new HTTPError("Invite already exists"); const guild = await Guild.findOneOrFail({ id: guild_id }); const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); - guild.vanity_url_code = code; Promise.all([ - guild.save(), + Guild.update({ id: guild_id }, { vanity_url_code: code }), Invite.delete({ code: guild.vanity_url_code }), new Invite({ code: code, From 5fa02e6b108a66968ba4f1c9fc85b49484d26b2a Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 17 Sep 2021 10:57:24 +0200 Subject: [PATCH 43/90] Update ChannelService.ts --- util/src/services/ChannelService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts index 7cded10f..319475b6 100644 --- a/util/src/services/ChannelService.ts +++ b/util/src/services/ChannelService.ts @@ -13,6 +13,7 @@ export class ChannelService { recipients = recipients.unique().filter((x) => x !== creator_user_id); const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); + // TODO: check config for max number of recipients if (otherRecipientsUsers.length !== recipients.length) { throw new HTTPError("Recipient/s not found"); } @@ -85,4 +86,4 @@ export class ChannelService { }, channel_id: channel.id } as ChannelRecipientRemoveEvent); } -} \ No newline at end of file +} From c4cfa42c60b7101e70bb76bdbd359daec434d78d Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Fri, 17 Sep 2021 13:59:21 +0200 Subject: [PATCH 44/90] Fix GET /users/@me/channels --- api/src/routes/users/@me/channels.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index bd7af18c..873ff245 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,21 +1,12 @@ import { Request, Response, Router } from "express"; -import { PublicUserProjection, Recipient, User, ChannelService } from "@fosscord/util"; +import { Recipient, ChannelService, DmChannelDTO } from "@fosscord/util"; import { route } from "@fosscord/api"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel", "user"] }); - - //TODO check if this is right - const aa = await Promise.all(recipients.map(async (x) => { - return { - ...(x.channel), - recipients: await User.findOneOrFail({ where: { id: x.user_id }, select: PublicUserProjection }), - } - })) - - res.json(aa); + const recipients = await Recipient.find({ where: { user_id: req.user_id, closed: false }, relations: ["channel", "channel.recipients"] }); + res.json(await Promise.all(recipients.map(r => DmChannelDTO.from(r.channel, [req.user_id])))); }); export interface DmChannelCreateSchema { From 50ab5e7d490413388a8365a82af43c40b252beec Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Fri, 17 Sep 2021 18:29:02 +0200 Subject: [PATCH 45/90] Fix icon, owner_id change and channel deletion for group DMs --- api/assets/schemas.json | 1186 +++++++++--------- api/src/routes/channels/#channel_id/index.ts | 8 +- cdn/src/Server.ts | 3 + util/src/entities/Channel.ts | 4 +- util/src/services/ChannelService.ts | 36 +- 5 files changed, 631 insertions(+), 606 deletions(-) diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 9c34f968..05046b97 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -81,11 +81,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -142,27 +161,7 @@ } }, "additionalProperties": false, - "required": [ - "name", - "type" - ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -308,11 +307,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -368,11 +386,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -470,22 +484,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -631,11 +629,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -691,11 +708,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -751,22 +764,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -912,11 +909,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -972,11 +988,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -1002,22 +1014,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -1163,11 +1159,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -1223,11 +1238,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -1256,22 +1267,6 @@ "messages" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -1417,11 +1412,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -1477,11 +1491,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -1519,22 +1529,6 @@ "type" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -1680,11 +1674,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -1740,11 +1753,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -1775,22 +1784,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -1936,11 +1929,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -1996,11 +2008,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -2026,22 +2034,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -2187,11 +2179,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -2247,11 +2258,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -2289,22 +2296,6 @@ ] }, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -2450,11 +2441,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -2510,11 +2520,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -2565,22 +2571,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -2726,11 +2716,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -2786,11 +2795,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -2889,22 +2894,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -3050,11 +3039,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -3110,11 +3118,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -3140,22 +3144,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -3301,11 +3289,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -3361,11 +3368,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -3391,22 +3394,6 @@ "nick" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -3552,11 +3539,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -3612,11 +3618,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -3654,22 +3656,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -3815,11 +3801,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -3875,11 +3880,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -3912,22 +3913,6 @@ ] }, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -4073,11 +4058,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -4133,11 +4137,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -4166,22 +4166,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -4327,11 +4311,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -4387,11 +4390,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -4420,22 +4419,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -4581,11 +4564,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -4641,11 +4643,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -4670,22 +4668,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -4831,11 +4813,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -4891,11 +4892,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -4940,22 +4937,6 @@ "channel_id" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -5101,11 +5082,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -5161,11 +5161,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -5217,22 +5213,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -5378,11 +5358,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -5438,11 +5437,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -5472,22 +5467,6 @@ "enabled" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -5633,11 +5612,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -5693,11 +5691,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -5726,22 +5720,6 @@ "name" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -5887,11 +5865,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -5947,11 +5944,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -5983,22 +5976,6 @@ "recipients" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -6144,11 +6121,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -6204,11 +6200,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -6264,22 +6256,6 @@ }, "additionalProperties": false, "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -6425,11 +6401,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -6485,11 +6480,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -6515,22 +6506,6 @@ "type" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -6676,11 +6651,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -6736,11 +6730,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -6770,22 +6760,6 @@ "username" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -6931,11 +6905,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -6991,11 +6984,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ @@ -7208,22 +7197,6 @@ "timezone_offset" ], "definitions": { - "ChannelType": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 2, - 3, - 4, - 5, - 6 - ], - "type": "number" - }, "ChannelPermissionOverwriteType": { "enum": [ 0, @@ -7369,11 +7342,30 @@ "type": "string" }, "type": { - "$ref": "#/definitions/ChannelType" + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" }, "topic": { "type": "string" }, + "icon": { + "type": [ + "null", + "string" + ] + }, "bitrate": { "type": "integer" }, @@ -7429,11 +7421,7 @@ "type": "integer" } }, - "additionalProperties": false, - "required": [ - "name", - "type" - ] + "additionalProperties": false }, "RelationshipType": { "enum": [ diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index e836622b..70dd3994 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,6 +1,6 @@ import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelService, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { route } from "@fosscord/api"; +import { handleFile, route } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel @@ -44,9 +44,10 @@ export interface ChannelModifySchema { /** * @maxLength 100 */ - name: string; - type: ChannelType; + name?: string; + type?: ChannelType; topic?: string; + icon?: string | null; bitrate?: number; user_limit?: number; rate_limit_per_user?: number; @@ -67,6 +68,7 @@ export interface ChannelModifySchema { router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { var payload = req.body as ChannelModifySchema; const { channel_id } = req.params; + if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon); const channel = await Channel.findOneOrFail({ id: channel_id }); channel.assign(payload); diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts index 5c4a8ae5..590eda6f 100644 --- a/cdn/src/Server.ts +++ b/cdn/src/Server.ts @@ -58,6 +58,9 @@ export class CDNServer extends Server { this.app.use("/team-icons/", avatarsRoute); this.log("verbose", "[Server] Route /team-icons registered"); + this.app.use("/channel-icons/", avatarsRoute); + this.log("verbose", "[Server] Route /channel-icons registered"); + return super.start(); } diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index 6eac19ca..aa2bfab3 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -30,8 +30,8 @@ export class Channel extends BaseClass { @Column({ nullable: true }) name?: string; - @Column({ nullable: true }) - icon?: string; + @Column({ type: 'text', nullable: true }) + icon?: string | null; @Column({ type: "simple-enum", enum: ChannelType }) type: ChannelType; diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts index 319475b6..8f57a28a 100644 --- a/util/src/services/ChannelService.ts +++ b/util/src/services/ChannelService.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelType, PublicUserProjection, Recipient, User } from "../entities"; +import { Channel, ChannelType, Message, PublicUserProjection, Recipient, User } from "../entities"; import { HTTPError } from "lambert-server"; import { emitEvent, trimSpecial } from "../util"; import { DmChannelDTO } from "../dtos"; @@ -72,10 +72,36 @@ export class ChannelService { public static async removeRecipientFromChannel(channel: Channel, user_id: string) { await Recipient.delete({ channel_id: channel.id, user_id: user_id }) + channel.recipients = channel.recipients?.filter(r => r.user_id !== user_id) + + if (channel.recipients?.length === 0) { + await ChannelService.deleteChannel(channel); + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + return + } + + let channel_dto = null; + + //If the owner leave we make the first recipient in the list the new owner + if (channel.owner_id === user_id) { + channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner? + channel_dto = await DmChannelDTO.from(channel, [user_id]) + await emitEvent({ + event: "CHANNEL_UPDATE", + data: channel_dto, + channel_id: channel.id + }); + } + + await channel.save() await emitEvent({ event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), + data: channel_dto !== null ? channel_dto : await DmChannelDTO.from(channel, [user_id]), user_id: user_id }); @@ -86,4 +112,10 @@ export class ChannelService { }, channel_id: channel.id } as ChannelRecipientRemoveEvent); } + + public static async deleteChannel(channel: Channel) { + await Message.delete({ channel_id: channel.id }) //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util + //TODO before deleting the channel we should check and delete other relations + await Channel.delete({ id: channel.id }) + } } From 859fdd679b2f42ae4fd90b3a1a7958370fcdccb8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 17 Sep 2021 23:42:40 +0200 Subject: [PATCH 46/90] :bug: fix body parse treating null not as undefined (except for icons/avatars) --- api/assets/schemas.json | 63 +++++++------------ api/package.json | 3 +- api/scripts/globalSetup.js | 3 +- api/src/routes/auth/register.ts | 30 ++++----- .../routes/channels/#channel_id/invites.ts | 6 +- api/src/routes/guilds/#guild_id/index.ts | 6 +- api/src/routes/guilds/index.ts | 4 +- api/src/routes/guilds/templates/index.ts | 2 +- api/src/routes/users/@me/index.ts | 2 +- api/src/util/route.ts | 45 ++++++++++--- 10 files changed, 88 insertions(+), 76 deletions(-) diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 9c34f968..e27087a9 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -26,8 +26,7 @@ "type": "string" }, "date_of_birth": { - "type": "string", - "format": "date-time" + "type": "string" }, "gift_code_sku_id": { "type": "string" @@ -713,22 +712,13 @@ "type": "object", "properties": { "target_user_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "target_type": { - "type": [ - "null", - "string" - ] + "type": "string" }, "validate": { - "type": [ - "null", - "string" - ] + "type": "string" }, "max_age": { "type": "integer" @@ -2539,7 +2529,10 @@ "type": "string" }, "icon": { - "type": "string" + "type": [ + "null", + "string" + ] }, "channels": { "type": "array", @@ -2551,10 +2544,7 @@ "type": "string" }, "system_channel_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "rules_channel_id": { "type": "string" @@ -2820,10 +2810,7 @@ ] }, "description": { - "type": [ - "null", - "string" - ] + "type": "string" }, "features": { "type": "array", @@ -2844,19 +2831,13 @@ "type": "integer" }, "public_updates_channel_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "afk_timeout": { "type": "integer" }, "afk_channel_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "preferred_locale": { "type": "string" @@ -2869,16 +2850,16 @@ "type": "string" }, "icon": { - "type": "string" + "type": [ + "null", + "string" + ] }, "guild_template_code": { "type": "string" }, "system_channel_id": { - "type": [ - "null", - "string" - ] + "type": "string" }, "rules_channel_id": { "type": "string" @@ -5718,7 +5699,10 @@ "type": "string" }, "avatar": { - "type": "string" + "type": [ + "null", + "string" + ] } }, "additionalProperties": false, @@ -6241,10 +6225,7 @@ "type": "string" }, "accent_color": { - "type": [ - "null", - "integer" - ] + "type": "integer" }, "banner": { "type": [ diff --git a/api/package.json b/api/package.json index d93a6269..ad959e57 100644 --- a/api/package.json +++ b/api/package.json @@ -5,7 +5,8 @@ "main": "dist/Server.js", "types": "dist/Server.d.ts", "scripts": { - "test": "npm run build && jest --coverage --verbose --forceExit ./tests", + "test:only": "node -r ./scripts/tsconfig-paths-bootstrap.js node_modules/.bin/jest --coverage --verbose --forceExit ./tests", + "test": "npm run build && npm run test:only", "test:watch": "jest --watch", "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start", "build": "npx tsc -b .", diff --git a/api/scripts/globalSetup.js b/api/scripts/globalSetup.js index 76cd8e0d..98e70fb9 100644 --- a/api/scripts/globalSetup.js +++ b/api/scripts/globalSetup.js @@ -1,10 +1,11 @@ const fs = require("fs"); +const path = require("path"); const { FosscordServer } = require("../dist/Server"); const Server = new FosscordServer({ port: 3001 }); global.server = Server; module.exports = async () => { try { - fs.unlinkSync(`${__dirname}/../database.db`); + fs.unlinkSync(path.join(__dirname, "..", "database.db")); } catch {} return await Server.start(); }; diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 33f089b2..efe91625 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -27,13 +27,16 @@ export interface RegisterSchema { email?: string; fingerprint?: string; invite?: string; + /** + * @TJS-type string + */ date_of_birth?: Date; // "2000-04-03" gift_code_sku_id?: string; captcha_key?: string; } router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Response) => { - const { + let { email, username, password, @@ -61,14 +64,11 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re // TODO: gift_code_sku_id? // TODO: check password strength - // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick - let adjusted_email = adjustEmail(email); - - // adjusted_password will be the hash of the password - let adjusted_password = ""; + // email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick + email = adjustEmail(email); // trim special uf8 control characters -> Backspace, Newline, ... - let adjusted_username = trimSpecial(username); + username = trimSpecial(username); // discriminator will be randomly generated let discriminator = ""; @@ -96,10 +96,10 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re if (email) { // replace all dots and chars after +, if its a gmail.com email - if (!adjusted_email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); + if (!email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); // check if there is already an account with this email - const exists = await User.findOneOrFail({ email: adjusted_email }).catch((e) => {}); + const exists = await User.findOneOrFail({ email: email }).catch((e) => {}); if (exists) { throw FieldErrors({ @@ -122,6 +122,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } else if (register.dateOfBirth.minimum) { const minimum = new Date(); minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); + date_of_birth = new Date(date_of_birth); // higher is younger if (date_of_birth > minimum) { @@ -162,7 +163,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } // the salt is saved in the password refer to bcrypt docs - adjusted_password = await bcrypt.hash(password, 12); + password = await bcrypt.hash(password, 12); let exists; // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists @@ -171,7 +172,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? for (let tries = 0; tries < 5; tries++) { discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); - exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] }); + exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"] }); if (!exists) break; } @@ -190,7 +191,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re const user = await new User({ created_at: new Date(), - username: adjusted_username, + username: username, discriminator, id: Snowflake.generate(), bot: false, @@ -204,12 +205,12 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re verified: false, disabled: false, deleted: false, - email: adjusted_email, + email: email, nsfw_allowed: true, // TODO: depending on age public_flags: "0", flags: "0", // TODO: generate data: { - hash: adjusted_password, + hash: password, valid_tokens_since: new Date() }, settings: { ...defaultSettings, locale: req.language || "en-US" }, @@ -220,6 +221,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re }); export function adjustEmail(email: string): string | undefined { + if (!email) return email; // body parser already checked if it is a valid email const parts = email.match(EMAIL_REGEX); // @ts-ignore diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index 71612e31..22420983 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -8,9 +8,9 @@ import { isTextChannel } from "./messages"; const router: Router = Router(); export interface InviteCreateSchema { - target_user_id?: string | null; - target_type?: string | null; - validate?: string | null; // ? what is this + target_user_id?: string; + target_type?: string; + validate?: string; // ? what is this max_age?: number; max_uses?: number; temporary?: boolean; diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 7e4bf28a..63000b84 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -11,15 +11,15 @@ const router = Router(); export interface GuildUpdateSchema extends Omit { banner?: string | null; splash?: string | null; - description?: string | null; + description?: string; features?: string[]; verification_level?: number; default_message_notifications?: number; system_channel_flags?: number; explicit_content_filter?: number; - public_updates_channel_id?: string | null; + public_updates_channel_id?: string; afk_timeout?: number; - afk_channel_id?: string | null; + afk_channel_id?: string; preferred_locale?: string; } diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 2334bb9c..2e68d953 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -12,10 +12,10 @@ export interface GuildCreateSchema { */ name: string; region?: string; - icon?: string; + icon?: string | null; channels?: ChannelModifySchema[]; guild_template_code?: string; - system_channel_id?: string | null; + system_channel_id?: string; rules_channel_id?: string; } diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index eb3867c8..b5e243e9 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -6,7 +6,7 @@ import { DiscordApiErrors } from "@fosscord/util"; export interface GuildTemplateCreateSchema { name: string; - avatar?: string; + avatar?: string | null; } router.get("/:code", route({}), async (req: Request, res: Response) => { diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index c0002d79..da2f3348 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -16,7 +16,7 @@ export interface UserModifySchema { * @maxLength 1024 */ bio?: string; - accent_color?: number | null; + accent_color?: number; banner?: string | null; password?: string; new_password?: string; diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 6cd8f622..678ca64c 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -43,10 +43,37 @@ export interface RouteOptions { }; } +// Normalizer is introduced to workaround https://github.com/ajv-validator/ajv/issues/1287 +// this removes null values as ajv doesn't treat them as undefined +// normalizeBody allows to handle circular structures without issues +// taken from https://github.com/serverless/serverless/blob/master/lib/classes/ConfigSchemaHandler/index.js#L30 (MIT license) +const normalizeBody = (body: any = {}) => { + const normalizedObjectsSet = new WeakSet(); + const normalizeObject = (object: any) => { + if (normalizedObjectsSet.has(object)) return; + normalizedObjectsSet.add(object); + if (Array.isArray(object)) { + for (const [index, value] of object.entries()) { + if (typeof value === "object") normalizeObject(value); + } + } else { + for (const [key, value] of Object.entries(object)) { + if (value == null) { + if (key === "icon" || key === "avatar" || key === "banner" || key === "splash") continue; + delete object[key]; + } else if (typeof value === "object") { + normalizeObject(value); + } + } + } + }; + normalizeObject(body); + return body; +}; + export function route(opts: RouteOptions) { - var validate: AnyValidateFunction; + var validate: AnyValidateFunction | undefined; if (opts.body) { - // @ts-ignore validate = ajv.getSchema(opts.body); if (!validate) throw new Error(`Body schema ${opts.body} not found`); } @@ -60,14 +87,14 @@ export function route(opts: RouteOptions) { if (!permission.has(required)) { throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); } + } - if (validate) { - const valid = validate(req.body); - if (!valid) { - const fields: Record = {}; - validate.errors?.forEach((x) => (fields[x.instancePath] = { code: x.keyword, message: x.message || "" })); - throw FieldErrors(fields); - } + if (validate) { + const valid = validate(normalizeBody(req.body)); + if (!valid) { + const fields: Record = {}; + validate.errors?.forEach((x) => (fields[x.instancePath.slice(1)] = { code: x.keyword, message: x.message || "" })); + throw FieldErrors(fields); } } next(); From 181afe7e09292f49a86f9794f9675f723378c57b Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 00:19:53 +0200 Subject: [PATCH 47/90] :bug: fix guild create icon --- api/src/routes/guilds/index.ts | 3 ++- api/src/util/cdn.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 2e68d953..3612ca82 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; -import { route } from "@fosscord/api"; +import { handleFile, route } from "@fosscord/api"; import { DiscordApiErrors } from "@fosscord/util"; import { ChannelModifySchema } from "../channels/#channel_id"; @@ -34,6 +34,7 @@ router.post("/", route({ body: "GuildCreateSchema" }), async (req: Request, res: await Guild.insert({ name: body.name, + icon: await handleFile(`/icons/${guild_id}`, body.icon as string), region: Config.get().regions.default, owner_id: req.user_id, afk_timeout: 300, diff --git a/api/src/util/cdn.ts b/api/src/util/cdn.ts index 88b0ea0d..8c6e9ac9 100644 --- a/api/src/util/cdn.ts +++ b/api/src/util/cdn.ts @@ -42,9 +42,9 @@ export async function handleFile(path: string, body?: string): Promise Date: Sat, 18 Sep 2021 01:49:17 +0200 Subject: [PATCH 48/90] :pencil: add default route description to all routes --- api/src/routes/applications/detectable.ts | 5 +++-- api/src/routes/guilds/#guild_id/channels.ts | 2 +- api/src/routes/guilds/#guild_id/roles.ts | 2 +- api/src/routes/outbound-promotions.ts | 5 +++-- api/src/routes/store/applications.ts | 9 +++++---- api/src/routes/store/skus.ts | 7 ++++--- .../users/@me/applications/#app_id/entitlements.ts | 5 +++-- api/src/routes/users/@me/billing/country-code.ts | 7 ++++--- api/src/routes/users/@me/billing/subscriptions.ts | 5 +++-- api/src/routes/users/@me/index.ts | 2 +- 10 files changed, 28 insertions(+), 21 deletions(-) diff --git a/api/src/routes/applications/detectable.ts b/api/src/routes/applications/detectable.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/applications/detectable.ts +++ b/api/src/routes/applications/detectable.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index 13c6b515..a36e5448 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -5,7 +5,7 @@ import { route } from "@fosscord/api"; import { ChannelModifySchema } from "../../channels/#channel_id"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const channels = await Channel.find({ guild_id }); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index bac63bd4..d1d60906 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -29,7 +29,7 @@ export type RolePositionUpdateSchema = { position: number; }[]; -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; await Member.IsInGuildOrFail(req.user_id, guild_id); diff --git a/api/src/routes/outbound-promotions.ts b/api/src/routes/outbound-promotions.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/outbound-promotions.ts +++ b/api/src/routes/outbound-promotions.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/store/applications.ts b/api/src/routes/store/applications.ts index 69cd716d..352c1752 100644 --- a/api/src/routes/store/applications.ts +++ b/api/src/routes/store/applications.ts @@ -1,11 +1,12 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/applications/:id", async (req: Request, res: Response) => { - //TODO - const { id } = req.params; +router.get("/applications/:id", route({}), async (req: Request, res: Response) => { + //TODO + const { id } = req.params; res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/store/skus.ts b/api/src/routes/store/skus.ts index 5c37850d..7d0e12eb 100644 --- a/api/src/routes/store/skus.ts +++ b/api/src/routes/store/skus.ts @@ -1,11 +1,12 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/skus/:id", async (req: Request, res: Response) => { +router.get("/skus/:id", route({}), async (req: Request, res: Response) => { //TODO - const { id } = req.params; + const { id } = req.params; res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/users/@me/applications/#app_id/entitlements.ts b/api/src/routes/users/@me/applications/#app_id/entitlements.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/users/@me/applications/#app_id/entitlements.ts +++ b/api/src/routes/users/@me/applications/#app_id/entitlements.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/users/@me/billing/country-code.ts b/api/src/routes/users/@me/billing/country-code.ts index ac3653a2..33d40796 100644 --- a/api/src/routes/users/@me/billing/country-code.ts +++ b/api/src/routes/users/@me/billing/country-code.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO - res.json({ "country_code": "US" }).status(200); + res.json({ country_code: "US" }).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/users/@me/billing/subscriptions.ts b/api/src/routes/users/@me/billing/subscriptions.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/users/@me/billing/subscriptions.ts +++ b/api/src/routes/users/@me/billing/subscriptions.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index da2f3348..67b11ce0 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -23,7 +23,7 @@ export interface UserModifySchema { code?: string; } -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } })); }); From 69550e535e990ded1e6bd8763e4f48299f70584a Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 01:49:36 +0200 Subject: [PATCH 49/90] :sparkles: generate test responses --- api/assets/responses.json | 90 +++++++++++++++++++++++++ api/scripts/generate_openapi_schema.ts | 41 +++++------ api/scripts/generate_test_schema.ts | 68 +++++++++++++++++++ api/scripts/globalSetup.js | 15 ----- api/scripts/tsconfig-paths-bootstrap.js | 10 --- 5 files changed, 175 insertions(+), 49 deletions(-) create mode 100644 api/assets/responses.json create mode 100644 api/scripts/generate_test_schema.ts delete mode 100644 api/scripts/globalSetup.js delete mode 100644 api/scripts/tsconfig-paths-bootstrap.js diff --git a/api/assets/responses.json b/api/assets/responses.json new file mode 100644 index 00000000..35645d73 --- /dev/null +++ b/api/assets/responses.json @@ -0,0 +1,90 @@ +{ + "UserProfileResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/UserPublic" + }, + "connected_accounts": { + "$ref": "#/definitions/PublicConnectedAccount" + }, + "premium_guild_since": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "connected_accounts", + "user" + ], + "definitions": { + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + } +} \ No newline at end of file diff --git a/api/scripts/generate_openapi_schema.ts b/api/scripts/generate_openapi_schema.ts index 329aeaf4..c0995b6c 100644 --- a/api/scripts/generate_openapi_schema.ts +++ b/api/scripts/generate_openapi_schema.ts @@ -48,34 +48,27 @@ function combineSchemas(opts: { program: TJS.Program; generator: TJS.JsonSchemaG return definitions; } +const ExcludedSchemas = [ + "DefaultSchema", + "Schema", + "EntitySchema", + "ServerResponse", + "Http2ServerResponse", + "global.Express.Response", + "Response", + "e.Response", + "request.Response", + "supertest.Response" +]; + function apiSchemas() { const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "src", "schema", "index.ts")], compilerOptions); const generator = TJS.buildGenerator(program, settings); - const schemas = [ - "BanCreateSchema", - "DmChannelCreateSchema", - "ChannelModifySchema", - "ChannelGuildPositionUpdateSchema", - "ChannelGuildPositionUpdateSchema", - "EmojiCreateSchema", - "GuildCreateSchema", - "GuildUpdateSchema", - "GuildTemplateCreateSchema", - "GuildUpdateWelcomeScreenSchema", - "InviteCreateSchema", - "MemberCreateSchema", - "MemberNickChangeSchema", - "MemberChangeSchema", - "MessageCreateSchema", - "RoleModifySchema", - "TemplateCreateSchema", - "TemplateModifySchema", - "UserModifySchema", - "UserSettingsSchema", - "WidgetModifySchema", - "" - ]; + const schemas = generator + .getUserSymbols() + .filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x)) + .concat(generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x))); // @ts-ignore combineSchemas({ schemas, generator, program }); diff --git a/api/scripts/generate_test_schema.ts b/api/scripts/generate_test_schema.ts new file mode 100644 index 00000000..eed77738 --- /dev/null +++ b/api/scripts/generate_test_schema.ts @@ -0,0 +1,68 @@ +// https://mermade.github.io/openapi-gui/# +// https://editor.swagger.io/ +import path from "path"; +import fs from "fs"; +import * as TJS from "typescript-json-schema"; +import "missing-native-js-functions"; +const schemaPath = path.join(__dirname, "..", "assets", "responses.json"); + +const settings: TJS.PartialArgs = { + required: true, + ignoreErrors: true, + excludePrivate: true, + defaultNumberType: "integer", + noExtraProps: true, + defaultProps: false +}; +const compilerOptions: TJS.CompilerOptions = { + strictNullChecks: true +}; +const ExcludedSchemas = [ + "ServerResponse", + "Http2ServerResponse", + "global.Express.Response", + "Response", + "e.Response", + "request.Response", + "supertest.Response" +]; + +function main() { + const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions); + const generator = TJS.buildGenerator(program, settings); + if (!generator || !program) return; + + const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x)); + console.log(schemas); + + var definitions: any = {}; + + for (const name of schemas) { + const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); + if (!part) continue; + + definitions = { ...definitions, [name]: { ...part } }; + } + + fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); +} + +// #/definitions/ +main(); + +function walk(dir: string) { + var results = [] as string[]; + var list = fs.readdirSync(dir); + list.forEach(function (file) { + file = dir + "/" + file; + var stat = fs.statSync(file); + if (stat && stat.isDirectory()) { + /* Recurse into a subdirectory */ + results = results.concat(walk(file)); + } else { + if (!file.endsWith(".ts")) return; + results.push(file); + } + }); + return results; +} diff --git a/api/scripts/globalSetup.js b/api/scripts/globalSetup.js deleted file mode 100644 index 98e70fb9..00000000 --- a/api/scripts/globalSetup.js +++ /dev/null @@ -1,15 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const { FosscordServer } = require("../dist/Server"); -const Server = new FosscordServer({ port: 3001 }); -global.server = Server; -module.exports = async () => { - try { - fs.unlinkSync(path.join(__dirname, "..", "database.db")); - } catch {} - return await Server.start(); -}; - -// afterAll(async () => { -// return await Server.stop(); -// }); diff --git a/api/scripts/tsconfig-paths-bootstrap.js b/api/scripts/tsconfig-paths-bootstrap.js deleted file mode 100644 index d6ad3c57..00000000 --- a/api/scripts/tsconfig-paths-bootstrap.js +++ /dev/null @@ -1,10 +0,0 @@ -const tsConfigPaths = require("tsconfig-paths"); -const path = require("path"); - -const cleanup = tsConfigPaths.register({ - baseUrl: path.join(__dirname, ".."), - paths: { - "@fosscord/api": ["dist/index.js"], - "@fosscord/api/*": ["dist/*"] - } -}); From 1e331b7c9d74cc4a0c532ebe8562986547d388ca Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 01:50:20 +0200 Subject: [PATCH 50/90] :art: use typescript plugin that converts to relative paths --- api/babel.config.js | 6 ++++++ api/package-lock.json | Bin 807368 -> 933007 bytes api/package.json | 14 +++++++++++--- api/tsconfig.json | 5 +++-- bundle/package-lock.json | Bin 72597 -> 89327 bytes bundle/package.json | 4 +++- bundle/tsconfig-paths-bootstrap.js | 14 -------------- cdn/package-lock.json | Bin 354786 -> 360232 bytes cdn/package.json | 6 ++++-- cdn/scripts/tsconfig-paths-bootstrap.js | 10 ---------- cdn/tsconfig.json | 3 ++- gateway/package-lock.json | Bin 156237 -> 161981 bytes gateway/package.json | 4 +++- gateway/scripts/tsconfig-paths-bootstrap.js | 10 ---------- gateway/tsconfig.json | 3 ++- util/tsconfig.json | 8 +------- 16 files changed, 35 insertions(+), 52 deletions(-) create mode 100644 api/babel.config.js delete mode 100644 bundle/tsconfig-paths-bootstrap.js delete mode 100644 cdn/scripts/tsconfig-paths-bootstrap.js delete mode 100644 gateway/scripts/tsconfig-paths-bootstrap.js diff --git a/api/babel.config.js b/api/babel.config.js new file mode 100644 index 00000000..45ab8ad8 --- /dev/null +++ b/api/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + ["@babel/preset-env", { targets: { node: "current" } }], + ["@babel/preset-typescript", { allowDeclareFields: true }] + ] +}; diff --git a/api/package-lock.json b/api/package-lock.json index fd9c68b4db350559571acf3f3bded2c26f32fb27..3e8f655d51697dd91c553eda6c015b656ee92956 100644 GIT binary patch delta 50734 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 F{{bzviwyt( delta 1159 zcmaKqe@v8h9LM*4?)yC7?{m-d+>h=6Jqps3*$NqTQ;LMc*r;r5jG?V7^oBqNl`dQ< zZgBID{)k^^-{7OTg6W)OP1qjYCt?s|R<`AiRda5*cB!*$cAYI?+U$A`+_^R9pP%>p z^ZD-c`MzI$x1K(C?8&hxh3FNoK+Fq{1wgr-c@J14V=pqT=jduDTSsuBwf-rEbrP*-nP1Oy< zzu5v%vyvET&4UF*x?)=zL`ytLkTEIr8SXzLHCDj(T3Z%`f7AmIZc$d_`9g4E(#rf>b_g=qE%$M?wybs)~uuDC7D(ag>Wm)#-Uv_ zi)Yl}7M%ErzSE7bH^}P=o^Fy~`T+em3hs8Z13}pK3gbBZGP~56PW~SWk^$}#G4!T`wn87r=y~>?mPxzLb~oSE1T1Ie zz(uwWiY_xRUi^jakkEOJ`LqE^@|lwzc?80>JRqDsDkMET56UG;HM=ABdpcVBD9NyK zM9)CbN-)vRommhIQ5ohMs0y8}RK}@(j?ZA{EbsEe36y;}wZNaNS^@wp06tG|CRi6s{;ce)J+W^_C0KSB&Km~z9~Z}jW9^PIj( nf`Qw*PxKyiL&aZe09~{C8&w#~G2Z/jest/setup.js" ], - "globalSetup": "/scripts/globalSetup.js", + "globalSetup": "/jest/globalSetup.js", "verbose": true } } diff --git a/api/tsconfig.json b/api/tsconfig.json index 21a8eb96..0169642d 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -66,8 +66,9 @@ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, "baseUrl": ".", "paths": { - "@fosscord/api": ["src/index.ts"], + "@fosscord/api": ["src/index"], "@fosscord/api/*": ["src/*"] - } + }, + "plugins": [{ "transform": "@zerollup/ts-transform-paths" }] } } diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 718cc969b5e4ecf07d132ad70a37c1b80c1a96d3..08e09450f296565c3ee9417a54b051cd317c3164 100644 GIT binary patch delta 8945 zcmd5>YmD32b%y3;kG=N#H6D-0g&}{Fe zMw*#@wCJt_j}4gQxfJg?=bp!RK7Z!n($D^S`OPmaoz-kAtMlT{&8K%hhTENXC-}kI z2KdU-_U`E1BCATfyAg0h{LRT{(NFkVXLt81`0N>a@{2EB zSpZ)-9t9`=@)USy`OKvBr}r)A^!M)g@iRN{ryylHzI%6T^4T*>i|@O~2Ke*y=*1;N zmt?5d|E-wIrblHF7VdN&oEshL6_x1o{6T2 zRLqxW%8Iu*&?-(%VLPIrxKo*Y+v^PDsQ@4LY9xgX)S8qVk8-}TqgqX*<#ee}db$+F z`P5oMPBYGbP8-8^l=j@`X;-Qopy#k0wa ztlXMdbbG`M?r(EtE$JMwUB;s~rI^eObD=7w#;|0`XARcN zPA=zX%b342)G>WXqal#?Rv)U! z)e6Yoz6@yIag949?jRKpOQ4SMFQAK8K=$eqBL$6YEEExXv9c#d^uuw7Yp9Mf&p00o zU|2ylgs#}G=*4!La=UPEJ80z63RQ{7wQxYj1l;Q}8p^oF#)MLmuS7UE-i=E4?w_<= z2DhCT$;1BH%RJ1E?c6xA3jgfDL*iK-x>-eQZ~>}@&VY-4YiS9c6L|HHF5bhRu;5Ql zkIm*VuQuo1Up{3Tpd>q-7#Zc_WtB1NW3LoQw?|>t+2NYbI7N>Pk#`J6zE;KKC>68( zAWr#;ez)73HN3eN<*{N>k2{~rQT5Tl7gTA?jZ4nYz}EXLsn?M!<}IwvY=4vs%+5 z8IB?6Qj!&3i^TG(8p(9Dro%;0#X>I0ImcO-(kTy$5prB?w+l*!ry9|qHJA&jjiiHd zd7X{4KzggOARCsGFrl$?)6F_;yN)dR1d0;&()C4;9Rs%uS6AoD0H@Y1IN1InwmFy8 z=gOkrqZk8USl?!9)W}NujS|TcgA7-V#f@}TE$~7!YNQy45lA@U8uI$9R|8jTKTng{mOYnGL1{M_4JvaH|ZE?e-8 z{g^V9ngy5*gYJ833%3zTxrdXEQnOKWQJr*%Z5jg31Zp`_1NhC~nEXZhm9=(fDjWuxQb#kLI7oo&I@N|z|vuwlfM(Dhr$ft|Vr0ORyr6YQ^0G(?y?v~g5?hSs; zvLwg~hpIPpD|GKCu_uuLeh*!2NJit^4Al?$!kKY4#npvyrc=qf7^abkOTB{Nw3Y|y zQk10|bfJPJ>YZVx-;XD=BttrMKb_CU4Tl^XmMi&cTP@2>tlss2J}1KTQ;&!o_MqD|6lET<{5 zp-LnH~M5ao|o&4r(0#lN;woM_+y1o(3zo>sG~Y$$Z%gyXAgGl z$sgB$e`VP`YFK#I!5`B2rP!<*?^gHF+dL_ZNuhGQkSP%7bE zqXo9p_U-pS|3Qyc)e-U&3~ONvT$BH5p(6qM&L!B1zau(McO-K^32sH&z2N@(Q%s`h z7#Tv5Fvca9l+NX)s(%#H>6#UGMyAuOr*w?vGqr)w+e)T-eIr*K1-zK9<-%0hGwMf+ z0xOGEcP>*7w!^U&lMEz>^;zRQE_L*J4+fcOHygm$X984g=l3=oJjg!7utL~LHv&Ye zrTUC`u;U5%yNbpSocU_j*D8bwaVSONWHXR$mlKUzS7OBi#~8_|mR2%ERPUyI{-Icr z6hB|=3mS*V`Z;deS^mEL;^h1CgM}qYLtu_t1Jpa@S@eJksd=?Q%mTxE3wqz3?T>4j zVUcr4OkE7Yg3s%fu~)N-ezUNAT1i`u?M0Jd5UEd_f9lXJS3}n>a0E~@O zD5#;T=we0$>K&z!#;Rng6l1g;rA2(5NYL$Zk!98wMu~9pV8|mJ6TkDk<-~pogx`k9 zUU5A4?$QYNdAA7eIqg>w%*eB`Ofi$Sdh432=PN+WC+d6|_rg6dnLr%*yZMRh$Ci0f zk$T|k>5Jg%7{A!7o6qp;dv`Vr&NdLCqNEn{fmAVLD5+vC=u}3fTFHY^eTGUqX=DpH z_U$JZ>@Z;TYv41lZi9tSoqzbZtCm#@P_LYteC<=;TwNCHvfKph&9y}grY{5p>zXM- zn1AmMZs>)5Xo)z_jL*;L%mX>w{jD%GP>xI6UK;VBQQX)GCbhg06D933=A zZC|=44_ywGB)L+-@KvJjKA(4{1H(!+6sz=Ha?cw;O-({=!aPt2Rrb5jJe~K>`oPkj zGt33#5Ml?R(K{w4-QlDTT=?xNKGnpghF*h0Xc18K2zc$&|F(osp*e%O!P(b|O-=7f zstpN|;qwO@%0oEoFn*(-Ec6NuI#bESd7aDQNk=7G$l)HBJ38XCn&9DlT&?H}8C|32 z6#8y9*^QN1(e0^vI)p^~B zn9zNy<3Z$qAmm^TnE`-9c*Xn?LP-!0fnWW6eGvi(VEOh1kvI^Eds$+>Yi7#qQ~Ob5 zt|6R(ePEQ%oPu6Ok;==Gy{UmaFS!;8Sm7XgpBUimF_4X(TZACx?l&$%{ND8uXHCQc zy=q=j7Y6tFZ6-EM)GDPeo-V{`Ejm{Q0vgxdqrbI6ER@lr(|hVnY2CmE4q z6KxU42}hyYjtz%Q!$@?qjro93;>xtT zf(Oov!1xhECDQj}bjC<0broUpi_0fawuA3n zTbumJ=f1T#Wqg3J{N>4&FRUz{c@Ix?>5HQk%FGB%CJ(;o`~Ws-@?U@UlZE#( zI+O2y`KQkxCHe-4W>YU7t55vby ztp6Nee9Tq*4`^6K>M}Tv=@xgq{Wb40QS^h{o z3k>}e`#j6NKUlE*>eorJ;5RoP{_djX+RCHiR-pcS+{zoDU$vY#3M>ljQLzIf?STRH z|Ieb_+dXgj+EHOhF2jl9{Kou$$juh&+8;gd{niD`!=ucTApOr>I?R>qtN+HoU$7hz zw&cdIfhoE55t)*Q-(R!5xqKYadOMIrcs#TROs!|%m-d(={71%WtUy-d#GMoW1LkE{ AssI20 delta 444 zcmX|--%C?r7{__e-t*3#j&A83tzhm*j*_v77)1n%sa;JlNEnh1Z@f?vRJ`#A7>VeG zXb-_Pf}zoL#v7XQRBKj}h~ z8Tc*c6mcEWh8G@Sqh5hSs-4$92^ED3XjJua9M5oJB$0=8Ne}vTCypg<;rBN>#q>so ze_RH3tfq^SC}Zi4`-cq+TeQ3w0=mECv{U8UU)yyWj;dd2XPw({z|)CMNey3{ z4jN7rF-tg189zQ_R`x#xL&5HCuXPp^#bF4GnILS)`Vir6 diff --git a/bundle/package.json b/bundle/package.json index 83f26116..7cf4ba40 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -4,6 +4,7 @@ "description": "", "main": "src/start.js", "scripts": { + "prepare": "ts-patch install -s", "preinstall": "cd ../util && npm i && cd ../api && npm i && cd ../cdn && npm i && cd ../gateway && npm i", "build": "npm run build:util && npm run build:api && npm run build:cdn && npm run build:gateway && npm run build:bundle", "build:bundle": "npx tsc -b .", @@ -12,7 +13,7 @@ "build:cdn": "cd ../cdn/ && npm run build", "build:gateway": "cd ../gateway/ && npm run build", "start": "npm run build && npm run start:bundle", - "start:bundle": "node -r ./tsconfig-paths-bootstrap.js dist/start.js", + "start:bundle": "node dist/start.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -43,6 +44,7 @@ "@types/uuid": "^8.3.0", "@types/ws": "^7.4.0", "@zerollup/ts-transform-paths": "^1.7.18", + "ts-patch": "^1.4.4", "typescript": "^4.3.5" }, "dependencies": { diff --git a/bundle/tsconfig-paths-bootstrap.js b/bundle/tsconfig-paths-bootstrap.js deleted file mode 100644 index efe4b3bc..00000000 --- a/bundle/tsconfig-paths-bootstrap.js +++ /dev/null @@ -1,14 +0,0 @@ -const tsConfigPaths = require("tsconfig-paths"); -const path = require("path"); - -const cleanup = tsConfigPaths.register({ - baseUrl: path.join(__dirname, "node_modules", "@fosscord"), - paths: { - "@fosscord/api": ["api/dist/index.js"], - "@fosscord/api/*": ["api/dist/*"], - "@fosscord/gateway": ["gateway/dist/index.js"], - "@fosscord/gateway/*": ["gateway/dist/*"], - "@fosscord/cdn": ["cdn/dist/index.js"], - "@fosscord/cdn/*": ["cdn/dist/*"], - }, -}); diff --git a/cdn/package-lock.json b/cdn/package-lock.json index 7a4edd89674878d41c0794505b4c1b61f482e1dd..8a70844c3f4c4f80ebd18075261a740377b6afed 100644 GIT binary patch delta 2804 zcmd6pS!~;M9LHrR>5i^rv}?PKmNnVIN){)!(;T2NB4)&_TTh%&ZYS1X(IJRY z5?Sti<%yy7r|vM&I^n6)eoe=(fj(K@WLW0u0xN^dCt)wRgs6um`VphSSmbJ1itG7c zbwYEHq?)c;HIN`(KGM73w#c|x2~)vbtkGNyF{OODQmeGEl-^sTB-WOW7SqMWK!$EI zMZA!5>D^qRUXMwsqRr8)m%K?gR&_+lRIy!u&G;-^W&0vz+r!S50@w0WQaEZu?+0#gQ7x4-zz2-qdfb`^@?ZS<6u^vQvI zI#ox*Nq;OGz^hGDt}Y0^T5^HFEY={$yCrW1O{F>6oe&8zNH7s9P+?FMGbS??pDYsv zDNv6tCL$H9=x$MhDU$HV)|G&zU*SWe8wFUwFWfZxh+BO&%jl-{o~6MDjcza z%d3!aTa{IgTR8!PQXaTUYqUgwG`6H7iAgJ|IW`P zwA;$T5Z{pEWXowYi9*~H@mO08FIl8GCS!>&EV?QlUzrFxgE9?HzOI^XU%EK5nXQU6 zue2VVx~rN5XTMXUm8c7(>5S+2<#~FZbJF&rIn(+tfwy9 zX`GjvnXoD7v3No@FXwHRS`~Yk=j1u#GLC0cNy(n}S{k{8Iqa0v8MiOZ&vCrX*&E$s zufW4r6Ji>y*~U!tOTH)77vD{o5DS8Vmu^FQMwpVY9$KGzfdO~i&}{n(Hr+Y%27J>B z8dMOGArm_KMaKj2~iLA*b%Y2!4c?CCy6}dU5n3(4lS6a#W zC|1cv$U;F5M^bUVnNKjdRS28V=8Bw1o10wL$@+XgN5Jo@P?WpqNvs5HT(C^mGRn}I zz@5*bZ3z0e%5~0lby$_Cb)fxT`pF=60U86F_QRv#+wURWj$UW_;%!RFdq0E^f!bYY zWMZIXa11$)MM^^35j)ua1>UZH4nwSM?eLSY=B~q?+;x>I`$#Yt#F{Go|AO!jYwG=) z9_dc5sV2JI1@B!`qZ{A_cdn`TJ^x_Hd*n#BZ*A>cW8h~*Gdau(wA|Y?4BR-Up6R@R zsF~dlmAsD#b$7PEI=iKF&8yBX{d?XL4}`bf4^rpQ2Wsu{`$Ac8 zShKeCq4Kr6V8FOlGkoM8ivAbC_Ws#*W+pYqkVgZhd%%^tY81RJtM5Bck3OZ*cUm3| GyZs{;swfCaaJ1p%vYw>xAU diff --git a/cdn/package.json b/cdn/package.json index 7c59381b..71211d67 100644 --- a/cdn/package.json +++ b/cdn/package.json @@ -5,9 +5,10 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { + "prepare": "ts-patch install -s", "test": "npm run build && jest --coverage ./tests", "build": "npx tsc -b .", - "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start.js" + "start": "npm run build && node dist/start.js" }, "repository": { "type": "git", @@ -34,7 +35,8 @@ "@types/multer": "^1.4.7", "@types/node": "^14.17.0", "@types/node-fetch": "^2.5.7", - "@types/uuid": "^8.3.0" + "@types/uuid": "^8.3.0", + "ts-patch": "^1.4.4" }, "dependencies": { "@fosscord/util": "file:../util", diff --git a/cdn/scripts/tsconfig-paths-bootstrap.js b/cdn/scripts/tsconfig-paths-bootstrap.js deleted file mode 100644 index 50602b02..00000000 --- a/cdn/scripts/tsconfig-paths-bootstrap.js +++ /dev/null @@ -1,10 +0,0 @@ -const tsConfigPaths = require("tsconfig-paths"); -const path = require("path"); - -const cleanup = tsConfigPaths.register({ - baseUrl: path.join(__dirname, ".."), - paths: { - "@fosscord/cdn": ["dist/index.js"], - "@fosscord/cdn/*": ["dist/*"], - }, -}); diff --git a/cdn/tsconfig.json b/cdn/tsconfig.json index 2daac1ad..f893869f 100644 --- a/cdn/tsconfig.json +++ b/cdn/tsconfig.json @@ -70,6 +70,7 @@ "paths": { "@fosscord/cdn/": ["src/index.ts"], "@fosscord/cdn/*": ["src/*"] - } + }, + "plugins": [{ "transform": "@zerollup/ts-transform-paths" }] } } diff --git a/gateway/package-lock.json b/gateway/package-lock.json index 40db319da8a742b4f6d65a61a87afc99f19909b0..df97d858837f70e044349095432f3a74a855b042 100644 GIT binary patch delta 2988 zcmd6oZD<>H9LM>mom;wgYr8dDx2EfZ!)<9U_mJj6-5^g*n&&3DqzSTlNiMldE=iNS zB$vF}zztuBI&b)A3~_rQ3VYL8G$;<4C_YYF1VscNP!Pd^eIxj0dP&pK;me}iz4`wh z;O_f-_Xo5qJ{tft<8lk zhbvEdtR9h;ZVc>~mTjtCY*C;qa(Cg{7hpsxeoY+Mj0hba8(Be%Nw3DqS}o-gpQ`J_iJ&1)#K!SB|q*I%3iZY zWa4bel8V-{d=hijLP?9XHZ&?-MGi~fzcjV--NlcTy(~|QaIFYNrRUG<2HGCoR?|ME z)$`Mb4Z&J7?H8j-9ZS-TJ;l$Zgm}}$YZ#04N8hoPt5+^7cXsCi%YzCPJnc{@q4oqg zwyztO-`BOY28nv(h=lIyCDW-eR^>Eip+MWsvk{Gmn{+he&8E!-ho2(6e9ayangycP zusQ<9P;g18S?n4?ua89+DKZzahEkQKIeLz>nPRp|l?>@;4UF9DL((dxhT}Fcv0{Gh zSyyk0DY4RxRo7sJZkrWZ%!VcHU7J#4S|)7uM*Y>2H=4HTOO04MW<&!uGT_rMz)3GS zC_O!>8miEIiL24wMZ^$;w0`n2xrdW0@89vMdKsGI;LwxcApG)AfQ|5NKgY{o>RMV< zUb*ELz$9tR<_R)k&Id9<@4O|+bKYhnMmt$YSv1o`MXV=$sK;S&cuh{uSPONI45s~At%UGd5+S20*Mr1CC7m|T!CL{U+u~4O+4l{(2_n}z5 zDTd?vI+wJvE|<&Vp0^b!%AR$En{E^5$wy0x_WJ6iyY7B?c^d52v{}?C!iZpqakKFI z2-Qgw2ns(vsd!X#{}}NtE$I%b+Y%F^mx2K`e6Jtue)%ZaGqMGw?e?TN7RmAOMJ*VE zXODtD__`YG={1uD3H1YmnRWRhFeIkgzXCcrf565Z)| ztc>|0^9xBgQLLk>GS9n85kH9=j2^CHuR0T0EY6Acus{kPk_k}m0)t_=E|Ms?M3Kx? z-R0n7I8ZPO_6EhHf$)5L9d>M7VC0fY);}Jcu5H5puypmx&#kj-;Fp~b6Y<-BS7j^x zEx3MiTeQdC1SkGY`qo{Q;`8ALjd*zLDKN7w#<$)%uJ~*JLqYM*^@i7<1OFGt-^CPr dTFa*t9}hk_Dt9Khfn~US1dO(70mV1N{{XNx+l>GK delta 122 zcmV-=0EPd(@d?eu39z*TlQ;$tlivd(mu*`C3zIN246`)^6M(big~|=H+Lgr#vtFQ? z39~1wh)A>3^h*}E)(in28n-ej0bLol=tBWOhPR8$0nQ4yKiB~X2)Fs*0mlfpw(0?^ cJ-0|H0=g2nSUm#i6t@Lb0>u!w=Vb!c5?d-W0ssI2 diff --git a/gateway/package.json b/gateway/package.json index 6b9e27d6..f5e66a9a 100644 --- a/gateway/package.json +++ b/gateway/package.json @@ -4,8 +4,9 @@ "description": "", "main": "dist/index.js", "scripts": { + "prepare": "ts-patch install -s", "test": "echo \"Error: no test specified\" && exit 1", - "start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start.js", + "start": "npm run build && node dist/start.js", "build": "npx tsc -b .", "dev": "tsnd --respawn src/start.ts" }, @@ -23,6 +24,7 @@ "@types/uuid": "^8.3.0", "@types/ws": "^7.4.0", "ts-node-dev": "^1.1.6", + "ts-patch": "^1.4.4", "typescript": "^4.2.3" }, "dependencies": { diff --git a/gateway/scripts/tsconfig-paths-bootstrap.js b/gateway/scripts/tsconfig-paths-bootstrap.js deleted file mode 100644 index 7308523b..00000000 --- a/gateway/scripts/tsconfig-paths-bootstrap.js +++ /dev/null @@ -1,10 +0,0 @@ -const tsConfigPaths = require("tsconfig-paths"); -const path = require("path"); - -const cleanup = tsConfigPaths.register({ - baseUrl: path.join(__dirname, ".."), - paths: { - "@fosscord/gateway": ["dist/index.js"], - "@fosscord/gateway/*": ["dist/*"], - }, -}); diff --git a/gateway/tsconfig.json b/gateway/tsconfig.json index 2530aa41..c50bd77a 100644 --- a/gateway/tsconfig.json +++ b/gateway/tsconfig.json @@ -72,6 +72,7 @@ "paths": { "@fosscord/gateway/*": ["src/*"], "@fosscord/util/*": ["../util/src/*"] - } + }, + "plugins": [{ "transform": "@zerollup/ts-transform-paths" }] } } diff --git a/util/tsconfig.json b/util/tsconfig.json index ac41cea5..7fbe3bac 100644 --- a/util/tsconfig.json +++ b/util/tsconfig.json @@ -68,12 +68,6 @@ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "resolveJsonModule": true, - "plugins": [ - { - "transform": "ts-transform-json-schema", - "type": "program" - } - ] + "resolveJsonModule": true } } From a7bf2955910772409ea9c3c6c32c0394c76f34b8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 01:50:29 +0200 Subject: [PATCH 51/90] :sparkles: jest automatic tests --- api/jest/getRouteDescriptions.ts | 56 ++++++++++++++++++++++++++++++++ api/jest/globalSetup.js | 15 +++++++++ api/jest/setup.js | 4 +-- api/src/util/route.ts | 5 ++- api/tests/automatic.test.js | 2 -- api/tests/routes.test.ts | 54 ++++++++++++++++++++++++++++++ 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 api/jest/getRouteDescriptions.ts create mode 100644 api/jest/globalSetup.js delete mode 100644 api/tests/automatic.test.js create mode 100644 api/tests/routes.test.ts diff --git a/api/jest/getRouteDescriptions.ts b/api/jest/getRouteDescriptions.ts new file mode 100644 index 00000000..d7d6e0c6 --- /dev/null +++ b/api/jest/getRouteDescriptions.ts @@ -0,0 +1,56 @@ +import { traverseDirectory } from "lambert-server"; +import path from "path"; +import express from "express"; +import * as RouteUtility from "../dist/util/route"; +const Router = express.Router; + +const routes = new Map(); +let currentPath = ""; +let currentFile = ""; +const methods = ["get", "post", "put", "delete", "patch"]; + +function registerPath(file, method, prefix, path, ...args) { + const urlPath = prefix + path; + const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); + const opts = args.find((x) => typeof x === "object"); + if (opts) { + routes.set(urlPath + "|" + method, opts); + // console.log(method, urlPath, opts); + } else { + console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args); + } +} + +function routeOptions(opts) { + return opts; +} + +// @ts-ignore +RouteUtility.route = routeOptions; + +express.Router = (opts) => { + const path = currentPath; + const file = currentFile; + const router = Router(opts); + + for (const method of methods) { + router[method] = registerPath.bind(null, file, method, path); + } + + return router; +}; + +export default function getRouteDescriptions() { + const root = path.join(__dirname, "..", "dist", "routes", "/"); + traverseDirectory({ dirname: root, recursive: true }, (file) => { + currentFile = file; + let path = file.replace(root.slice(0, -1), ""); + path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path + path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes + if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path + currentPath = path; + + require(file); + }); + return routes; +} diff --git a/api/jest/globalSetup.js b/api/jest/globalSetup.js new file mode 100644 index 00000000..98e70fb9 --- /dev/null +++ b/api/jest/globalSetup.js @@ -0,0 +1,15 @@ +const fs = require("fs"); +const path = require("path"); +const { FosscordServer } = require("../dist/Server"); +const Server = new FosscordServer({ port: 3001 }); +global.server = Server; +module.exports = async () => { + try { + fs.unlinkSync(path.join(__dirname, "..", "database.db")); + } catch {} + return await Server.start(); +}; + +// afterAll(async () => { +// return await Server.stop(); +// }); diff --git a/api/jest/setup.js b/api/jest/setup.js index abc485ae..bd535866 100644 --- a/api/jest/setup.js +++ b/api/jest/setup.js @@ -1,2 +1,2 @@ -jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); -jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); +// jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); +// jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 678ca64c..35ea43ba 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -28,12 +28,11 @@ declare global { } } -export type RouteSchema = string; // typescript interface name -export type RouteResponse = { status?: number; body?: RouteSchema; headers?: Record }; +export type RouteResponse = { status?: number; body?: `${string}Response`; headers?: Record }; export interface RouteOptions { permission?: PermissionResolvable; - body?: RouteSchema; + body?: `${string}Schema`; // typescript interface name response?: RouteResponse; example?: { body?: any; diff --git a/api/tests/automatic.test.js b/api/tests/automatic.test.js deleted file mode 100644 index 2d0a9fcb..00000000 --- a/api/tests/automatic.test.js +++ /dev/null @@ -1,2 +0,0 @@ -// TODO: check every route based on route() paramters: https://github.com/fosscord/fosscord-server/issues/308 -// TODO: check every route with different database engine diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts new file mode 100644 index 00000000..e913e0dc --- /dev/null +++ b/api/tests/routes.test.ts @@ -0,0 +1,54 @@ +// TODO: check every route based on route() parameters: https://github.com/fosscord/fosscord-server/issues/308 +// TODO: check every route with different database engine + +import getRouteDescriptions from "../jest/getRouteDescriptions"; +import supertest, { Response } from "supertest"; +import path from "path"; +import fs from "fs"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +const request = supertest("http://localhost:3001/api"); + +const SchemaPath = path.join(__dirname, "..", "assets", "responses.json"); +const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); +export const ajv = new Ajv({ + allErrors: true, + parseDate: true, + allowDate: true, + schemas, + messages: true, + strict: true, + strictRequired: true +}); +addFormats(ajv); + +describe("Automatic unit tests with route description middleware", () => { + const routes = getRouteDescriptions(); + + routes.forEach((route, pathAndMethod) => { + const [path, method] = pathAndMethod.split("|"); + test(path, (done) => { + if (!route.example) { + console.log(`Route ${path} is missing the example property`); + return done(); + } + if (!route.response) { + console.log(`Route ${path} is missing the response property`); + return done(); + } + const urlPath = path || route.example?.path; + const validate = ajv.getSchema(route.response.body); + if (!validate) return done(new Error(`Response schema ${route.response.body} not found`)); + + request[method](urlPath) + .expect(route.response.status) + .expect((err: any, res: Response) => { + if (err) return done(err); + const valid = validate(res.body); + if (!valid) return done(validate.errors); + + return done(); + }); + }); + }); +}); From 97e0c8709bdda98d92b97f1085bda505880f8068 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Sat, 18 Sep 2021 10:44:25 +0200 Subject: [PATCH 52/90] Fix gateway not listening for new channels events --- gateway/src/listener/listener.ts | 11 +++-------- util/src/services/ChannelService.ts | 15 ++++++--------- util/src/util/Event.ts | 1 - 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index 16803639..35841312 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -116,7 +116,7 @@ async function consume(this: WebSocket, opts: EventOpts) { .has("VIEW_CHANNEL") ) return; - break; + //No break needed here, we need to call the listenEvent function below case "GUILD_CREATE": this.events[id] = await listenEvent(id, consumer, listenOpts); break; @@ -193,16 +193,11 @@ async function consume(this: WebSocket, opts: EventOpts) { break; } - let aa = { + Send(this, { op: OPCODES.Dispatch, t: event, d: data, s: this.sequence++, - } - - //TODO remove before PR merge - console.log(aa) - - Send(this, aa); + }); opts.acknowledge?.(); } diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts index 8f57a28a..aa021a4a 100644 --- a/util/src/services/ChannelService.ts +++ b/util/src/services/ChannelService.ts @@ -84,27 +84,24 @@ export class ChannelService { return } - let channel_dto = null; + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); //If the owner leave we make the first recipient in the list the new owner if (channel.owner_id === user_id) { channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner? - channel_dto = await DmChannelDTO.from(channel, [user_id]) await emitEvent({ event: "CHANNEL_UPDATE", - data: channel_dto, + data: await DmChannelDTO.from(channel, [user_id]), channel_id: channel.id }); } await channel.save() - await emitEvent({ - event: "CHANNEL_DELETE", - data: channel_dto !== null ? channel_dto : await DmChannelDTO.from(channel, [user_id]), - user_id: user_id - }); - await emitEvent({ event: "CHANNEL_RECIPIENT_REMOVE", data: { channel_id: channel.id, diff --git a/util/src/util/Event.ts b/util/src/util/Event.ts index ae296df9..765e5fc7 100644 --- a/util/src/util/Event.ts +++ b/util/src/util/Event.ts @@ -5,7 +5,6 @@ import { EVENT, Event } from "../interfaces"; const events = new EventEmitter(); export async function emitEvent(payload: Omit) { - console.log(payload) //TODO remove before merge const id = (payload.channel_id || payload.user_id || payload.guild_id) as string; if (!id) return console.error("event doesn't contain any id", payload); From a3484e64e45764775c814739efe73759d5bdeaeb Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 11:56:06 +0200 Subject: [PATCH 53/90] :sparkles: route middleware test option --- api/jest/getRouteDescriptions.ts | 6 ++++-- api/src/routes/users/#id/profile.ts | 2 +- api/src/util/route.ts | 6 +++--- api/tests/routes.test.ts | 11 ++++------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/api/jest/getRouteDescriptions.ts b/api/jest/getRouteDescriptions.ts index d7d6e0c6..33922899 100644 --- a/api/jest/getRouteDescriptions.ts +++ b/api/jest/getRouteDescriptions.ts @@ -2,6 +2,7 @@ import { traverseDirectory } from "lambert-server"; import path from "path"; import express from "express"; import * as RouteUtility from "../dist/util/route"; +import { RouteOptions } from "../dist/util/route"; const Router = express.Router; const routes = new Map(); @@ -12,9 +13,10 @@ const methods = ["get", "post", "put", "delete", "patch"]; function registerPath(file, method, prefix, path, ...args) { const urlPath = prefix + path; const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); - const opts = args.find((x) => typeof x === "object"); + const opts: RouteOptions = args.find((x) => typeof x === "object"); if (opts) { - routes.set(urlPath + "|" + method, opts); + routes.set(urlPath + "|" + method, opts); // @ts-ignore + opts.file = sourceFile; // console.log(method, urlPath, opts); } else { console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args); diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index d60c4f86..d099bce7 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -11,7 +11,7 @@ export interface UserProfileResponse { premium_since?: Date; } -router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req: Request, res: Response) => { +router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => { if (req.params.id === "@me") req.params.id = req.user_id; const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 35ea43ba..9ef92c3a 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -33,11 +33,11 @@ export type RouteResponse = { status?: number; body?: `${string}Response`; heade export interface RouteOptions { permission?: PermissionResolvable; body?: `${string}Schema`; // typescript interface name - response?: RouteResponse; - example?: { + test?: { + response?: RouteResponse; body?: any; path?: string; - event?: EventData; + event?: EventData | EventData[]; headers?: Record; }; } diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index e913e0dc..4cb7e6bc 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -3,13 +3,13 @@ import getRouteDescriptions from "../jest/getRouteDescriptions"; import supertest, { Response } from "supertest"; -import path from "path"; +import { join } from "path"; import fs from "fs"; import Ajv from "ajv"; import addFormats from "ajv-formats"; const request = supertest("http://localhost:3001/api"); -const SchemaPath = path.join(__dirname, "..", "assets", "responses.json"); +const SchemaPath = join(__dirname, "..", "assets", "responses.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); export const ajv = new Ajv({ allErrors: true, @@ -27,13 +27,10 @@ describe("Automatic unit tests with route description middleware", () => { routes.forEach((route, pathAndMethod) => { const [path, method] = pathAndMethod.split("|"); + test(path, (done) => { if (!route.example) { - console.log(`Route ${path} is missing the example property`); - return done(); - } - if (!route.response) { - console.log(`Route ${path} is missing the response property`); + console.log(`${(route as any).file}\nrouter.${method} is missing the test property`); return done(); } const urlPath = path || route.example?.path; From 55ae0d6ae9289aab06a68033bb5782adcba74485 Mon Sep 17 00:00:00 2001 From: The Arcane Brony Date: Fri, 17 Sep 2021 15:35:35 +0200 Subject: [PATCH 54/90] Add /users/@me/connections --- api/src/routes/users/@me/connections.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 api/src/routes/users/@me/connections.ts diff --git a/api/src/routes/users/@me/connections.ts b/api/src/routes/users/@me/connections.ts new file mode 100644 index 00000000..e4fbe1e4 --- /dev/null +++ b/api/src/routes/users/@me/connections.ts @@ -0,0 +1,10 @@ +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + //TODO + res.json([]).status(200); +}); + +export default router; \ No newline at end of file From e628c92a440ee43f19f02a992b8b10575a8a6978 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 16:24:42 +0200 Subject: [PATCH 55/90] Update connections.ts --- api/src/routes/users/@me/connections.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/src/routes/users/@me/connections.ts b/api/src/routes/users/@me/connections.ts index e4fbe1e4..411e95bf 100644 --- a/api/src/routes/users/@me/connections.ts +++ b/api/src/routes/users/@me/connections.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; From 0d3562de76db5cc003fc55a8f92705ba5bd54540 Mon Sep 17 00:00:00 2001 From: The Arcane Brony Date: Sat, 18 Sep 2021 16:13:15 +0000 Subject: [PATCH 56/90] Add request logging (with env var: log-requests) Request logging is enabled when the log-request environment variable is set. --- api/package-lock.json | Bin 807368 -> 812052 bytes api/package.json | 2 ++ api/src/Server.ts | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/api/package-lock.json b/api/package-lock.json index fd9c68b4db350559571acf3f3bded2c26f32fb27..499c612e44d82431c0a952b4d641c34ecb1bf02e 100644 GIT binary patch delta 1722 zcmd6mPi!Jp9LFu-lP-}^)$Pfmq{{RVwN`Kl2;&TbV2AeTYOn$^f(&zrEw32#VIk?rX-IG z^pD^fpnN-av8&v)?fr4*STSeRQg%MmY=F$M(F%?UBW~sY)`|mPX9sXEj@XkCKPo6q zCMP9QNs(<9Gi{!)p*bzsXtcr-n?h5iwzws(GOm!a?%E`1X^V1YlW|E%5gSoH>Wfvf z> z?%no`{d^xjjTFMcuEnzO^eFkqv%~?eL$(CP3k7eITU3i~Mnv&Kq^0>>DK1;_NE;O4 zZV5h5TjSd7Vo4&MWh^0wNQz-Io08NaHJokB(kd-Bs$xWSr9(WciAy}bH45|Dvv`Kz z;Oc_I>A*q%7xQbz;noRo4zgd6rTL``zCU>D5?EdN$j@-Pr-`T!ZXMB~3A?KNyyE zF0MCnwU9I6Qc*vrlq1eylvwNdOIVYPwNWPN!Zy)he3RW`$+o{EXpDy{d-J|pK*LBa zOxD&_t{9MNu}Yl+vn%kc;QpFvW_z+`7?iO$Su>Hp{XZBxoEV=5??;d!W0PQK89{nX z2>G0UQqmIVLK>L;0GS4N&CpbDK-(M8_Q@?|Q0Csrt?}rG|1D6%o+?^m$^ wG3)mGRcxlWwi}1B&q-=$+szKd9NXD;bNY8}@AKrEX4fv<$+ca$lbi8209r04P5=M^ diff --git a/api/package.json b/api/package.json index ad959e57..98b15787 100644 --- a/api/package.json +++ b/api/package.json @@ -62,6 +62,7 @@ }, "dependencies": { "@fosscord/util": "file:../util", + "@types/morgan": "^1.9.3", "ajv": "8.6.2", "ajv-formats": "^2.1.1", "amqplib": "^0.8.0", @@ -85,6 +86,7 @@ "mongoose": "^5.12.3", "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", + "morgan": "^1.10.0", "multer": "^1.4.2", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", diff --git a/api/src/Server.ts b/api/src/Server.ts index b9ca3fba..318d7094 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -11,6 +11,7 @@ import path from "path"; import { initRateLimits } from "./middlewares/RateLimit"; import TestClient from "./middlewares/TestClient"; import { initTranslation } from "./middlewares/Translation"; +import morgan from "morgan"; export interface FosscordServerOptions extends ServerOptions {} @@ -36,6 +37,11 @@ export class FosscordServer extends Server { await Config.init(); await initEvent(); + let logRequests = process.env["log-requests"] != undefined; + if(logRequests) { + this.app.use(morgan("combined")); + } + this.app.use(CORS); this.app.use(BodyParser({ inflate: true, limit: "10mb" })); @@ -65,6 +71,9 @@ export class FosscordServer extends Server { this.app.use(ErrorHandler); TestClient(this.app); + if(logRequests){ + console.log("Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'log-requests' environment variable!"); + } return super.start(); } } From ada95b6c3938f9aa644aebc2bb26dbb073aa3324 Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Sat, 18 Sep 2021 18:36:29 +0200 Subject: [PATCH 57/90] Removed ChannelService, more fixes --- api/src/routes/channels/#channel_id/index.ts | 4 +- .../channels/#channel_id/messages/index.ts | 6 +- .../routes/channels/#channel_id/recipients.ts | 6 +- api/src/routes/users/@me/channels.ts | 4 +- gateway/src/listener/listener.ts | 2 +- util/src/entities/Channel.ts | 124 ++++++++++++++++-- util/src/index.ts | 1 - util/src/services/ChannelService.ts | 118 ----------------- util/src/services/index.ts | 1 - util/src/util/Array.ts | 3 + util/src/util/index.ts | 1 + 11 files changed, 131 insertions(+), 139 deletions(-) delete mode 100644 util/src/services/ChannelService.ts delete mode 100644 util/src/services/index.ts create mode 100644 util/src/util/Array.ts diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 70dd3994..3f434f5e 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelService, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { handleFile, route } from "@fosscord/api"; @@ -28,7 +28,7 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request ]); } else if (channel.type === ChannelType.GROUP_DM) { - await ChannelService.removeRecipientFromChannel(channel, req.user_id) + await Channel.removeRecipientFromChannel(channel, req.user_id) } else { //TODO messages in this channel should be deleted before deleting the channel await Promise.all([ diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index bb610a6a..cde14164 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent, Recipient } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { handleMessage, postHandleMessage, route } from "@fosscord/api"; import multer from "multer"; @@ -150,7 +150,6 @@ router.post( return res.status(400).json(error); } } - //TODO querying the DB at every message post should be avoided, caching maybe? const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] }) const embeds = []; @@ -184,7 +183,8 @@ router.post( } } - await Promise.all(channel.recipients!.map(async r => { + //Only one recipients should be closed here, since in group DMs the recipient is deleted not closed + await Promise.all(channel.recipients!.filter(r => r.closed).map(async r => { r.closed = false; return await r.save() })); diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts index d88b38f3..c7beeee8 100644 --- a/api/src/routes/channels/#channel_id/recipients.ts +++ b/api/src/routes/channels/#channel_id/recipients.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { Channel, ChannelRecipientAddEvent, ChannelService, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util"; +import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util"; const router: Router = Router(); @@ -13,7 +13,7 @@ router.put("/:user_id", async (req: Request, res: Response) => { user_id ].unique() - const new_channel = await ChannelService.createDMChannel(recipients, req.user_id) + const new_channel = await Channel.createDMChannel(recipients, req.user_id) return res.status(201).json(new_channel); } else { if (channel.recipients!.map(r => r.user_id).includes(user_id)) { @@ -49,7 +49,7 @@ router.delete("/:user_id", async (req: Request, res: Response) => { throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error? } - await ChannelService.removeRecipientFromChannel(channel, user_id) + await Channel.removeRecipientFromChannel(channel, user_id) return res.sendStatus(204); }); diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 873ff245..b5782eca 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { Recipient, ChannelService, DmChannelDTO } from "@fosscord/util"; +import { Recipient, DmChannelDTO, Channel } from "@fosscord/util"; import { route } from "@fosscord/api"; const router: Router = Router(); @@ -16,7 +16,7 @@ export interface DmChannelCreateSchema { router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; - res.json(await ChannelService.createDMChannel(body.recipients, req.user_id, body.name)); + res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name)); }); export default router; diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index 35841312..ae13cca7 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -32,7 +32,7 @@ export async function setupListener(this: WebSocket) { }); const guilds = members.map((x) => x.guild); const recipients = await Recipient.find({ - where: { user_id: this.user_id }, + where: { user_id: this.user_id, closed: false }, relations: ["channel"], }); const dm_channels = recipients.map((x) => x.channel); diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index aa2bfab3..ea632778 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -1,11 +1,13 @@ import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; -import { User } from "./User"; +import { PublicUserProjection, User } from "./User"; import { HTTPError } from "lambert-server"; -import { emitEvent, getPermission, Snowflake } from "../util"; -import { ChannelCreateEvent } from "../interfaces"; +import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial } from "../util"; +import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; import { Recipient } from "./Recipient"; +import { DmChannelDTO } from "../dtos"; +import { Message } from "./Message"; export enum ChannelType { GUILD_TEXT = 0, // a text channel within a server @@ -97,7 +99,6 @@ export class Channel extends BaseClass { @Column({ nullable: true }) topic?: string; - // TODO: DM channel static async createChannel( channel: Partial, user_id: string = "0", @@ -150,16 +151,123 @@ export class Channel extends BaseClass { new Channel(channel).save(), !opts?.skipEventEmit ? emitEvent({ - event: "CHANNEL_CREATE", - data: channel, - guild_id: channel.guild_id, - } as ChannelCreateEvent) + event: "CHANNEL_CREATE", + data: channel, + guild_id: channel.guild_id, + } as ChannelCreateEvent) : Promise.resolve(), ]); return channel; } + static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { + recipients = recipients.unique().filter((x) => x !== creator_user_id); + const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); + + // TODO: check config for max number of recipients + if (otherRecipientsUsers.length !== recipients.length) { + throw new HTTPError("Recipient/s not found"); + } + + const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; + + let channel = null; + + const channelRecipients = [...recipients, creator_user_id] + + const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] }) + + for (let ur of userRecipients) { + let re = ur.channel.recipients!.map(r => r.user_id) + if (re.length === channelRecipients.length) { + if (containsAll(re, channelRecipients)) { + if (channel == null) { + channel = ur.channel + await ur.assign({ closed: false }).save() + } + } + } + } + + if (channel == null) { + name = trimSpecial(name); + + channel = await new Channel({ + name, + type, + owner_id: (type === ChannelType.DM ? undefined : creator_user_id), + created_at: new Date(), + last_message_id: null, + recipients: channelRecipients.map((x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })), + }).save(); + } + + + const channel_dto = await DmChannelDTO.from(channel) + + if (type === ChannelType.GROUP_DM) { + + for (let recipient of channel.recipients!) { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id + }) + } + } else { + await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id }); + } + + return channel_dto.excludedRecipients([creator_user_id]) + } + + static async removeRecipientFromChannel(channel: Channel, user_id: string) { + await Recipient.delete({ channel_id: channel.id, user_id: user_id }) + channel.recipients = channel.recipients?.filter(r => r.user_id !== user_id) + + if (channel.recipients?.length === 0) { + await Channel.deleteChannel(channel); + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + return + } + + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + + //If the owner leave we make the first recipient in the list the new owner + if (channel.owner_id === user_id) { + channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner? + await emitEvent({ + event: "CHANNEL_UPDATE", + data: await DmChannelDTO.from(channel, [user_id]), + channel_id: channel.id + }); + } + + await channel.save() + + await emitEvent({ + event: "CHANNEL_RECIPIENT_REMOVE", data: { + channel_id: channel.id, + user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) + }, channel_id: channel.id + } as ChannelRecipientRemoveEvent); + } + + static async deleteChannel(channel: Channel) { + await Message.delete({ channel_id: channel.id }) //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util + //TODO before deleting the channel we should check and delete other relations + await Channel.delete({ id: channel.id }) + } + isDm() { return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM } diff --git a/util/src/index.ts b/util/src/index.ts index 538bfdd1..fc00d46b 100644 --- a/util/src/index.ts +++ b/util/src/index.ts @@ -4,7 +4,6 @@ import "reflect-metadata"; export * from "./util/index"; export * from "./interfaces/index"; export * from "./entities/index"; -export * from "./services/index"; export * from "./dtos/index"; // import Config from "../util/Config"; diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts deleted file mode 100644 index aa021a4a..00000000 --- a/util/src/services/ChannelService.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Channel, ChannelType, Message, PublicUserProjection, Recipient, User } from "../entities"; -import { HTTPError } from "lambert-server"; -import { emitEvent, trimSpecial } from "../util"; -import { DmChannelDTO } from "../dtos"; -import { ChannelRecipientRemoveEvent } from "../interfaces"; - -export function checker(arr: any[], target: any[]) { - return target.every(v => arr.includes(v)); -} - -export class ChannelService { - public static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { - recipients = recipients.unique().filter((x) => x !== creator_user_id); - const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); - - // TODO: check config for max number of recipients - if (otherRecipientsUsers.length !== recipients.length) { - throw new HTTPError("Recipient/s not found"); - } - - const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; - - let channel = null; - - const channelRecipients = [...recipients, creator_user_id] - - const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] }) - - for (let ur of userRecipients) { - let re = ur.channel.recipients!.map(r => r.user_id) - if (re.length === channelRecipients.length) { - if (checker(re, channelRecipients)) { - if (channel == null) { - channel = ur.channel - await ur.assign({ closed: false }).save() - } - } - } - } - - if (channel == null) { - name = trimSpecial(name); - - channel = await new Channel({ - name, - type, - owner_id: (type === ChannelType.DM ? undefined : creator_user_id), - created_at: new Date(), - last_message_id: null, - recipients: channelRecipients.map((x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })), - }).save(); - } - - - const channel_dto = await DmChannelDTO.from(channel) - - if (type === ChannelType.GROUP_DM) { - - for (let recipient of channel.recipients!) { - await emitEvent({ - event: "CHANNEL_CREATE", - data: channel_dto.excludedRecipients([recipient.user_id]), - user_id: recipient.user_id - }) - } - } else { - await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id }); - } - - return channel_dto.excludedRecipients([creator_user_id]) - } - - public static async removeRecipientFromChannel(channel: Channel, user_id: string) { - await Recipient.delete({ channel_id: channel.id, user_id: user_id }) - channel.recipients = channel.recipients?.filter(r => r.user_id !== user_id) - - if (channel.recipients?.length === 0) { - await ChannelService.deleteChannel(channel); - await emitEvent({ - event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id - }); - return - } - - await emitEvent({ - event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id - }); - - //If the owner leave we make the first recipient in the list the new owner - if (channel.owner_id === user_id) { - channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner? - await emitEvent({ - event: "CHANNEL_UPDATE", - data: await DmChannelDTO.from(channel, [user_id]), - channel_id: channel.id - }); - } - - await channel.save() - - await emitEvent({ - event: "CHANNEL_RECIPIENT_REMOVE", data: { - channel_id: channel.id, - user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) - }, channel_id: channel.id - } as ChannelRecipientRemoveEvent); - } - - public static async deleteChannel(channel: Channel) { - await Message.delete({ channel_id: channel.id }) //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util - //TODO before deleting the channel we should check and delete other relations - await Channel.delete({ id: channel.id }) - } -} diff --git a/util/src/services/index.ts b/util/src/services/index.ts deleted file mode 100644 index c012a208..00000000 --- a/util/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ChannelService"; diff --git a/util/src/util/Array.ts b/util/src/util/Array.ts new file mode 100644 index 00000000..27f7c961 --- /dev/null +++ b/util/src/util/Array.ts @@ -0,0 +1,3 @@ +export function containsAll(arr: any[], target: any[]) { + return target.every(v => arr.includes(v)); +} \ No newline at end of file diff --git a/util/src/util/index.ts b/util/src/util/index.ts index 4e92f017..2de26fc7 100644 --- a/util/src/util/index.ts +++ b/util/src/util/index.ts @@ -12,3 +12,4 @@ export * from "./RabbitMQ"; export * from "./Regex"; export * from "./Snowflake"; export * from "./String"; +export * from "./Array"; From 0d41e18065131958de98c6dc86a564893115ebff Mon Sep 17 00:00:00 2001 From: The Arcane Brony Date: Sat, 18 Sep 2021 18:43:00 +0000 Subject: [PATCH 58/90] Add response code white/blacklisting --- api/src/Server.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/api/src/Server.ts b/api/src/Server.ts index 318d7094..bda19db5 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -1,3 +1,4 @@ +import { OptionsJson } from 'body-parser'; import "missing-native-js-functions"; import { Connection } from "mongoose"; import { Server, ServerOptions } from "lambert-server"; @@ -37,9 +38,27 @@ export class FosscordServer extends Server { await Config.init(); await initEvent(); + + /* + DOCUMENTATION: uses log-requests environment variable + + # only log 200 and 204 + log-requests=200 204 + # log everything except 200 and 204 + log-requests=-200 204 + # log all requests + log-requests=- + */ + let logRequests = process.env["log-requests"] != undefined; if(logRequests) { - this.app.use(morgan("combined")); + this.app.use(morgan("combined", { + skip: (req, res) => { + var skip = !(process.env["log-requests"]?.includes(res.statusCode.toString()) ?? false); + if(process.env["log-requests"]?.charAt(0) == '-') skip = !skip; + return skip; + } + })); } this.app.use(CORS); From 157f29536ac43c2d423e218eea78ee58bf7e80f6 Mon Sep 17 00:00:00 2001 From: The Arcane Brony Date: Sat, 18 Sep 2021 21:01:06 +0200 Subject: [PATCH 59/90] Fix environment variable name to work on non-Windows platforms --- api/src/Server.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/api/src/Server.ts b/api/src/Server.ts index bda19db5..4a226d12 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -40,22 +40,22 @@ export class FosscordServer extends Server { /* - DOCUMENTATION: uses log-requests environment variable + DOCUMENTATION: uses LOG_REQUESTS environment variable # only log 200 and 204 - log-requests=200 204 + LOG_REQUESTS=200 204 # log everything except 200 and 204 - log-requests=-200 204 + LOG_REQUESTS=-200 204 # log all requests - log-requests=- + LOG_REQUESTS=- */ - - let logRequests = process.env["log-requests"] != undefined; + + let logRequests = process.env["LOG_REQUESTS"] != undefined; if(logRequests) { this.app.use(morgan("combined", { skip: (req, res) => { - var skip = !(process.env["log-requests"]?.includes(res.statusCode.toString()) ?? false); - if(process.env["log-requests"]?.charAt(0) == '-') skip = !skip; + var skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); + if(process.env["LOG_REQUESTS"]?.charAt(0) == '-') skip = !skip; return skip; } })); @@ -91,7 +91,7 @@ export class FosscordServer extends Server { TestClient(this.app); if(logRequests){ - console.log("Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'log-requests' environment variable!"); + console.log("Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!"); } return super.start(); } From 75e948b62bc6bee57d34c0b5a6122043c881fbd7 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:43:25 +0200 Subject: [PATCH 60/90] :art: user is verified by default --- api/src/routes/auth/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index efe91625..c0b0e18a 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -202,7 +202,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re premium_type: 2, bio: "", mfa_enabled: false, - verified: false, + verified: true, disabled: false, deleted: false, email: email, From c50648ddccfef4f06dbd46fb8aaede9f9709e6e8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:44:49 +0200 Subject: [PATCH 61/90] :truck: move handleFile to cdn --- api/src/routes/channels/#channel_id/messages/index.ts | 3 +-- api/src/routes/guilds/#guild_id/index.ts | 3 +-- api/src/routes/guilds/index.ts | 5 ++--- api/src/routes/users/@me/index.ts | 3 +-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index ec93649e..07bfc22d 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,10 +1,9 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, Embed, getPermission, Message, uploadFile } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; import multer from "multer"; import { sendMessage } from "@fosscord/api"; -import { uploadFile } from "@fosscord/api"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; const router: Router = Router(); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 63000b84..d8ee86ff 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; -import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; +import { emitEvent, getPermission, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; -import { handleFile } from "@fosscord/api"; import "missing-native-js-functions"; import { GuildCreateSchema } from "../index"; diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 3612ca82..abde147d 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,7 +1,6 @@ import { Router, Request, Response } from "express"; -import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; -import { handleFile, route } from "@fosscord/api"; -import { DiscordApiErrors } from "@fosscord/util"; +import { Role, Guild, Snowflake, Config, Member, Channel, DiscordApiErrors, handleFile } from "@fosscord/util"; +import { route } from "@fosscord/api"; import { ChannelModifySchema } from "../channels/#channel_id"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index da2f3348..835aab4d 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,7 +1,6 @@ import { Router, Request, Response } from "express"; -import { User, PrivateUserProjection, emitEvent, UserUpdateEvent } from "@fosscord/util"; +import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { handleFile } from "@fosscord/api"; const router: Router = Router(); From c220f27894afab44310fb0428790021c9c206a9e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:45:09 +0200 Subject: [PATCH 62/90] :bug: fix bundle debug --- bundle/.vscode/launch.json | 6 ++++-- bundle/package-lock.json | Bin 72597 -> 82022 bytes bundle/package.json | 1 + bundle/src/start.ts | 8 +++++--- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/bundle/.vscode/launch.json b/bundle/.vscode/launch.json index 0b5d2287..917f2a93 100644 --- a/bundle/.vscode/launch.json +++ b/bundle/.vscode/launch.json @@ -10,9 +10,11 @@ "request": "launch", "name": "Launch server bundle", "program": "${workspaceFolder}/dist/start.js", + "runtimeArgs": ["-r", "./tsconfig-paths-bootstrap.js"], "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "envFile": "${workspaceFolder}/.env" + "outFiles": ["${workspaceFolder}/dist/**/*.js", "${workspaceFolder}/node_modules/@fosscord/**/*.js"], + "envFile": "${workspaceFolder}/.env", + "outDir": "${workspaceFolder}/dist" } ] } diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 718cc969b5e4ecf07d132ad70a37c1b80c1a96d3..3199f7d8e3ad0dc33b924269fb3f20f41a943f21 100644 GIT binary patch delta 5819 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 { - console.log(`[Worker] died with pid: ${worker.process.pid} , restarting ...`); + console.log( + `[Worker] died with pid: ${worker.process.pid} , restarting ...` + ); cluster.fork(); }); })(); } else { - require("./Server.js"); + require("./Server"); } From 994406e5cea886444838ce71c4aaee46663be1a9 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:46:22 +0200 Subject: [PATCH 63/90] :art: remove deleteMessageAttachments and move to entity --- .../#channel_id/messages/#message_id/index.ts | 3 --- api/src/util/Attachments.ts | 12 ------------ api/src/util/index.ts | 1 - util/src/entities/Attachment.ts | 9 ++++++++- 4 files changed, 8 insertions(+), 17 deletions(-) delete mode 100644 api/src/util/Attachments.ts diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index b5220fab..7f7de264 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -3,7 +3,6 @@ import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; import { handleMessage, postHandleMessage } from "@fosscord/api"; import { MessageCreateSchema } from "../index"; -import { deleteMessageAttachments } from "@fosscord/api/util/Attachments"; const router = Router(); // TODO: message content/embed string length limit @@ -34,7 +33,6 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE }); await Promise.all([ - await deleteMessageAttachments(message_id, new_message.attachments), //This delete all the attachments not in the array new_message!.save(), await emitEvent({ event: "MESSAGE_UPDATE", @@ -60,7 +58,6 @@ router.delete("/", route({}), async (req: Request, res: Response) => { permission.hasThrow("MANAGE_MESSAGES"); } - await deleteMessageAttachments(message_id); await Message.delete({ id: message_id }); await emitEvent({ diff --git a/api/src/util/Attachments.ts b/api/src/util/Attachments.ts deleted file mode 100644 index addda97f..00000000 --- a/api/src/util/Attachments.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Attachment } from "@fosscord/util"; -import { deleteFile } from "@fosscord/api"; -import { URL } from "url"; - -export async function deleteMessageAttachments(messageId: string, keep?: Attachment[]) { - let attachments = await Attachment.find({ message_id: messageId }); - if (keep) - attachments = attachments.filter(x => !keep.map(k => k.id).includes(x.id)); - await Promise.all(attachments.map(a => a.remove())); - - attachments.forEach(a => deleteFile((new URL(a.url)).pathname)); //We don't need to await since this is done on the cdn -} diff --git a/api/src/util/index.ts b/api/src/util/index.ts index 4b1e8e77..3e47ce4e 100644 --- a/api/src/util/index.ts +++ b/api/src/util/index.ts @@ -1,5 +1,4 @@ export * from "./Base64"; -export * from "./cdn"; export * from "./FieldError"; export * from "./ipAddress"; export * from "./Message"; diff --git a/util/src/entities/Attachment.ts b/util/src/entities/Attachment.ts index ca893400..82c5ecf5 100644 --- a/util/src/entities/Attachment.ts +++ b/util/src/entities/Attachment.ts @@ -1,4 +1,6 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { URL } from "url"; +import { deleteFile } from "../util/cdn"; import { BaseClass } from "./BaseClass"; @Entity("attachments") @@ -31,4 +33,9 @@ export class Attachment extends BaseClass { @JoinColumn({ name: "message_id" }) @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments) message: import("./Message").Message; + + @BeforeRemove() + onDelete() { + return deleteFile(new URL(this.url).pathname); + } } From 705206ec1b020bbae2b92de1e98e8a7609a6c850 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:47:38 +0200 Subject: [PATCH 64/90] :art: add orphanedRowAction and cascade onDelete to entities --- util/package-lock.json | Bin 446397 -> 459190 bytes util/package.json | 2 + util/src/entities/Channel.ts | 71 +++++++++++++++++++++++++++++++---- util/src/entities/Guild.ts | 54 +++++++++++++++++++++----- util/src/entities/Message.ts | 11 ++++-- util/src/util/cdn.ts | 54 ++++++++++++++++++++++++++ util/src/util/index.ts | 1 + 7 files changed, 173 insertions(+), 20 deletions(-) create mode 100644 util/src/util/cdn.ts diff --git a/util/package-lock.json b/util/package-lock.json index 4b0f1f1517e9ee45bfdacc0a32ed79d16784a74c..e5a62d0f36a1fa06c3e0b137b0fd5e4f3349a0d8 100644 GIT binary patch delta 5089 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 UTdsz2A;@5N&2aB+s#^m80lVCg3;+NC delta 219 zcmV<103`pmgB-n|8?fgAv;P630h1782(!rp^oz6Em5vCrZ=Z1lvm>Q@1G6uzZ6C8> z%v(IS>j?qm9k=2x0Y@N*TTlVFTTlYTF1Im(0<*!B+R`7FAdv>2mwYe-4!5900~sur z>9hk4w>!560|A$Cv;!Bno^k`w)VD@W1S1HyWL5;`1Gjuz1W*H)7mx)Gw?JY9X&Sd+ zeFWGvw=1&*ni-b{r~((4TLc6VmmnYkHJ3jx0eH72)C6c7w}bEm_WPGHfdbRFAMXV# Vp_kM{1HQMvkp{jbw=TB^h)U^@SL^@) diff --git a/util/package.json b/util/package.json index 751484af..2fa93f9b 100644 --- a/util/package.json +++ b/util/package.json @@ -31,6 +31,7 @@ "@types/amqplib": "^0.8.1", "@types/jsonwebtoken": "^8.5.0", "@types/mongoose-autopopulate": "^0.10.1", + "@types/multer": "^1.4.7", "@types/node": "^14.17.9", "@types/node-fetch": "^2.5.12", "jest": "^27.0.6" @@ -44,6 +45,7 @@ "jsonwebtoken": "^8.5.1", "lambert-server": "^1.2.10", "missing-native-js-functions": "^1.2.15", + "multer": "^1.4.3", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "pg": "^8.7.1", diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index fc954f63..0196fb3e 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -1,12 +1,26 @@ -import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { + Column, + Entity, + FindConditions, + JoinColumn, + ManyToOne, + ObjectID, + OneToMany, + RelationId, + RemoveOptions, +} from "typeorm"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; -import { Message } from "./Message"; import { User } from "./User"; import { HTTPError } from "lambert-server"; import { emitEvent, getPermission, Snowflake } from "../util"; import { ChannelCreateEvent } from "../interfaces"; import { Recipient } from "./Recipient"; +import { Message } from "./Message"; +import { ReadState } from "./ReadState"; +import { Invite } from "./Invite"; +import { VoiceState } from "./VoiceState"; +import { Webhook } from "./Webhook"; export enum ChannelType { GUILD_TEXT = 0, // a text channel within a server @@ -31,20 +45,22 @@ export class Channel extends BaseClass { @Column({ nullable: true }) name?: string; + @Column({ type: "text", nullable: true }) + icon?: string | null; + @Column({ type: "simple-enum", enum: ChannelType }) type: ChannelType; - @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { cascade: true }) + @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) recipients?: Recipient[]; @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.last_message) last_message_id: string; - @JoinColumn({ name: "last_message_id" }) - @ManyToOne(() => Message) - last_message?: Message; - @Column({ nullable: true }) @RelationId((channel: Channel) => channel.guild) guild_id?: string; @@ -100,6 +116,41 @@ export class Channel extends BaseClass { @Column({ nullable: true }) topic?: string; + @OneToMany(() => Invite, (invite: Invite) => invite.channel, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + invites?: Invite[]; + + @OneToMany(() => Message, (message: Message) => message.channel, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + messages?: Message[]; + + @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + voice_states?: VoiceState[]; + + @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + read_states?: ReadState[]; + + @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + webhooks?: Webhook[]; + // TODO: DM channel static async createChannel( channel: Partial, @@ -162,6 +213,10 @@ export class Channel extends BaseClass { return channel; } + + isDm() { + return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM; + } } export interface ChannelPermissionOverwrite { diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts index 7b5d2908..d46d2161 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts @@ -81,7 +81,11 @@ export class Guild extends BaseClass { // application?: string; @JoinColumn({ name: "ban_ids" }) - @OneToMany(() => Ban, (ban: Ban) => ban.guild) + @OneToMany(() => Ban, (ban: Ban) => ban.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) bans: Ban[]; @Column({ nullable: true }) @@ -124,15 +128,27 @@ export class Guild extends BaseClass { @Column({ nullable: true }) presence_count?: number; // users online - @OneToMany(() => Member, (member: Member) => member.guild) + @OneToMany(() => Member, (member: Member) => member.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) members: Member[]; @JoinColumn({ name: "role_ids" }) - @OneToMany(() => Role, (role: Role) => role.guild) + @OneToMany(() => Role, (role: Role) => role.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) roles: Role[]; @JoinColumn({ name: "channel_ids" }) - @OneToMany(() => Channel, (channel: Channel) => channel.guild) + @OneToMany(() => Channel, (channel: Channel) => channel.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) channels: Channel[]; @Column({ nullable: true }) @@ -144,23 +160,43 @@ export class Guild extends BaseClass { template: Template; @JoinColumn({ name: "emoji_ids" }) - @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild) + @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) emojis: Emoji[]; @JoinColumn({ name: "sticker_ids" }) - @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild) + @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) stickers: Sticker[]; @JoinColumn({ name: "invite_ids" }) - @OneToMany(() => Invite, (invite: Invite) => invite.guild) + @OneToMany(() => Invite, (invite: Invite) => invite.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) invites: Invite[]; @JoinColumn({ name: "voice_state_ids" }) - @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild) + @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) voice_states: VoiceState[]; @JoinColumn({ name: "webhook_ids" }) - @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild) + @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) webhooks: Webhook[]; @Column({ nullable: true }) diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts index 506db71a..0712f545 100644 --- a/util/src/entities/Message.ts +++ b/util/src/entities/Message.ts @@ -8,12 +8,14 @@ import { Column, CreateDateColumn, Entity, + FindConditions, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId, + RemoveOptions, UpdateDateColumn, } from "typeorm"; import { BaseClass } from "./BaseClass"; @@ -112,7 +114,7 @@ export class Message extends BaseClass { mention_everyone?: boolean; @JoinTable({ name: "message_user_mentions" }) - @ManyToMany(() => User) + @ManyToMany(() => User, { orphanedRowAction: "delete", onDelete: "CASCADE", cascade: true }) mentions: User[]; @JoinTable({ name: "message_role_mentions" }) @@ -127,8 +129,11 @@ export class Message extends BaseClass { @ManyToMany(() => Sticker) sticker_items?: Sticker[]; - @JoinColumn({ name: "attachment_ids" }) - @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { cascade: true }) + @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) attachments?: Attachment[]; @Column({ type: "simple-json" }) diff --git a/util/src/util/cdn.ts b/util/src/util/cdn.ts new file mode 100644 index 00000000..754d6244 --- /dev/null +++ b/util/src/util/cdn.ts @@ -0,0 +1,54 @@ +import FormData from "form-data"; +import { HTTPError } from "lambert-server"; +import fetch from "node-fetch"; +import { Config } from "./Config"; +import multer from "multer"; + +export async function uploadFile(path: string, file: Express.Multer.File) { + const form = new FormData(); + form.append("file", file.buffer, { + contentType: file.mimetype, + filename: file.originalname, + }); + + const response = await fetch(`${Config.get().cdn.endpoint || "http://localhost:3003"}${path}`, { + headers: { + signature: Config.get().security.requestSignature, + ...form.getHeaders(), + }, + method: "POST", + body: form, + }); + const result = await response.json(); + + if (response.status !== 200) throw result; + return result; +} + +export async function handleFile(path: string, body?: string): Promise { + if (!body || !body.startsWith("data:")) return body; + try { + const mimetype = body.split(":")[1].split(";")[0]; + const buffer = Buffer.from(body.split(",")[1], "base64"); + + // @ts-ignore + const { id } = await uploadFile(path, { buffer, mimetype, originalname: "banner" }); + return id; + } catch (error) { + console.error(error); + throw new HTTPError("Invalid " + path); + } +} + +export async function deleteFile(path: string) { + const response = await fetch(`${Config.get().cdn.endpoint || "http://localhost:3003"}${path}`, { + headers: { + signature: Config.get().security.requestSignature, + }, + method: "DELETE", + }); + const result = await response.json(); + + if (response.status !== 200) throw result; + return result; +} diff --git a/util/src/util/index.ts b/util/src/util/index.ts index 4e92f017..1ae96da9 100644 --- a/util/src/util/index.ts +++ b/util/src/util/index.ts @@ -1,6 +1,7 @@ export * from "./ApiError"; export * from "./BitField"; export * from "./checkToken"; +export * from "./cdn"; export * from "./Config"; export * from "./Constants"; export * from "./Database"; From b8627809902efea805c82cfa0a2d02196c06e762 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 19:05:26 +0200 Subject: [PATCH 65/90] :truck: move file handle to utility --- bundle/package-lock.json | Bin 82022 -> 82024 bytes cdn/package-lock.json | Bin 354786 -> 354815 bytes gateway/package-lock.json | Bin 156237 -> 156266 bytes util/package-lock.json | Bin 446397 -> 459190 bytes util/package.json | 2 ++ util/src/util/cdn.ts | 54 ++++++++++++++++++++++++++++++++++++++ util/src/util/index.ts | 1 + 7 files changed, 57 insertions(+) create mode 100644 util/src/util/cdn.ts diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 3199f7d8e3ad0dc33b924269fb3f20f41a943f21..f2dd7beee49d02d63db6474ebf54ae0c06c9ce43 100644 GIT binary patch delta 152 zcmaFX!1|(rb;AT!afg!1g4AOD-29^S#5^S{1*JGcJxe{~%^j?}nFNpp4Gr`RCU4vk zGdYt_dUC~1Ck>>q=$}LpE>Rv7QsGXZIma TMxY6tjFW$Jb8dcjq@o%CXJa_i delta 110 zcmaFS!1}C#b;AVK&Ffh=Gfh_AXS{hGhb8ai96o+V#d=J>qF| zC+}sB+B|!!Ju^t<bHbEN-XqSn`RXA~M!0rC#>u+7xF-KN%D4Idk<4lU D#xX6W diff --git a/cdn/package-lock.json b/cdn/package-lock.json index 7a4edd89674878d41c0794505b4c1b61f482e1dd..8a2346f4ea42409e25ab4084cd8d4f8b565e5f51 100644 GIT binary patch delta 49 zcmaEKS@i#9(G5nd6U&6yN=q_xlys&mIx)#Kd$P8BvN8fO6A&{4G0S#OR@Rv*0JV+} A5&!@I delta 44 vcmex=S@h9m(G5nd)1$>%`KKGyFmW`ev9_nNG6FFZ5HkZY%l0%@)|n{)V>J!u diff --git a/gateway/package-lock.json b/gateway/package-lock.json index 40db319da8a742b4f6d65a61a87afc99f19909b0..ae6009dbd6bc7627f95c985add5825f9376262eb 100644 GIT binary patch delta 36 scmX?mg!9!A&JE9*Csqlum6l}YDCtZVoGa7(kGcIHGvoGu%uN1q04eJZ>;M1& delta 26 icmaELg!AkX&JE9*C$G98-Ym`1F3rNYU7Cf-KMnw;oC)Rt diff --git a/util/package-lock.json b/util/package-lock.json index 4b0f1f1517e9ee45bfdacc0a32ed79d16784a74c..e5a62d0f36a1fa06c3e0b137b0fd5e4f3349a0d8 100644 GIT binary patch delta 5089 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 UTdsz2A;@5N&2aB+s#^m80lVCg3;+NC delta 219 zcmV<103`pmgB-n|8?fgAv;P630h1782(!rp^oz6Em5vCrZ=Z1lvm>Q@1G6uzZ6C8> z%v(IS>j?qm9k=2x0Y@N*TTlVFTTlYTF1Im(0<*!B+R`7FAdv>2mwYe-4!5900~sur z>9hk4w>!560|A$Cv;!Bno^k`w)VD@W1S1HyWL5;`1Gjuz1W*H)7mx)Gw?JY9X&Sd+ zeFWGvw=1&*ni-b{r~((4TLc6VmmnYkHJ3jx0eH72)C6c7w}bEm_WPGHfdbRFAMXV# Vp_kM{1HQMvkp{jbw=TB^h)U^@SL^@) diff --git a/util/package.json b/util/package.json index 751484af..2fa93f9b 100644 --- a/util/package.json +++ b/util/package.json @@ -31,6 +31,7 @@ "@types/amqplib": "^0.8.1", "@types/jsonwebtoken": "^8.5.0", "@types/mongoose-autopopulate": "^0.10.1", + "@types/multer": "^1.4.7", "@types/node": "^14.17.9", "@types/node-fetch": "^2.5.12", "jest": "^27.0.6" @@ -44,6 +45,7 @@ "jsonwebtoken": "^8.5.1", "lambert-server": "^1.2.10", "missing-native-js-functions": "^1.2.15", + "multer": "^1.4.3", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", "pg": "^8.7.1", diff --git a/util/src/util/cdn.ts b/util/src/util/cdn.ts new file mode 100644 index 00000000..754d6244 --- /dev/null +++ b/util/src/util/cdn.ts @@ -0,0 +1,54 @@ +import FormData from "form-data"; +import { HTTPError } from "lambert-server"; +import fetch from "node-fetch"; +import { Config } from "./Config"; +import multer from "multer"; + +export async function uploadFile(path: string, file: Express.Multer.File) { + const form = new FormData(); + form.append("file", file.buffer, { + contentType: file.mimetype, + filename: file.originalname, + }); + + const response = await fetch(`${Config.get().cdn.endpoint || "http://localhost:3003"}${path}`, { + headers: { + signature: Config.get().security.requestSignature, + ...form.getHeaders(), + }, + method: "POST", + body: form, + }); + const result = await response.json(); + + if (response.status !== 200) throw result; + return result; +} + +export async function handleFile(path: string, body?: string): Promise { + if (!body || !body.startsWith("data:")) return body; + try { + const mimetype = body.split(":")[1].split(";")[0]; + const buffer = Buffer.from(body.split(",")[1], "base64"); + + // @ts-ignore + const { id } = await uploadFile(path, { buffer, mimetype, originalname: "banner" }); + return id; + } catch (error) { + console.error(error); + throw new HTTPError("Invalid " + path); + } +} + +export async function deleteFile(path: string) { + const response = await fetch(`${Config.get().cdn.endpoint || "http://localhost:3003"}${path}`, { + headers: { + signature: Config.get().security.requestSignature, + }, + method: "DELETE", + }); + const result = await response.json(); + + if (response.status !== 200) throw result; + return result; +} diff --git a/util/src/util/index.ts b/util/src/util/index.ts index 2de26fc7..3160380f 100644 --- a/util/src/util/index.ts +++ b/util/src/util/index.ts @@ -1,6 +1,7 @@ export * from "./ApiError"; export * from "./BitField"; export * from "./checkToken"; +export * from "./cdn"; export * from "./Config"; export * from "./Constants"; export * from "./Database"; From 1633d9a9fe012c5e06767bc42068bb3619a90741 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 19 Sep 2021 22:15:30 +0200 Subject: [PATCH 66/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a281aa3f..87f90055 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ This repository contains: ## [Ressources](https://docs.fosscord.com/resources/) -- [Contributing](https://docs.fosscord.com/contributing/) +- [Contributing](https://docs.fosscord.com/contributing/server/) ## [Download](https://github.com/fosscord/fosscord-server/releases) From 12a151eb2d907bdcfa80baa0defe3a255d58f8a7 Mon Sep 17 00:00:00 2001 From: Chris Chrome Date: Mon, 20 Sep 2021 08:58:06 -0400 Subject: [PATCH 67/90] Delete messages before deleting channel Might need some testing, but seems pretty solid for one line of code lol --- api/src/routes/channels/#channel_id/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 3f434f5e..ca8cc87c 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Channel, Message, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { handleFile, route } from "@fosscord/api"; @@ -30,8 +30,8 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request } else if (channel.type === ChannelType.GROUP_DM) { await Channel.removeRecipientFromChannel(channel, req.user_id) } else { - //TODO messages in this channel should be deleted before deleting the channel await Promise.all([ + Message.delete({ channel_id: channel_id }), Channel.delete({ id: channel_id }), emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) ]); From ce09e01c2154072dfb52fceaca33a6106d2e1220 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:11:22 +0200 Subject: [PATCH 68/90] :construction: auto delete relations --- api/src/routes/channels/#channel_id/index.ts | 20 +++++--- util/package-lock.json | Bin 459190 -> 459116 bytes util/src/entities/BaseClass.ts | 49 ++++++++++++++++++- util/src/entities/Channel.ts | 12 +---- util/src/util/Database.ts | 2 +- 5 files changed, 64 insertions(+), 19 deletions(-) diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 3f434f5e..1063b151 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,6 +1,15 @@ -import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { + Channel, + ChannelDeleteEvent, + ChannelPermissionOverwriteType, + ChannelType, + ChannelUpdateEvent, + emitEvent, + Recipient, + handleFile +} from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { handleFile, route } from "@fosscord/api"; +import { route } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel @@ -20,15 +29,14 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); if (channel.type === ChannelType.DM) { - const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } }) - recipient.closed = true + const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } }); + recipient.closed = true; await Promise.all([ recipient.save(), emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent) ]); - } else if (channel.type === ChannelType.GROUP_DM) { - await Channel.removeRecipientFromChannel(channel, req.user_id) + await Channel.removeRecipientFromChannel(channel, req.user_id); } else { //TODO messages in this channel should be deleted before deleting the channel await Promise.all([ diff --git a/util/package-lock.json b/util/package-lock.json index e5a62d0f36a1fa06c3e0b137b0fd5e4f3349a0d8..f412961430c0fff63d0f6f363743b7c9e0762666 100644 GIT binary patch delta 1821 zcmciBOKjV890zbYb{oiO8Csb}N!o^qmCY1C@^Zv8Pq(oXI}fL^>BVv4yp!hbRGQRw znu>%(5tjH%2*#ud&SjCXgpi7aI3OX!P<_ibh{^%HE-f3e!Z_GQz8AjO*Ha-7C|-LRUkgi|8wsf*4; z!Jn*D3#qQxgDajy*-^tHTydrAVtBvMP5Hb&(Y+xviqNY#3+_-hn92F;#gfAn_O;u& zv9W50ps5W%v+6#?p!5 z`{(=956aRvS+gFcW{xXr4HPNUi=$f78y~DF5$|#)A6!+5uGXw((_x>`#1lstOS0GG3`KDRd zJ~##B>!X|0OTMy_?-xR1FUbd_nuC+0-ho?;w^%Y6;+MVrpw)Jmb7aq#}0%HtFpbF#hbAMO?)3J{AB2KJ|*b3ncZEhH0u|A3?2 z+G<8v#{&rxTNOyTmf3K$J7s1)S#?DSO>la{M4jZcAcl4Q66fp(7@FWYtnHy z>U1WhUa5xDnXW%kV!-b&K^J!R*C1nm&Gf|&;LCSL+rIwB@9H)T>Tz4gNr}HX8q=pWhCrzNWZ%K@PpVO|9}@xj0@cT*RUOrvu{GC z!QP+n$>EOI4uPW?)t#=Hye2biJUi>~fodr)%`1kdLBy?puc{e6CW z!+Gyb=bf8Qu&@#xEGf+y1;SjdYB$2f;cXAQc}iX{SmR)wRFn6 z(Z^$wR*NUHy+*fZ36vj?M@vJ`hQ7K%NZsc!9V&o3gV z?-d&SemW2B$LJKWFAHGjWykL3;^Aq;2b+&j0@%wl;PKi;ICqS`FwXWKq05>bPxs|C zp~)oA@v2hZikVcdU9NCu-cQe_X<6ZLjQE?ZketJF32xE%L9y^G3Og3DNg}hYQh*2?;YUYS+Xx-c3Rk!SqmY9^ks^wcg zl5Q%4W;^YQR1i>8o&cQt1wbV#sPK=D^e|(TE^EDxvrjJ;JH)C%IIhY`BH*zWQu@)TtnPo@8czHpft2vLJA4>Dlhas-oBI$tmo8j zy%q{KaXhAW`dlbZRLX;pK&LaTwk{a0ywD3z#H<8i9%~PNK2yuPN1q*LG7GVAebAsbnF)geVan9wkR5fj7N-VE+@Sad_7QF@g{OBya^0{Lc@qj(oOHfWB3!`rt^v(wpbO%oo*xI}M*m-#2Yjhgk{ssN+-wK^$ zeXk)<{tJB$?*D+Uy~92|x_OAMIwlSho?JoZNBhU<^>hC}H@N)`G7raP)9{N6*vx40 zPc(UbGW15iP3)`mB=Etz_pyc1$vy0doshTr8wXFY(ZQ3c0+^ULc=sZ9`9I)B?2co# F^*4PsW#|9^ diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts index 9b2ce058..2a621f40 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts @@ -1,5 +1,14 @@ import "reflect-metadata"; -import { BaseEntity, BeforeInsert, BeforeUpdate, EntityMetadata, FindConditions, PrimaryColumn } from "typeorm"; +import { + BaseEntity, + BeforeInsert, + BeforeUpdate, + EntityMetadata, + FindConditions, + getConnection, + PrimaryColumn, + RemoveOptions, +} from "typeorm"; import { Snowflake } from "../util/Snowflake"; import "missing-native-js-functions"; @@ -69,6 +78,44 @@ export class BaseClassWithoutId extends BaseEntity { const repository = this.getRepository(); return repository.decrement(conditions, propertyPath, value); } + + static async delete(criteria: FindConditions, options?: RemoveOptions) { + if (!criteria) throw new Error("You need to specify delete criteria"); + + const repository = this.getRepository(); + const promises = repository.metadata.relations.map((x) => { + if (x.orphanedRowAction !== "delete") return; + if (typeof x.type === "string") return; + + const foreignKey = + x.foreignKeys.find((key) => key.entityMetadata === repository.metadata) || + x.inverseRelation?.foreignKeys[0]; // find foreign key for this entity + if (!foreignKey) { + throw new Error( + `Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}` + ); + } + console.log(foreignKey); + const id = (criteria as any)[foreignKey.referencedColumnNames[0]]; + if (!id) throw new Error("id missing in criteria options"); + + if (x.relationType === "many-to-many" || x.relationType === "one-to-many") { + return getConnection() + .createQueryBuilder() + .relation(this, x.propertyName) + .of(id) + .remove({ [foreignKey.columnNames[0]]: id }); + } else if (x.relationType === "one-to-one" || x.relationType === "many-to-one") { + return getConnection() + .createQueryBuilder() + .from(x.inverseEntityMetadata, "user") + .of(id) + .remove({ [foreignKey.name]: id }); + } + }); + await Promise.all(promises); + return super.delete(criteria, options); + } } export class BaseClass extends BaseClassWithoutId { diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index b1f75f33..74611eea 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -1,14 +1,4 @@ -import { - Column, - Entity, - FindConditions, - JoinColumn, - ManyToOne, - ObjectID, - OneToMany, - RelationId, - RemoveOptions, -} from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; import { PublicUserProjection, User } from "./User"; diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts index d3844cd9..c22d8abd 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts @@ -21,7 +21,7 @@ export function initDatabase() { // entities: Object.values(Models).filter((x) => x.constructor.name !== "Object"), synchronize: true, - logging: false, + logging: true, cache: { duration: 1000 * 3, // cache all find queries for 3 seconds }, From 6c0a69d95b1dcc09cc4c8bab6639289af963010c Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:15:47 +0200 Subject: [PATCH 69/90] :arrow_up: update test client --- api/client_test/index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/client_test/index.html b/api/client_test/index.html index ac66df06..9a0aeae1 100644 --- a/api/client_test/index.html +++ b/api/client_test/index.html @@ -45,9 +45,9 @@ `{"trace":false,"canary":false,"logGatewayEvents":true,"logOverlayEvents":true,"logAnalyticsEvents":true,"sourceMapsEnabled":false,"axeEnabled":false}` ); - - - - + + + + From 013a4b8bc8d50b0df21381628bfe61db4ef927f6 Mon Sep 17 00:00:00 2001 From: Chris Chrome Date: Mon, 20 Sep 2021 11:29:37 -0400 Subject: [PATCH 70/90] Fix one thing, another problem pops up Co-authored-by: TheArcaneBrony --- api/src/routes/channels/#channel_id/index.ts | 5 ++++- api/src/routes/users/@me/relationships.ts | 2 +- api/src/util/route.ts | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index ca8cc87c..9a3cb4c2 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { Channel, Message, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Channel, Message, Invite, ReadState, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { handleFile, route } from "@fosscord/api"; @@ -32,6 +32,9 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request } else { await Promise.all([ Message.delete({ channel_id: channel_id }), + Invite.delete({ channel_id: channel_id }), + Recipient.delete({ channel_id: channel_id }), + ReadState.delete({ channel_id: channel_id }), Channel.delete({ id: channel_id }), emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) ]); diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 1d72f11a..6ad873a6 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -59,7 +59,7 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, relations: ["relationships", "relationships.to"], select: userProjection, where: { - discriminator: String(req.body.discriminator,).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes + discriminator: String(req.body.discriminator).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes username: req.body.username } }), diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 678ca64c..b7e6296b 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -14,6 +14,7 @@ export const ajv = new Ajv({ parseDate: true, allowDate: true, schemas, + coerceTypes: true, messages: true, strict: true, strictRequired: true From dd9ec0c6a0dc3fb87df635ca64c40fab598f753e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:38:16 +0200 Subject: [PATCH 71/90] :sparkles: accept invite page --- api/assets/schemas.json | 1 - api/client_test/index.html | 2 +- api/src/routes/auth/register.ts | 36 ++++++++++++++++++++------------- api/src/routes/invites/index.ts | 9 ++------- util/src/entities/Config.ts | 10 +++++---- util/src/entities/Invite.ts | 10 +++++++++ 6 files changed, 41 insertions(+), 27 deletions(-) diff --git a/api/assets/schemas.json b/api/assets/schemas.json index bfe6092b..da193b28 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -38,7 +38,6 @@ "additionalProperties": false, "required": [ "consent", - "password", "username" ], "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/api/client_test/index.html b/api/client_test/index.html index 9a0aeae1..ebe92e4c 100644 --- a/api/client_test/index.html +++ b/api/client_test/index.html @@ -19,7 +19,7 @@ ASSET_ENDPOINT: "", MEDIA_PROXY_ENDPOINT: "https://media.discordapp.net", WIDGET_ENDPOINT: `//${location.host}/widget`, - INVITE_HOST: `${location.hostname}`, + INVITE_HOST: `${location.host}/invite`, GUILD_TEMPLATE_HOST: "discord.new", GIFT_CODE_HOST: "discord.gift", RELEASE_CHANNEL: "stable", diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index c0b0e18a..4d3f2860 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, Config, defaultSettings } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, Config, defaultSettings, Member, Invite } from "@fosscord/util"; import bcrypt from "bcrypt"; import { EMAIL_REGEX, FieldErrors, route } from "@fosscord/api"; import "missing-native-js-functions"; @@ -19,7 +19,7 @@ export interface RegisterSchema { * @minLength 1 * @maxLength 72 */ - password: string; // TODO: use password strength of config + password?: string; consent: boolean; /** * @TJS-format email @@ -60,7 +60,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } console.log("register", req.body.email, req.body.username, ip); - // TODO: automatically join invite // TODO: gift_code_sku_id? // TODO: check password strength @@ -87,13 +86,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re }); } - // require invite to register -> e.g. for organizations to send invites to their employees - if (register.requireInvite && !invite) { - throw FieldErrors({ - email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } - }); - } - if (email) { // replace all dots and chars after +, if its a gmail.com email if (!email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); @@ -109,13 +101,13 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } }); } - } else if (register.email.necessary) { + } else if (register.email.required) { throw FieldErrors({ email: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } }); } - if (register.dateOfBirth.necessary && !date_of_birth) { + if (register.dateOfBirth.required && !date_of_birth) { throw FieldErrors({ date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } }); @@ -162,8 +154,14 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re // TODO: check captcha } - // the salt is saved in the password refer to bcrypt docs - password = await bcrypt.hash(password, 12); + if (password) { + // the salt is saved in the password refer to bcrypt docs + password = await bcrypt.hash(password, 12); + } else if (register.password.required) { + throw FieldErrors({ + password: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + }); + } let exists; // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists @@ -217,6 +215,16 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re fingerprints: [] }).save(); + if (invite) { + // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible) + await Invite.joinGuild(user.id, invite); + } else if (register.requireInvite) { + // require invite to register -> e.g. for organizations to send invites to their employees + throw FieldErrors({ + email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") } + }); + } + return res.json({ token: await generateToken(user.id) }); }); diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index ae8a5944..0fcf7c86 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -15,14 +15,9 @@ router.get("/:code", route({}), async (req: Request, res: Response) => { router.post("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; + const invite = await Invite.joinGuild(req.user_id, code); - const invite = await Invite.findOneOrFail({ code }); - if (invite.uses++ >= invite.max_uses) await Invite.delete({ code }); - else await invite.save(); - - await Member.addToGuild(req.user_id, invite.guild_id); - - res.status(200).send(invite); + res.json(invite); }); // * cant use permission of route() function because path doesn't have guild_id/channel_id diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts index fd830db8..f969b6bb 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts @@ -110,13 +110,13 @@ export interface ConfigValue { }; register: { email: { - necessary: boolean; // we have to use necessary instead of required as the cli tool uses json schema and can't use required + required: boolean; allowlist: boolean; blocklist: boolean; domains: string[]; }; dateOfBirth: { - necessary: boolean; + required: boolean; minimum: number; // in years }; requireCaptcha: boolean; @@ -125,6 +125,7 @@ export interface ConfigValue { allowMultipleAccounts: boolean; blockProxies: boolean; password: { + required: boolean; minLength: number; minNumbers: number; minUpperCase: number; @@ -246,14 +247,14 @@ export const DefaultConfigOptions: ConfigValue = { }, register: { email: { - necessary: true, + required: false, 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, + required: false, minimum: 13, }, requireInvite: false, @@ -262,6 +263,7 @@ export const DefaultConfigOptions: ConfigValue = { allowMultipleAccounts: true, blockProxies: true, password: { + required: false, minLength: 8, minNumbers: 2, minUpperCase: 2, diff --git a/util/src/entities/Invite.ts b/util/src/entities/Invite.ts index afad9c02..1396004e 100644 --- a/util/src/entities/Invite.ts +++ b/util/src/entities/Invite.ts @@ -1,4 +1,5 @@ import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm"; +import { Member } from "."; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; import { Guild } from "./Guild"; @@ -63,4 +64,13 @@ export class Invite extends BaseClass { @Column({ nullable: true }) target_user_type?: number; + + static async joinGuild(user_id: string, code: string) { + const invite = await Invite.findOneOrFail({ code }); + if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); + else await invite.save(); + + await Member.addToGuild(user_id, invite.guild_id); + return invite; + } } From 0247df3e4248e6bd336cc528fc56fceb845fd786 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:02:57 +0200 Subject: [PATCH 72/90] :sparkles: finish and fix .delete() for one-to-many relations --- util/src/entities/BaseClass.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts index 2a621f40..8a0b7a26 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts @@ -83,7 +83,7 @@ export class BaseClassWithoutId extends BaseEntity { if (!criteria) throw new Error("You need to specify delete criteria"); const repository = this.getRepository(); - const promises = repository.metadata.relations.map((x) => { + const promises = repository.metadata.relations.map(async (x) => { if (x.orphanedRowAction !== "delete") return; if (typeof x.type === "string") return; @@ -95,22 +95,23 @@ export class BaseClassWithoutId extends BaseEntity { `Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}` ); } - console.log(foreignKey); const id = (criteria as any)[foreignKey.referencedColumnNames[0]]; if (!id) throw new Error("id missing in criteria options"); - if (x.relationType === "many-to-many" || x.relationType === "one-to-many") { + if (x.relationType === "many-to-many") { return getConnection() .createQueryBuilder() .relation(this, x.propertyName) .of(id) .remove({ [foreignKey.columnNames[0]]: id }); - } else if (x.relationType === "one-to-one" || x.relationType === "many-to-one") { + } else if ( + x.relationType === "one-to-one" || + x.relationType === "many-to-one" || + x.relationType === "one-to-many" + ) { return getConnection() - .createQueryBuilder() - .from(x.inverseEntityMetadata, "user") - .of(id) - .remove({ [foreignKey.name]: id }); + .getRepository(x.inverseEntityMetadata.target) + .delete({ [foreignKey.columnNames[0]]: id }); } }); await Promise.all(promises); From a4811d86ce0ad31639f33620646c6bd9f4c6b661 Mon Sep 17 00:00:00 2001 From: Chris Chrome Date: Mon, 20 Sep 2021 12:16:17 -0400 Subject: [PATCH 73/90] oRdEr Of OpErAtIoNs --- api/src/routes/channels/#channel_id/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 9a3cb4c2..dcbf6d4e 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -31,10 +31,10 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request await Channel.removeRecipientFromChannel(channel, req.user_id) } else { await Promise.all([ - Message.delete({ channel_id: channel_id }), Invite.delete({ channel_id: channel_id }), - Recipient.delete({ channel_id: channel_id }), ReadState.delete({ channel_id: channel_id }), + Message.delete({ channel_id: channel_id }), + Recipient.delete({ channel_id: channel_id }), Channel.delete({ id: channel_id }), emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) ]); From 2d183f4c455f1eb549b30902a64c9eaa4eba8708 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:33:33 +0200 Subject: [PATCH 74/90] Update index.ts --- api/src/routes/channels/#channel_id/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index dcbf6d4e..fde75ec9 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { Channel, Message, Invite, ReadState, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; +import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { handleFile, route } from "@fosscord/api"; @@ -31,10 +31,6 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request await Channel.removeRecipientFromChannel(channel, req.user_id) } else { await Promise.all([ - Invite.delete({ channel_id: channel_id }), - ReadState.delete({ channel_id: channel_id }), - Message.delete({ channel_id: channel_id }), - Recipient.delete({ channel_id: channel_id }), Channel.delete({ id: channel_id }), emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) ]); From 62f9b35185bacfe5cc0bfcf34c485fc72bea9f10 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 20:22:56 +0200 Subject: [PATCH 75/90] :bug: fix .delete -> add onDelete: "CASCADE" --- api/src/routes/guilds/#guild_id/delete.ts | 12 +--- util/src/entities/Application.ts | 4 +- util/src/entities/Attachment.ts | 4 +- util/src/entities/Ban.ts | 8 ++- util/src/entities/BaseClass.ts | 68 +++++++++++------------ util/src/entities/Channel.ts | 10 +--- util/src/entities/ConnectedAccount.ts | 4 +- util/src/entities/Emoji.ts | 4 +- util/src/entities/Guild.ts | 2 - util/src/entities/Invite.ts | 12 +++- util/src/entities/Member.ts | 9 ++- util/src/entities/Message.ts | 13 +++-- util/src/entities/ReadState.ts | 8 ++- util/src/entities/Recipient.ts | 8 ++- util/src/entities/Relationship.ts | 8 ++- util/src/entities/Role.ts | 4 +- util/src/entities/Session.ts | 4 +- util/src/entities/Sticker.ts | 4 +- util/src/entities/Team.ts | 4 +- util/src/entities/TeamMember.ts | 8 ++- util/src/entities/User.ts | 10 +++- util/src/entities/VoiceState.ts | 16 ++++-- util/src/entities/Webhook.ts | 20 +++++-- 23 files changed, 150 insertions(+), 94 deletions(-) diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts index 7c3c5530..bd158c56 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/api/src/routes/guilds/#guild_id/delete.ts @@ -13,15 +13,8 @@ router.post("/", route({}), async (req: Request, res: Response) => { const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); - // do not put everything into promise all, because of "QueryFailedError: SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" - - await Message.delete({ guild_id }); // messages must be deleted before channel - await Promise.all([ - Role.delete({ guild_id }), - Channel.delete({ guild_id }), - Emoji.delete({ guild_id }), - Member.delete({ guild_id }), + Guild.delete({ id: guild_id }), // this will also delete all guild related data emitEvent({ event: "GUILD_DELETE", data: { @@ -31,9 +24,6 @@ router.post("/", route({}), async (req: Request, res: Response) => { } as GuildDeleteEvent) ]); - await Invite.delete({ guild_id }); // invite must be deleted after channel - await Guild.delete({ id: guild_id }); // guild must be deleted after everything else - return res.sendStatus(204); }); diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts index 2092cd4e..fab3d93f 100644 --- a/util/src/entities/Application.ts +++ b/util/src/entities/Application.ts @@ -41,7 +41,9 @@ export class Application extends BaseClass { verify_key: string; @JoinColumn({ name: "team_id" }) - @ManyToOne(() => Team) + @ManyToOne(() => Team, { + onDelete: "CASCADE", + }) team?: Team; @JoinColumn({ name: "guild_id" }) diff --git a/util/src/entities/Attachment.ts b/util/src/entities/Attachment.ts index 82c5ecf5..7b4b17eb 100644 --- a/util/src/entities/Attachment.ts +++ b/util/src/entities/Attachment.ts @@ -31,7 +31,9 @@ export class Attachment extends BaseClass { message_id: string; @JoinColumn({ name: "message_id" }) - @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments) + @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, { + onDelete: "CASCADE", + }) message: import("./Message").Message; @BeforeRemove() diff --git a/util/src/entities/Ban.ts b/util/src/entities/Ban.ts index e8a6d648..9504bd8e 100644 --- a/util/src/entities/Ban.ts +++ b/util/src/entities/Ban.ts @@ -10,7 +10,9 @@ export class Ban extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column({ nullable: true }) @@ -18,7 +20,9 @@ export class Ban extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts index 8a0b7a26..d18757f2 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts @@ -6,6 +6,7 @@ import { EntityMetadata, FindConditions, getConnection, + getManager, PrimaryColumn, RemoveOptions, } from "typeorm"; @@ -79,44 +80,41 @@ export class BaseClassWithoutId extends BaseEntity { return repository.decrement(conditions, propertyPath, value); } - static async delete(criteria: FindConditions, options?: RemoveOptions) { - if (!criteria) throw new Error("You need to specify delete criteria"); + // static async delete(criteria: FindConditions, options?: RemoveOptions) { + // if (!criteria) throw new Error("You need to specify delete criteria"); - const repository = this.getRepository(); - const promises = repository.metadata.relations.map(async (x) => { - if (x.orphanedRowAction !== "delete") return; - if (typeof x.type === "string") return; + // const repository = this.getRepository(); + // const promises = repository.metadata.relations.map(async (x) => { + // if (x.orphanedRowAction !== "delete") return; - const foreignKey = - x.foreignKeys.find((key) => key.entityMetadata === repository.metadata) || - x.inverseRelation?.foreignKeys[0]; // find foreign key for this entity - if (!foreignKey) { - throw new Error( - `Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}` - ); - } - const id = (criteria as any)[foreignKey.referencedColumnNames[0]]; - if (!id) throw new Error("id missing in criteria options"); + // const foreignKey = + // x.foreignKeys.find((key) => key.entityMetadata === repository.metadata) || + // x.inverseRelation?.foreignKeys[0]; // find foreign key for this entity + // if (!foreignKey) { + // throw new Error( + // `Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}` + // ); + // } + // const id = (criteria as any)[foreignKey.referencedColumnNames[0]]; + // if (!id) throw new Error("id missing in criteria options " + foreignKey.referencedColumnNames); - if (x.relationType === "many-to-many") { - return getConnection() - .createQueryBuilder() - .relation(this, x.propertyName) - .of(id) - .remove({ [foreignKey.columnNames[0]]: id }); - } else if ( - x.relationType === "one-to-one" || - x.relationType === "many-to-one" || - x.relationType === "one-to-many" - ) { - return getConnection() - .getRepository(x.inverseEntityMetadata.target) - .delete({ [foreignKey.columnNames[0]]: id }); - } - }); - await Promise.all(promises); - return super.delete(criteria, options); - } + // if (x.relationType === "many-to-many") { + // return getConnection() + // .createQueryBuilder() + // .relation(this, x.propertyName) + // .of(id) + // .remove({ [foreignKey.columnNames[0]]: id }); + // } else if ( + // x.relationType === "one-to-one" || + // x.relationType === "many-to-one" || + // x.relationType === "one-to-many" + // ) { + // return (x.inverseEntityMetadata.target as any).delete({ [foreignKey.columnNames[0]]: id }); + // } + // }); + // await Promise.all(promises); + // return super.delete(criteria, options); + // } } export class BaseClass extends BaseClassWithoutId { diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index 74611eea..ece82bd0 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -45,7 +45,6 @@ export class Channel extends BaseClass { @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) recipients?: Recipient[]; @@ -57,7 +56,9 @@ export class Channel extends BaseClass { guild_id?: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) @@ -110,35 +111,30 @@ export class Channel extends BaseClass { @OneToMany(() => Invite, (invite: Invite) => invite.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) invites?: Invite[]; @OneToMany(() => Message, (message: Message) => message.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) messages?: Message[]; @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) voice_states?: VoiceState[]; @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) read_states?: ReadState[]; @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) webhooks?: Webhook[]; diff --git a/util/src/entities/ConnectedAccount.ts b/util/src/entities/ConnectedAccount.ts index 59a8f423..b8aa2889 100644 --- a/util/src/entities/ConnectedAccount.ts +++ b/util/src/entities/ConnectedAccount.ts @@ -11,7 +11,9 @@ export class ConnectedAccount extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column({ select: false }) diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts index 181aff2c..a252d9f4 100644 --- a/util/src/entities/Emoji.ts +++ b/util/src/entities/Emoji.ts @@ -15,7 +15,9 @@ export class Emoji extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column() diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts index d46d2161..e107937d 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts @@ -84,7 +84,6 @@ export class Guild extends BaseClass { @OneToMany(() => Ban, (ban: Ban) => ban.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) bans: Ban[]; @@ -147,7 +146,6 @@ export class Guild extends BaseClass { @OneToMany(() => Channel, (channel: Channel) => channel.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) channels: Channel[]; diff --git a/util/src/entities/Invite.ts b/util/src/entities/Invite.ts index afad9c02..cb308823 100644 --- a/util/src/entities/Invite.ts +++ b/util/src/entities/Invite.ts @@ -34,7 +34,9 @@ export class Invite extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) @@ -42,7 +44,9 @@ export class Invite extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -58,7 +62,9 @@ export class Invite extends BaseClass { target_user_id: string; @JoinColumn({ name: "target_user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62 @Column({ nullable: true }) diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts index 66f5d9a1..feb9c069 100644 --- a/util/src/entities/Member.ts +++ b/util/src/entities/Member.ts @@ -39,7 +39,9 @@ export class Member extends BaseClassWithoutId { id: string; @JoinColumn({ name: "id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column() @@ -47,7 +49,9 @@ export class Member extends BaseClassWithoutId { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) @@ -55,7 +59,6 @@ export class Member extends BaseClassWithoutId { @JoinTable({ name: "member_roles", - joinColumn: { name: "index", referencedColumnName: "index" }, inverseJoinColumn: { name: "role_id", diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts index 0712f545..c4901693 100644 --- a/util/src/entities/Message.ts +++ b/util/src/entities/Message.ts @@ -54,7 +54,9 @@ export class Message extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -62,7 +64,9 @@ export class Message extends BaseClass { guild_id?: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild?: Guild; @Column({ nullable: true }) @@ -70,7 +74,9 @@ export class Message extends BaseClass { author_id: string; @JoinColumn({ name: "author_id", referencedColumnName: "id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) author?: User; @Column({ nullable: true }) @@ -132,7 +138,6 @@ export class Message extends BaseClass { @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", }) attachments?: Attachment[]; diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts index 8dd05b21..68e867a0 100644 --- a/util/src/entities/ReadState.ts +++ b/util/src/entities/ReadState.ts @@ -15,7 +15,9 @@ export class ReadState extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -23,7 +25,9 @@ export class ReadState extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column({ nullable: true }) diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts index bb280588..a945f938 100644 --- a/util/src/entities/Recipient.ts +++ b/util/src/entities/Recipient.ts @@ -8,7 +8,9 @@ export class Recipient extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => require("./Channel").Channel) + @ManyToOne(() => require("./Channel").Channel, { + onDelete: "CASCADE", + }) channel: import("./Channel").Channel; @Column() @@ -16,7 +18,9 @@ export class Recipient extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => require("./User").User) + @ManyToOne(() => require("./User").User, { + onDelete: "CASCADE", + }) user: import("./User").User; @Column({ default: false }) diff --git a/util/src/entities/Relationship.ts b/util/src/entities/Relationship.ts index 61b3ac82..e016b36b 100644 --- a/util/src/entities/Relationship.ts +++ b/util/src/entities/Relationship.ts @@ -17,7 +17,9 @@ export class Relationship extends BaseClass { from_id: string; @JoinColumn({ name: "from_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) from: User; @Column({}) @@ -25,7 +27,9 @@ export class Relationship extends BaseClass { to_id: string; @JoinColumn({ name: "to_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) to: User; @Column({ nullable: true }) diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts index 33c8d272..9fca99a5 100644 --- a/util/src/entities/Role.ts +++ b/util/src/entities/Role.ts @@ -10,7 +10,9 @@ export class Role extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column() diff --git a/util/src/entities/Session.ts b/util/src/entities/Session.ts index d42a8f98..7cc325f5 100644 --- a/util/src/entities/Session.ts +++ b/util/src/entities/Session.ts @@ -11,7 +11,9 @@ export class Session extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; //TODO check, should be 32 char long hex string diff --git a/util/src/entities/Sticker.ts b/util/src/entities/Sticker.ts index 7730a86a..ab224d1d 100644 --- a/util/src/entities/Sticker.ts +++ b/util/src/entities/Sticker.ts @@ -31,7 +31,9 @@ export class Sticker extends BaseClass { guild_id?: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild?: Guild; @Column({ type: "simple-enum", enum: StickerType }) diff --git a/util/src/entities/Team.ts b/util/src/entities/Team.ts index beb8bf68..22140b7f 100644 --- a/util/src/entities/Team.ts +++ b/util/src/entities/Team.ts @@ -9,7 +9,9 @@ export class Team extends BaseClass { icon?: string; @JoinColumn({ name: "member_ids" }) - @OneToMany(() => TeamMember, (member: TeamMember) => member.team) + @OneToMany(() => TeamMember, (member: TeamMember) => member.team, { + orphanedRowAction: "delete", + }) members: TeamMember[]; @Column() diff --git a/util/src/entities/TeamMember.ts b/util/src/entities/TeamMember.ts index 6b184d08..bdfdccf0 100644 --- a/util/src/entities/TeamMember.ts +++ b/util/src/entities/TeamMember.ts @@ -20,7 +20,9 @@ export class TeamMember extends BaseClass { team_id: string; @JoinColumn({ name: "team_id" }) - @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members) + @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, { + onDelete: "CASCADE", + }) team: import("./Team").Team; @Column({ nullable: true }) @@ -28,6 +30,8 @@ export class TeamMember extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; } diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index cef88777..4c86b2d8 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -127,11 +127,17 @@ export class User extends BaseClass { public_flags: number; @JoinColumn({ name: "relationship_ids" }) - @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from) + @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { + cascade: true, + orphanedRowAction: "delete", + }) relationships: Relationship[]; @JoinColumn({ name: "connected_account_ids" }) - @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user) + @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, { + cascade: true, + orphanedRowAction: "delete", + }) connected_accounts: ConnectedAccount[]; @Column({ type: "simple-json", select: false }) diff --git a/util/src/entities/VoiceState.ts b/util/src/entities/VoiceState.ts index 56eb244e..75748a01 100644 --- a/util/src/entities/VoiceState.ts +++ b/util/src/entities/VoiceState.ts @@ -13,7 +13,9 @@ export class VoiceState extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild?: Guild; @Column({ nullable: true }) @@ -21,7 +23,9 @@ export class VoiceState extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -29,11 +33,15 @@ export class VoiceState extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; // @JoinColumn([{ name: "user_id", referencedColumnName: "id" },{ name: "guild_id", referencedColumnName: "guild_id" }]) - // @ManyToOne(() => Member) + // @ManyToOne(() => Member, { + // onDelete: "CASCADE", + // }) //TODO find a way to make it work without breaking Guild.voice_states member: Member; diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts index 12ba0d08..8382435f 100644 --- a/util/src/entities/Webhook.ts +++ b/util/src/entities/Webhook.ts @@ -32,7 +32,9 @@ export class Webhook extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) guild: Guild; @Column({ nullable: true }) @@ -40,7 +42,9 @@ export class Webhook extends BaseClass { channel_id: string; @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) channel: Channel; @Column({ nullable: true }) @@ -48,7 +52,9 @@ export class Webhook extends BaseClass { application_id: string; @JoinColumn({ name: "application_id" }) - @ManyToOne(() => Application) + @ManyToOne(() => Application, { + onDelete: "CASCADE", + }) application: Application; @Column({ nullable: true }) @@ -56,7 +62,9 @@ export class Webhook extends BaseClass { user_id: string; @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) user: User; @Column({ nullable: true }) @@ -64,6 +72,8 @@ export class Webhook extends BaseClass { source_guild_id: string; @JoinColumn({ name: "source_guild_id" }) - @ManyToOne(() => Guild) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) source_guild: Guild; } From 5b3ac6dadbfec8d2a0411858dfdfa715b7ab06ee Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 21:34:33 +0200 Subject: [PATCH 76/90] :bug: fix relationships --- api/assets/schemas.json | 256 +--------------------- api/src/routes/users/@me/relationships.ts | 14 +- 2 files changed, 15 insertions(+), 255 deletions(-) diff --git a/api/assets/schemas.json b/api/assets/schemas.json index da193b28..cc45ebb3 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -385,15 +385,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -707,15 +698,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -978,15 +960,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1228,15 +1201,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1481,15 +1445,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1743,15 +1698,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1998,15 +1944,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2248,15 +2185,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2510,15 +2438,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2785,15 +2704,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3099,15 +3009,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3349,15 +3250,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3599,15 +3491,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3861,15 +3744,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4118,15 +3992,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4371,15 +4236,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4624,15 +4480,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4873,15 +4720,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5142,15 +4980,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5418,15 +5247,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5672,15 +5492,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5928,15 +5739,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6184,15 +5986,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6461,8 +6254,14 @@ } }, "additionalProperties": false - }, - "RelationshipType": { + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RelationshipPutSchema": { + "type": "object", + "properties": { + "type": { "enum": [ 1, 2, @@ -6472,19 +6271,7 @@ "type": "number" } }, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "RelationshipPutSchema": { - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/RelationshipType" - } - }, "additionalProperties": false, - "required": [ - "type" - ], "definitions": { "ChannelPermissionOverwriteType": { "enum": [ @@ -6711,15 +6498,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6965,15 +6743,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -7402,15 +7171,6 @@ } }, "additionalProperties": false - }, - "RelationshipType": { - "enum": [ - 1, - 2, - 3, - 4 - ], - "type": "number" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 6ad873a6..567c734e 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -21,20 +21,20 @@ router.get("/", route({}), async (req: Request, res: Response) => { const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships", "relationships.to"] }); //TODO DTO - const related_users = user.relationships.map(r => { + const related_users = user.relationships.map((r) => { return { id: r.to.id, type: r.type, nickname: null, - user: r.to.toPublicUser(), - } - }) + user: r.to.toPublicUser() + }; + }); return res.json(related_users); }); export interface RelationshipPutSchema { - type: RelationshipType; + type?: RelationshipType; } router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => { @@ -42,7 +42,7 @@ router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request req, res, await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships", "relationships.to"], select: userProjection }), - req.body.type + req.body.type ?? RelationshipType.friends ); }); @@ -59,7 +59,7 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, relations: ["relationships", "relationships.to"], select: userProjection, where: { - discriminator: String(req.body.discriminator).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes + discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes username: req.body.username } }), From 912d1c31d8fc00550d5f9a4d437b874be811a08d Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 23:34:54 +0200 Subject: [PATCH 77/90] :art: rewrite imports --- api/package-lock.json | Bin 938709 -> 1010210 bytes api/package.json | 8 +++++++- bundle/package-lock.json | Bin 94710 -> 99097 bytes bundle/package.json | 1 + cdn/package-lock.json | Bin 360232 -> 361915 bytes cdn/package.json | 1 + cdn/tsconfig.json | 2 +- gateway/package-lock.json | Bin 161981 -> 163664 bytes gateway/package.json | 1 + gateway/src/events/Close.ts | 2 +- gateway/src/events/Connection.ts | 11 +++++++---- gateway/src/events/Message.ts | 3 +-- gateway/src/listener/listener.ts | 7 ++----- gateway/src/opcodes/Heartbeat.ts | 5 +---- gateway/src/opcodes/Identify.ts | 16 +++++++++++----- gateway/src/opcodes/LazyRequest.ts | 5 +---- gateway/src/opcodes/PresenceUpdate.ts | 3 +-- gateway/src/opcodes/RequestGuildMembers.ts | 4 +--- gateway/src/opcodes/Resume.ts | 5 +---- gateway/src/opcodes/VoiceStateUpdate.ts | 4 +--- gateway/src/opcodes/index.ts | 3 +-- gateway/src/opcodes/instanceOf.ts | 3 +-- gateway/src/util/Send.ts | 6 ++---- gateway/src/util/WebSocket.ts | 4 +--- gateway/src/util/setHeartbeat.ts | 2 +- gateway/tsconfig.json | 1 + 26 files changed, 46 insertions(+), 51 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index dcbb75d1b0bffcffebf956cd7c400ee3eccdc702..53088d5db68182a874a3380567f35b7d7ad5eacf 100644 GIT binary patch delta 14936 zcmeHu33OZKmH&Ctll7!0d5^O?aaNLuM5`qk$Rt`UOV+;Gk`oqLmSt_0WXbZ(*N?|#KC!0 z@Z{~(8Mxy?q6s{_DiKr7BcK6l-Xf})*RCL*AmH8>qMoVAk$3W-Wg}5l9^br<6jh7? z$3o_fBKc>c;-b1DZ|UrKKJ&~5k|w$H3_dhRz=6|*02E&(g%JLQq!G3_$ToQ7PWmwH z?dLCHa=Yj@J}mo3vKrQEi5ejPhPDL*^7t2p)POK!FNPNXpAM;gkQj11zSW7hE zfpe?ql>v=MqwN{eM58HxqFCrIx}CmK-02+DW!&~&W5L@O9<|0g{I(8tDreKgT|QMQ z)vD}`q&>sw9_ygdYx4HWL&jV&H&o2}OG;mRe+0Il=FfwZHN*<|U@cJx3nV-#{Lvv= z3hP{?gpX4Lc?0hXIPy!v%GoT8r?dFT0uXNzjerKXz{kHJ)MaaRtBA$$_{YSmV8YX# z@!Aw4K3mw|6H<@Zhc%Xv)vk%z@~Qs3c2H?HnLGWml6tszOy_rphODKKCYA^ohui!7 zrJ~vA4A`uyRC36lN?QBnmbOk?cnp@WB(<>1OxA($1fhasX0ipH(;^nG`z!;$v-$!9 z8`jJi821mW!`715o$ZPQy2BlDtHr3Zg)POXMP)UPhcszjL8I%6CQGSc+@Xi}BaaEQB*Le%|67q;UFfYJPe0ete2p0)-;E z>e9etmcL8%U*wK`r-=s0hDa&g(?!mM8$TrU@J<)r%`Fy-%+aAv1V%a)aZhs87}8oz zV?zd?C6w1DlZpJmh|wD>q*9&UxZe@8D3jXZgwhx2Qt16Yox4>T3Iqp=QT=#6XHM!9 zrVfp-BhzP}*#3zEK4}(8;Al6wsBAy?P9I)P-gQBU0cYKv5mEv#=jmzda@S+Ta@LK> z3jG0jo5qyz=!fi~_=w3p?p9`kk#SplU;Dt2BRQNHmig=%lSig^%LAPO|6r`GFYopB zT5MKhG3`^y^15hWUuU|zBQc~88Un+uXq|5I4yqoQSA|j-uP18R71Ic^0^J9noFPQ8 z?r!uymyW)^gIoyfB4j#etG2nP!W#M8kBIFENxKr+E$l4xtwEJ=) zxy~pz7QKnQEj+F^luT_sPM6&uNtlbpA^m{8G~Q-TCvAf|m$thzp-yDGvVo#eIb1Xw zLKlZNAQLPxS+30^O;mf4df+N6*F)2G+)AeX@Ry75g)2=D%$!lI9_#E5SqqJIlD z8A8N7og!}JGdDN#p1dB;ItdcikI-`1c3iZIP=eART#30j*fw0~8PUu8Lbi~-I1vUm8g;s~BWgvo;PM4yL)QM7x7DW&s#B?;+*0g}+4|K^l|tpV_J>tLeKcwt zGm5ENLO+Mfb<$@WtDkL-H3d7^!s5jT$ zno(*!C9S-puPqff$-FYJGTh|~Dm1BZ)Zi|)xkAptWL%rF2gg9!i1@>E4JKU9@|G`= z%}^T>t0DI#TwZ6drFTJY6EVHe*h`oi8~Je6d9ZhcDBt)^H`&A@zI{C4J|eiQE`L0a z^N8tFWr)Xp9eXK<=%&}n`Gd@xTS+^?J?cM>_@WFxH{EiHjR%J?IV*lmkj#;J#9#9` z!x=p2Bbf@D@Id1wHn9h%Z90GcKeTN7;gjr4P*mLLIfwc4S0D4`f^>(@j zM;w|&F=lR;cjh&Ley?3;NDg?i!JhuK(HA;;n{Md0DF4r zrT9^?Q49Mr7!TZ)ZM*4t)93T`#KLkhxn(y^!(ks{tz$nB)WOtR1n5WbLR^5kDM~Gh zMI&6R(F%%odL`_a5fWIqhnB%3r-NELN$-ZIBplztf9lp3?A$+cbPn_uD&4(~S%)W1OGg-^RQ7L?-me7>z*+rIr&Vc_# z*8K&d{ye(?r^DjQa!hVq!sB|t>;HjT2)_4;RRK#ZrO6~(?cSk$CLLDj0xET-Ba>G9+T^#clD z$B@Q477Huw8JQ(fGTV{?%edL+4r;UsLutG#Y1XK+*^ZHLHXnA&o!&qqJ*# z_i#^C>By@_yDcWSso?WiizqFAUM#qxAZJt1bWc@UN}I5(LL zk`Ie3#*lUpYyYR(wy7dUSMBWxqCeS22&Y@N*%_8zEm(F*zczdNwHtWNuuEC3hP4r1 z6TESlu7Q`g67%4u2gpB^BlmlG-r|aOZuZ{sJ-mgmZda_YNkI!iE z*xGHb&ZOFrZL^MbB#P;DbUdokwGXt0!`WogX>ktf$E;4XGZM76t2*=jc}1&PJ~A8~ zv1vLxg1)g(QCW%(jKT40o)V6#t7@9r;-X%0rC5ajR>Wh)4Ta)RERx}Bg%&&>{!%_N zR`9;MY60xpC*Fefkt#0WIyzm>n4x>L&(j~&Td=h(s9nMSh}Gl? zPZ%t%Fs0>R1vf0?D=Iwr`&aqP;g(6h8tgygFNI%z8#yPV#$xHw=H&rZa$q1;h^Knn zyVX`>soU0X?$);!22uUjTuHfshWf0qbVL@x959lQntBzIwqpLZIA?BH_# z?6`~_KR;vul-DoEVg6;20Ows?cIoGg0l3h*>GuD^gK&KkA?f-7OwAl6bd(+5G=Q*- zE373&TdvL4EAJ}itWK?IT$X4{w;M{z9=WsC)iqieix2p$K4*K-ogeXd3+nNaK;KAC zIqFxt<*8OjHk!02I{esCMmn=be=gbURCT5+P#{NkedQ*40es6MTm(NqKrb&t->MPJ z@B0(vQWgbP0@@94^M#P@5eP|@0%^_dwZt_SS79!WzBhruoI^0RUn5sCuYEw6Dwauk zx}^7z6fD?}aD1BZzF|MTY`Ru6&{NB410S$)^1&gB3yC=lA5uAFXYJhW9@EP zDx!{=TGPG5t$l@nZ!Bwe`IQdcsIJ`-42*@n0~TwW)!U(z2VCjiVBFW{fPHdmG3?b5 zYgt}C?qq%!-tyurlmzxpl2x!}H(h;kn=v^b zd4L;=IB7Q@pqKHL8(U#{iB!Yn(-ive04mgE` z$(k}4r|u>-;{YAVJPCz}?*%Xdj>`1IAP@4|`q&=tpiR&{$tmVN)P zg)Pjr8;OM#x53Sac~wyReAOc6>=VMtiZjgpzbAfDF$T$3c<(U>f*Of^XTF#LXlt7+}fT`F2Twe^vM=WZo`Oql+(=&={1^^u%RlHwI<5 zcSLZ*atwRzBUBCiq^Y_F_AVl5P&`1dKQB+MfLbp-ANtl3*TFs8MC;*}?X&>CltEB@ za65L@fAc8S1aCe_)xz1k=ql!g`>BTnfYinUxc6mYH5_avN!V~)B!q|S#p_vqObsvp zK{OwxioEOKJ3FWb*zyfp!u;7z?;_#HE?f-1`<}EBPVS`<8L&(*@)K9U`;%zHyLZyp zf%t1^9B0GU40bYTgXp|9B6$5_inB`u$S6#o{b(1xguPA-zj&CO{*>;Z&2Z3%(r zF8&d>s2S>uy!y#ovA3_)AU$x44CR^+cVqHAy_2p*+WC}|-U#1n#{&mlGzGLrR09X~ zNGaU>AVq;JjxdG$)Uz7N?vGt`H7xx)-40KFnQokPqmw_qoi@R%C-9`A4IlmKdm<5x z?xh=H(;9LC++C2?GSVFV7{NUGE$Tl0Ow#;sR0*fPDPDRxl@Lu?eps@m94e?HRzsD# zx&e?zl1yHY0l}HbNrQ!JMQi3r8dUy{QkSP4r?`$wxe7RT2(vdh}c_cS{=NFrZ%;r0ZFHBZ2?J z(!euWP*4|%rZx;06X^oif#aOOsXwBU#J&@m$wpJK{dpXs_D93LS7E}b6ac-1c=a3! zfR|gtm}gK5+KO_|Bfq6Y@WBC;hdAHA(Md{~-8~{iF_|umpbMwruO7lzzxNy}h?_%L z`<^{lQA{fX79YO{_FOHlW*&HyD$>w>t7yp=CHiw$qT&80ao*SGaUaO1PLTi(YKgP3 zw?HkPIEH%+jx^&I5F`xegF=pV|JoCXCLaA2)_qo>V3z3#B*RV)ikq009|*S*%(1+< zhG6zSOZ|FX?ZsQEK97p2mp~je^&GCv8QB!e?9b63lXk>=_h*Se!Il%0klAsXdf@H< z329Wcv*{HP)AWk?+8bF!^(4V;_R&WA^DcpSy{H@SXIMnciupeVk)?`#BZ00gi6Nu6Rby`roE;3$(0a0^VCiE7+K) zU}`)5w`P^};x3R=xH-5vHwyP>Kr(Gb8}+9!ZZ7Gv(yQRd+pz)sqE-F(_OxEkr~ipo z6`&WlQSNERod%n~i#yS3LShL#(lf&kup&3MD+(|)Nmd5uLg<#2d(}w=u?QZ%3z?~| zC#VHn*9z~f$E}C+9>QH*=UISRjhhSCxzV`yZ9}$ehN~*?Z@GxH5W^WYv4&-wR>QG7 zCAIL>J-90cLFED%I*ASbw8YJ}&2uAhmpiy-C%tTzsbE}g(LVlP4(m`jOj88QJ@5%; z@~kvRFl&RNZ3H~Tm!f3N>dare3Vm6rGjna@+~~|4V{+TglG=qV-!P*yU!XL@4F{;k zl>@_>$l!*umORt?;x8k`QcX7^iC6r4X*S1*I#v|?L1 z#r_iI{wFZMPSdY)_~U6p#vrX=nRks%ah!Em|o2JgPXlsE`F1p6v5=ns6ViY`pN4>13Y;2htxZ8tV2={x6~nxd}Knp z9Hy>DE%7@`t8r(>6Vxx^;LTDgv;VZ{HlloH&u+;|c=q>Y=354PAHr>X@AZmaWX}rW z=r2X*VAJm?1dty|x56FIQ!OxVs^+vAAw2y&mI)NHX?UtxdL{QmEx;dY;ml1T5xwk( zxa;(qDI}u5y-iXBIXf+!yidFk`ff!O`t4P?sk*dZyc~|aEB!ef=oSiC@mC6G)%f4! nY3UMH+kOGQn-gEb)V|GXR8Q=n*6f_RkH$XteKxcw<;SdNjg=4$F6 z3l$G2>_e0RvLPUYVAkhEwS-y~XH%0GlD=NQ7ad8CU#}hiz7BVGHL^I6I6z^Ke|Px7uAP2o~5xghK{NTp8SQPJibNj??kMfWV%_o_$@+r zkT@kg93KwOVfHpvATtu&i@uZAx_rXtBY)T%sNXOz zi%${0skQxdooGz^dJI(^u8)C-vJ5l6-j2)P%3dCPQ${?P(<3;zLNuatr9O^h*J(MD zIho*={qltK&xX&A$`HS@UQ5EU51n#Ed%efFb3`U0h;1i_ClAYa1RBoErF@`Ex`GXi z2}HZeeE0=<$X;kRNnEX*xP|r`X7ODmRLXs?)1Zai9l0d0)4O6G!axUiqr&C)e0oTK zll=axQV7fz*-P@hOXX0NUC!o~O_V1WFN~TZext^2N%LFh?XTngJ)UF2H4)$*PYqSh zn<=$TKgAu7D@A#erKW>3e7H{eiZ6$Box;GNwt4zXeNkX*t8)1ICY844C5LgtCm85a z6^K^r6-Yi|B#{0I=4#D0IHRs}U^=AQEpH>>rgoW{9 z-e8%ey4S!D#?_fy|FXtU{iDM{9v${xKCrOSCBc~r{k+HCN*#1LoX``5mnLK}Hx0|e z)J3?pRu`u?>Q1FHWA{svqe|VJOc9}RyP0u diff --git a/api/package.json b/api/package.json index ce5bcdf9..53031071 100644 --- a/api/package.json +++ b/api/package.json @@ -46,6 +46,7 @@ "@types/express": "^4.17.9", "@types/i18next-node-fs-backend": "^2.1.0", "@types/jest": "^27.0.1", + "@types/jest-expect-message": "^1.0.3", "@types/jsonwebtoken": "^8.5.0", "@types/mongodb": "^3.6.9", "@types/mongoose": "^5.10.5", @@ -61,6 +62,8 @@ "caxa": "^2.1.0", "image-size": "^1.0.0", "jest": "^26.6.3", + "jest-expect-message": "^1.0.2", + "jest-runtime": "^27.2.1", "saslprep": "^1.0.3", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", @@ -85,7 +88,7 @@ "express": "^4.17.1", "express-validator": "^6.9.2", "form-data": "^3.0.0", - "i18next": "^19.8.5", + "i18next": "^19.9.2", "i18next-http-middleware": "^3.1.3", "i18next-node-fs-backend": "^2.1.3", "jsonwebtoken": "^8.5.1", @@ -106,6 +109,9 @@ "setupFiles": [ "/jest/setup.js" ], + "setupFilesAfterEnv": [ + "jest-expect-message" + ], "globalSetup": "/jest/globalSetup.js", "verbose": true } diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 6a6588427b3840e66fdb87a59aaf2e325c551def..0ed6b03a887e8da24430c92adb434de7e07cee91 100644 GIT binary patch delta 2528 zcmd6nO>EO<7{}!`ZP&smw6xHcQ5p_oq;_m4&Bvs{ny;q$Y~rL%qYdFCPMpMv9otEq zXo4|KyKn(LV%c^X+i90cXgP2gprP%+G=$m%6Vt>1Ar9j(;IJKbn&Z%Ro7iOn4PX2| zAFtoX&-?s;zu)cs=>5KPmvrF!@nHL!PSU})_xg^cd6Ch-woiY=iV?HetbS&2Kz;OY zr05jG83mD%7#U^CwapyMlXK*Tz({E^FUXszIPll;_Q`7$0vCIZ?v$zmBg%}_%^)!x zBM1ns21d2R(85Np8c@pr$K1k z*bdjzlkLO1Uo;Jrr93J!IYxmKUyQZhbs@w!z2P&W#!UmAh1E}|;Oi+}yi)Z=5?qXm z&j-kW*G|O~ylts2t(YaAF0(n_5(rTxo7qhS9BH>rGKKNFkjY1zji|k8OeTFnF1TDX zSu5di$}VJlTrlqTd*IGH(LwDvNp5fqAp9=G1gG!elbcRyy&^3|Ld|gJbho5t%3&9s zzPT77rV9BbSBhjSevdb_5>Rq-(H4qFm85AQS$CN{mXNHZ63K|OT&D6wpr{xYTs)gu z(IHzsdDOTfA^Q?shAjqJ$bEKAeuHaHpb(MbSAgxkxg{%{pYt!zG%Hm}ikJrNT?utP%__ zN5y)EVog>lh!gduLamtVT*{I6dcAYL1!sw-T{$<^^w~Inafwd?@jz>`M{A+hTr);s z__}0r9G_a32WI`Nm*!AaY}ve`g-xn9)J?4u4rEtoLzJo*d9K!#D}j+(N>{ED%Wzyl z()Qw*4KuHQT9Q>oqMf1BVixtL_nw*@Tkl54naY=qX)ccyB)~53fuZ^F9ul*{+3~&Y ztC#op_te=uY!B~N=a&xPL#n0qQ+uOC>o$Vyk9!qgW@WiT1Y--4l+RSI2@od#sSxivjl2vv!XbBHf3lK5j zFXbgh>HctMNY&DZwS(8|){M?f_SQvO5a3~c9F9*Lhcv69UVC=|$L~(ROrSCqu@{^nQ0m9SmLTVFl^?6LoY#DgXcg delta 169 zcmbQ)#`f(o>xO+SlcPihHossgW|`ce!?yVx=Q+m7r7U`r-*SsiuDC5Wx&4va=4hUU zT$?>#axzYqm6$a-f!k>E0}-yxLXyVDo2~s7T{rhkO*7nVa8i+dbIv7y*2y*ZeK)`7 z3}&2s;$|h>tdp!;H&@;c;el({c)`gCG_+Z3JChirt>t#p0LDEUldrwxpKO2f<@UAJ NjGI`d8#Xb%008vMMd<(l diff --git a/bundle/package.json b/bundle/package.json index 72b24568..4b4e8286 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -45,6 +45,7 @@ "@types/ws": "^7.4.0", "@zerollup/ts-transform-paths": "^1.7.18", "ts-node": "^10.2.1", + "ts-patch": "^1.4.4", "typescript": "^4.3.5" }, "dependencies": { diff --git a/cdn/package-lock.json b/cdn/package-lock.json index 8a70844c3f4c4f80ebd18075261a740377b6afed..b6faa0e4f0172cd5e98240a3bbf2953085d96e0c 100644 GIT binary patch delta 979 zcmZ4SPjq*;*am4K&cx4VYYgfDQ8oBuR96LmY$|baa&p$T^UP)5&NG)q<`DqYOHyqB delta 76 zcmdnpEwEjazZD^=Cd4!)-_+-)qZUkVid?N%_&JOf(o1H8E?*HU5z1XKKX!v_;iIBMw86}>~SK(C^lrI<`kqBO@264 zoCRd;WJXSX6=WAcY#ooQO>r=MDu7G79d73^=E zQ<_v55aC%I5SV6GVCkP3;9C$>;ZmV*P?BE~;Nxa&8Id1UZern*SZ10MT$beNYT;s( z>KITE5#d!)=3PF$QH9Z9a>W;ZMx)6e4$4iAJ;gOmlaZgz))?q>qsa?{Wu#HOfEq|B zUNJ%T3aUD&m(T*DlHF53%m^sq=Wa7pvgE>AZocXRQ`^m5e>HmaI@kke2O z*-g+8MT*$zhH8vzX#Qn8~G_OuxC;hm6e<9=c J{e?{b*Z>;NTV((M delta 64 zcmccck8|%w&JELlZ#khF~>te>uRqV4wnxo~nN6Ry2IZUo%x7n_cz{m-b x.id !== this.user_id); + x.channel.recipients = x.channel.recipients!.filter( + (x) => x.id !== this.user_id + ); } return x.channel; }); @@ -109,7 +115,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { if (!user) return this.close(CLOSECODES.Authentication_failed); for (let relation of user.relationships) { - const related_user = relation.to + const related_user = relation.to; const public_related_user = { username: related_user.username, discriminator: related_user.discriminator, diff --git a/gateway/src/opcodes/LazyRequest.ts b/gateway/src/opcodes/LazyRequest.ts index 41ec3446..db00157f 100644 --- a/gateway/src/opcodes/LazyRequest.ts +++ b/gateway/src/opcodes/LazyRequest.ts @@ -2,13 +2,10 @@ import { getPermission, Member, PublicMemberProjection, - PublicUserProjection, Role, } from "@fosscord/util"; import { LazyRequest } from "../schema/LazyRequest"; -import { OPCODES, Payload } from "@fosscord/gateway/util/Constants"; -import { Send } from "@fosscord/gateway/util/Send"; -import WebSocket from "@fosscord/gateway/util/WebSocket"; +import { WebSocket, Send, OPCODES, Payload } from "@fosscord/gateway"; import { check } from "./instanceOf"; import "missing-native-js-functions"; diff --git a/gateway/src/opcodes/PresenceUpdate.ts b/gateway/src/opcodes/PresenceUpdate.ts index 4febbfcb..53d7b9d2 100644 --- a/gateway/src/opcodes/PresenceUpdate.ts +++ b/gateway/src/opcodes/PresenceUpdate.ts @@ -1,5 +1,4 @@ -import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; -import WebSocket from "@fosscord/gateway/util/WebSocket"; +import { WebSocket, Payload } from "@fosscord/gateway"; export function onPresenceUpdate(this: WebSocket, data: Payload) { // return this.close(CLOSECODES.Unknown_error); diff --git a/gateway/src/opcodes/RequestGuildMembers.ts b/gateway/src/opcodes/RequestGuildMembers.ts index 9c1654fa..b80721dc 100644 --- a/gateway/src/opcodes/RequestGuildMembers.ts +++ b/gateway/src/opcodes/RequestGuildMembers.ts @@ -1,6 +1,4 @@ -import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; - -import WebSocket from "@fosscord/gateway/util/WebSocket"; +import { Payload, WebSocket } from "@fosscord/gateway"; export function onRequestGuildMembers(this: WebSocket, data: Payload) { // return this.close(CLOSECODES.Unknown_error); diff --git a/gateway/src/opcodes/Resume.ts b/gateway/src/opcodes/Resume.ts index e155c139..398cce25 100644 --- a/gateway/src/opcodes/Resume.ts +++ b/gateway/src/opcodes/Resume.ts @@ -1,7 +1,4 @@ -import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants"; -import { Send } from "@fosscord/gateway/util/Send"; - -import WebSocket from "@fosscord/gateway/util/WebSocket"; +import { WebSocket, Payload, Send } from "@fosscord/gateway"; export async function onResume(this: WebSocket, data: Payload) { console.log("Got Resume -> cancel not implemented"); diff --git a/gateway/src/opcodes/VoiceStateUpdate.ts b/gateway/src/opcodes/VoiceStateUpdate.ts index 60803ec3..084c5766 100644 --- a/gateway/src/opcodes/VoiceStateUpdate.ts +++ b/gateway/src/opcodes/VoiceStateUpdate.ts @@ -1,6 +1,5 @@ import { VoiceStateUpdateSchema } from "../schema/VoiceStateUpdateSchema"; -import { Payload } from "@fosscord/gateway/util/Constants"; -import WebSocket from "@fosscord/gateway/util/WebSocket"; +import { Payload, WebSocket, genVoiceToken } from "@fosscord/gateway"; import { check } from "./instanceOf"; import { Config, @@ -12,7 +11,6 @@ import { VoiceState, VoiceStateUpdateEvent, } from "@fosscord/util"; -import { genVoiceToken } from "@fosscord/gateway/util/SessionUtils"; // TODO: check if a voice server is setup // Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not. diff --git a/gateway/src/opcodes/index.ts b/gateway/src/opcodes/index.ts index c4069589..027739db 100644 --- a/gateway/src/opcodes/index.ts +++ b/gateway/src/opcodes/index.ts @@ -1,5 +1,4 @@ -import { Payload } from "@fosscord/gateway/util/Constants"; -import WebSocket from "@fosscord/gateway/util/WebSocket"; +import { WebSocket, Payload } from "@fosscord/gateway"; import { onHeartbeat } from "./Heartbeat"; import { onIdentify } from "./Identify"; import { onLazyRequest } from "./LazyRequest"; diff --git a/gateway/src/opcodes/instanceOf.ts b/gateway/src/opcodes/instanceOf.ts index 6d84ac21..37d513ad 100644 --- a/gateway/src/opcodes/instanceOf.ts +++ b/gateway/src/opcodes/instanceOf.ts @@ -1,6 +1,5 @@ import { instanceOf } from "lambert-server"; -import { CLOSECODES } from "@fosscord/gateway/util/Constants"; -import WebSocket from "@fosscord/gateway/util/WebSocket"; +import { WebSocket, CLOSECODES } from "@fosscord/gateway"; export function check(this: WebSocket, schema: any, data: any) { try { diff --git a/gateway/src/util/Send.ts b/gateway/src/util/Send.ts index 1b00e361..ff8aefc9 100644 --- a/gateway/src/util/Send.ts +++ b/gateway/src/util/Send.ts @@ -2,9 +2,7 @@ var erlpack: any; try { erlpack = require("erlpack"); } catch (error) {} -import { Payload } from "@fosscord/gateway/util/Constants"; - -import WebSocket from "./WebSocket"; +import { Payload, WebSocket } from "@fosscord/gateway"; export async function Send(socket: WebSocket, data: Payload) { let buffer: Buffer | string; @@ -20,7 +18,7 @@ export async function Send(socket: WebSocket, data: Payload) { } return new Promise((res, rej) => { - socket.send(buffer, (err) => { + socket.send(buffer, (err: any) => { if (err) return rej(err); return res(null); }); diff --git a/gateway/src/util/WebSocket.ts b/gateway/src/util/WebSocket.ts index 15d1549f..b80265a7 100644 --- a/gateway/src/util/WebSocket.ts +++ b/gateway/src/util/WebSocket.ts @@ -3,7 +3,7 @@ import WS from "ws"; import { Deflate } from "zlib"; import { Channel } from "amqplib"; -interface WebSocket extends WS { +export interface WebSocket extends WS { version: number; user_id: string; session_id: string; @@ -19,5 +19,3 @@ interface WebSocket extends WS { permissions: Record; events: Record; } - -export default WebSocket; diff --git a/gateway/src/util/setHeartbeat.ts b/gateway/src/util/setHeartbeat.ts index 9f88b481..f6871cfe 100644 --- a/gateway/src/util/setHeartbeat.ts +++ b/gateway/src/util/setHeartbeat.ts @@ -1,5 +1,5 @@ import { CLOSECODES } from "./Constants"; -import WebSocket from "./WebSocket"; +import { WebSocket } from "./WebSocket"; // TODO: make heartbeat timeout configurable export function setHeartbeat(socket: WebSocket) { diff --git a/gateway/tsconfig.json b/gateway/tsconfig.json index c50bd77a..dd066383 100644 --- a/gateway/tsconfig.json +++ b/gateway/tsconfig.json @@ -70,6 +70,7 @@ "resolveJsonModule": true, "baseUrl": ".", "paths": { + "@fosscord/gateway": ["src/index"], "@fosscord/gateway/*": ["src/*"], "@fosscord/util/*": ["../util/src/*"] }, From 8d611abe45e971cc0c873deaaa63d4a7c59df98e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 23:35:04 +0200 Subject: [PATCH 78/90] :bug: fix errror handler --- api/src/middlewares/ErrorHandler.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index d288f3fb..96e703ce 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -3,6 +3,7 @@ import { HTTPError } from "lambert-server"; import { EntityNotFoundError } from "typeorm"; import { FieldError } from "@fosscord/api"; import { ApiError } from "@fosscord/util"; +const EntityNotFoundErrorRegex = /"(\w+)"/; export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { if (!error) return next(); @@ -18,9 +19,9 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne code = error.code; message = error.message; httpcode = error.httpStatus; - } else if (error instanceof EntityNotFoundError) { - message = `${(error as any).stringifyTarget || "Item"} could not be found`; - code = 404; + } else if (error.name === "EntityNotFoundError") { + message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; + code = httpcode = 404; } else if (error instanceof FieldError) { code = Number(error.code); message = error.message; From cc33e87a1451d1b327fa8945394806cc0a7a0e91 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 23:35:32 +0200 Subject: [PATCH 79/90] :sparkles: add option to disable all rate limits --- api/src/middlewares/BodyParser.ts | 2 ++ api/src/middlewares/RateLimit.ts | 3 ++- util/src/entities/Config.ts | 2 ++ util/src/entities/User.ts | 4 +--- util/src/util/Config.ts | 2 +- util/src/util/Database.ts | 3 ++- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/api/src/middlewares/BodyParser.ts b/api/src/middlewares/BodyParser.ts index b0ff699d..4cb376bc 100644 --- a/api/src/middlewares/BodyParser.ts +++ b/api/src/middlewares/BodyParser.ts @@ -6,6 +6,8 @@ export function BodyParser(opts?: OptionsJson) { const jsonParser = bodyParser.json(opts); return (req: Request, res: Response, next: NextFunction) => { + if (!req.headers["content-type"]) req.headers["content-type"] = "application/json"; + jsonParser(req, res, (err) => { if (err) { // TODO: different errors for body parser (request size limit, wrong body type, invalid body, ...) diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index d1fd072f..1a38cfcf 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -107,7 +107,8 @@ export default function rateLimit(opts: { } export async function initRateLimits(app: Router) { - const { routes, global, ip, error } = Config.get().limits.rate; + const { routes, global, ip, error, disabled } = Config.get().limits.rate; + if (disabled) return; await listenEvent(EventRateLimit, (event) => { Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts index f969b6bb..a460b437 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts @@ -77,6 +77,7 @@ export interface ConfigValue { maxWebhooks: number; }; rate: { + disabled: boolean; ip: Omit; global: RateLimitOptions; error: RateLimitOptions; @@ -188,6 +189,7 @@ export const DefaultConfigOptions: ConfigValue = { maxWebhooks: 10, }, rate: { + disabled: true, ip: { count: 500, window: 5, diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 4c86b2d8..b5c2c308 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -161,15 +161,13 @@ export class User extends BaseClass { } static async getPublicUser(user_id: string, opts?: FindOneOptions) { - const user = await User.findOne( + return await User.findOneOrFail( { id: user_id }, { ...opts, select: [...PublicUserProjection, ...(opts?.select || [])], } ); - if (!user) throw new HTTPError("User not found", 404); - return user; } } diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts index 1ec71ad0..c87d598e 100644 --- a/util/src/util/Config.ts +++ b/util/src/util/Config.ts @@ -14,7 +14,7 @@ export const Config = { get: function get() { return config.value as ConfigValue; }, - set: function set(val: any) { + set: function set(val: Partial) { if (!config) return; config.value = val.merge(config?.value || {}); return config.save(); diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts index c22d8abd..0c3d7cef 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts @@ -1,3 +1,4 @@ +import path from "path"; import "reflect-metadata"; import { Connection, createConnection, ValueTransformer } from "typeorm"; import * as Models from "../entities"; @@ -15,7 +16,7 @@ export function initDatabase() { // @ts-ignore promise = createConnection({ type: "sqlite", - database: "database.db", + database: path.join(process.cwd(), "database.db"), // type: "postgres", // url: "postgres://fosscord:wb94SmuURM2Syv&@localhost/fosscord", // From eb2f447d962c17c1e13ba69c4203cd98091847d3 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 23:35:37 +0200 Subject: [PATCH 80/90] :bug: fix unittests --- api/jest/globalSetup.js | 7 +++- api/jest/setup.js | 4 +- api/src/util/route.ts | 4 +- api/tests/routes.test.ts | 86 +++++++++++++++++++++++++++++++++------- 4 files changed, 81 insertions(+), 20 deletions(-) diff --git a/api/jest/globalSetup.js b/api/jest/globalSetup.js index 98e70fb9..520aa0e2 100644 --- a/api/jest/globalSetup.js +++ b/api/jest/globalSetup.js @@ -1,3 +1,4 @@ +const { Config, initDatabase } = require("@fosscord/util"); const fs = require("fs"); const path = require("path"); const { FosscordServer } = require("../dist/Server"); @@ -5,8 +6,12 @@ const Server = new FosscordServer({ port: 3001 }); global.server = Server; module.exports = async () => { try { - fs.unlinkSync(path.join(__dirname, "..", "database.db")); + fs.unlinkSync(path.join(process.cwd(), "database.db")); } catch {} + + await initDatabase(); + await Config.init(); + Config.get().limits.rate.disabled = true; return await Server.start(); }; diff --git a/api/jest/setup.js b/api/jest/setup.js index bd535866..abc485ae 100644 --- a/api/jest/setup.js +++ b/api/jest/setup.js @@ -1,2 +1,2 @@ -// jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); -// jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); +jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); +jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 5b06a2b5..e7c7ed1c 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -1,4 +1,4 @@ -import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; +import { DiscordApiErrors, EVENT, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; import fs from "fs"; import path from "path"; @@ -38,7 +38,7 @@ export interface RouteOptions { response?: RouteResponse; body?: any; path?: string; - event?: EventData | EventData[]; + event?: EVENT | EVENT[]; headers?: Record; }; } diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index 4cb7e6bc..0473c579 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -2,12 +2,12 @@ // TODO: check every route with different database engine import getRouteDescriptions from "../jest/getRouteDescriptions"; -import supertest, { Response } from "supertest"; import { join } from "path"; import fs from "fs"; import Ajv from "ajv"; import addFormats from "ajv-formats"; -const request = supertest("http://localhost:3001/api"); +import fetch from "node-fetch"; +import { User } from "@fosscord/util"; const SchemaPath = join(__dirname, "..", "assets", "responses.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); @@ -18,34 +18,90 @@ export const ajv = new Ajv({ schemas, messages: true, strict: true, - strictRequired: true + strictRequired: true, + coerceTypes: true }); addFormats(ajv); +var token: string; +var user: User; +beforeAll(async (done) => { + try { + const response = await fetch("http://localhost:3001/api/auth/register", { + method: "POST", + body: JSON.stringify({ + fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw", + email: "test@example.com", + username: "tester", + password: "wtp9gep9gw", + invite: null, + consent: true, + date_of_birth: "2000-01-01", + gift_code_sku_id: null, + captcha_key: null + }), + headers: { + "content-type": "application/json" + } + }); + const json = await response.json(); + token = json.token; + user = await ( + await fetch(`http://localhost:3001/api/users/@me`, { + headers: { authorization: token } + }) + ).json(); + + done(); + } catch (error) { + done(error); + } +}); + describe("Automatic unit tests with route description middleware", () => { const routes = getRouteDescriptions(); routes.forEach((route, pathAndMethod) => { const [path, method] = pathAndMethod.split("|"); - test(path, (done) => { - if (!route.example) { + test(path, async (done) => { + if (!route.test) { console.log(`${(route as any).file}\nrouter.${method} is missing the test property`); return done(); } - const urlPath = path || route.example?.path; - const validate = ajv.getSchema(route.response.body); - if (!validate) return done(new Error(`Response schema ${route.response.body} not found`)); + const urlPath = path.replace(":id", user.id) || route.test?.path; + var validate: any; + if (route.test.body) { + validate = ajv.getSchema(route.test.body); + if (!validate) return done(new Error(`Response schema ${route.test.body} not found`)); + } - request[method](urlPath) - .expect(route.response.status) - .expect((err: any, res: Response) => { - if (err) return done(err); - const valid = validate(res.body); - if (!valid) return done(validate.errors); + var body = ""; - return done(); + try { + const response = await fetch(`http://localhost:3001/api${urlPath}`, { + method: method.toUpperCase(), + body: JSON.stringify(route.test.body), + headers: { ...route.test.headers, authorization: token } }); + + body = await response.text(); + + expect(response.status, body).toBe(route.test.response.status || 200); + + // TODO: check headers + // TODO: expect event + + if (validate) { + body = JSON.parse(body); + const valid = validate(body); + if (!valid) return done(validate.errors); + } + } catch (error) { + return done(error); + } + + return done(); }); }); }); From 2a094c603a35c7174022ec2d2ffa42fa28508e3b Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 21 Sep 2021 22:52:30 +0200 Subject: [PATCH 81/90] :sparkles: generate openapi documentation --- api/assets/openapi.json | 5974 +++++++++++++++-- api/assets/responses.json | 90 - api/assets/schemas.json | 1961 ++++++ ...escriptions.ts => getRouteDescriptions.js} | 30 +- ...test_schema.ts => generate_body_schema.js} | 22 +- api/scripts/generate_body_schema.ts | 60 - api/scripts/generate_openapi_schema.js | 127 + api/scripts/generate_openapi_schema.ts | 92 - api/src/routes/auth/login.ts | 23 +- api/src/routes/auth/register.ts | 24 +- .../routes/channels/#channel_id/recipients.ts | 5 +- .../#member_id/roles/#role_id/index.ts | 4 +- api/src/routes/sticker-packs/#id/index.ts | 5 +- api/src/routes/sticker-packs/index.ts | 5 +- api/src/util/String.ts | 2 - api/tests/routes.test.ts | 4 +- util/src/util/Email.ts | 20 + util/src/util/{checkToken.ts => Token.ts} | 20 + util/src/util/index.ts | 3 +- 19 files changed, 7419 insertions(+), 1052 deletions(-) delete mode 100644 api/assets/responses.json rename api/jest/{getRouteDescriptions.ts => getRouteDescriptions.js} (72%) rename api/scripts/{generate_test_schema.ts => generate_body_schema.js} (75%) delete mode 100644 api/scripts/generate_body_schema.ts create mode 100644 api/scripts/generate_openapi_schema.js delete mode 100644 api/scripts/generate_openapi_schema.ts create mode 100644 util/src/util/Email.ts rename util/src/util/{checkToken.ts => Token.ts} (72%) diff --git a/api/assets/openapi.json b/api/assets/openapi.json index fa527911..caf572b9 100644 --- a/api/assets/openapi.json +++ b/api/assets/openapi.json @@ -4,7 +4,12 @@ { "url": "https://api.fosscord.com/v{version}", "description": "Official fosscord instance", - "variables": { "version": { "description": "", "default": "9", "enum": ["8", "9"] } } + "variables": { + "version": { + "default": "9", + "enum": ["8", "9"] + } + } } ], "info": { @@ -12,95 +17,2866 @@ "version": "1.0.0", "title": "Fosscord HTTP API Routes", "termsOfService": "", - "contact": { "name": "Fosscord" }, - "license": { "name": "AGPLV3", "url": "https://www.gnu.org/licenses/agpl-3.0.en.html" } + "contact": { + "name": "Fosscord" + }, + "license": { + "name": "AGPLV3", + "url": "https://www.gnu.org/licenses/agpl-3.0.en.html" + } }, - "tags": [], + "tags": [ + { + "name": "voice" + }, + { + "name": "users" + }, + { + "name": "store" + }, + { + "name": "sticker-packs" + }, + { + "name": "science" + }, + { + "name": "ping" + }, + { + "name": "outbound-promotions" + }, + { + "name": "invites" + }, + { + "name": "guilds" + }, + { + "name": "gateway" + }, + { + "name": "experiments" + }, + { + "name": "discoverable-guilds" + }, + { + "name": "channels" + }, + { + "name": "auth" + }, + { + "name": "applications" + } + ], "paths": { "/users/{id}": { "get": { "summary": "", - "description": "", - "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "user id" }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user id" + } + ], "operationId": "", "responses": { "200": { "description": "User found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserPublic" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserPublic" + } + } + } }, "404": { "description": "User not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } }, - "security": [{ "Token": [] }] + "security": [ + { + "Token": [] + } + ] } }, "/users/@me": { "get": { "summary": "", - "description": "", "parameters": [], "operationId": "", "responses": { "200": { "description": "Authenticated user", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserPublic" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserPublic" + } + } + } } }, - "security": [{ "Token": [] }] + "security": [ + { + "Token": [] + } + ] + } + }, + "/voice/regions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["voice"] + } + }, + "/users/@me/settings/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSettingsSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/relationships/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users", "relationships"] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipPostSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/relationships/{id}": { + "put": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipPutSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + } + }, + "/users/@me/library/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/guilds/{id}": { + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + } + }, + "/users/@me/disable/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/devices/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/delete/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/connections/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/channels/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DmChannelCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/billing/subscriptions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/billing/country-code/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/applications/{app_id}/entitlements/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "app_id" + } + ], + "tags": ["users"] + } + }, + "/users/@me/affinities/users/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/@me/affinities/guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["users"] + } + }, + "/users/{id}/profile/": { + "get": { + "description": "", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserProfileResponse" + } + } + }, + "description": "" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + } + }, + "/users/{id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["users"] + } + }, + "/store/skus/skus/{id}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["store"] + } + }, + "/store/applications/applications/{id}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["store"] + } + }, + "/sticker-packs/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["sticker", "sticker-packs"] + } + }, + "/sticker-packs/{id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": ["sticker", "sticker-packs"] + } + }, + "/science/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["science"] + } + }, + "/ping/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["ping"] + } + }, + "/outbound-promotions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["outbound", "outbound-promotions"] + } + }, + "/invites/{code}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["invites"] + }, + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["invites"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["invites"] + } + }, + "/guilds/templates/{code}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildTemplateCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/widget.png/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/widget.json/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/widget/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WidgetModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/welcome_screen/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildUpdateWelcomeScreenSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/voice-states/{user_id}/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceStateUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/vanity-url/": { + "get": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VanityUrlSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/templates/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "post": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/templates/{code}": { + "delete": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + }, + "put": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/roles/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "post": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RolePositionUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/roles/{role_id}": { + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/regions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/members/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/members/{member_id}/nick/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemberNickChangeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/members/{member_id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemberChangeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + }, + "put": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + }, + "delete": { + "description": "##### Requires the ``KICK_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/invites/": { + "get": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/delete/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/channels/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "post": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelReorderSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/bans/": { + "get": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/bans/{user}": { + "get": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user" + } + ], + "tags": ["guilds"] + } + }, + "/guilds/{guild_id}/bans/{user_id}": { + "put": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BanCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["guilds"] + }, + "delete": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["guilds"] + } + }, + "/gateway/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["gateway"] + } + }, + "/gateway/bot": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["gateway"] + } + }, + "/experiments/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["experiments"] + } + }, + "/discoverable-guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["discoverable", "discoverable-guilds"] + } + }, + "/channels/{channel_id}/webhooks/": { + "post": { + "description": "##### Requires the ``MANAGE_WEBHOOKS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/typing/": { + "post": { + "description": "##### Requires the ``SEND_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/recipients/{user_id}": { + "put": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/pins/{message_id}": { + "put": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/pins/": { + "get": { + "description": "##### Requires the ``READ_MESSAGE_HISTORY`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/permissions/{overwrite_id}": { + "put": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "overwrite_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "overwrite_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "overwrite_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "overwrite_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/bulk-delete/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkDeleteSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/": { + "delete": { + "description": "##### Requires the ``MANAGE_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}": { + "delete": { + "description": "##### Requires the ``MANAGE_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + } + ], + "tags": ["channels"] + }, + "get": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{user_id}": { + "put": { + "description": "##### Requires the ``READ_MESSAGE_HISTORY`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/": { + "patch": { + "description": "##### Requires the ``SEND_MESSAGES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/messages/{message_id}/ack/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageAcknowledgeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/invites/": { + "post": { + "description": "##### Requires the ``CREATE_INSTANT_INVITE`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InviteCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + }, + "get": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/channels/{channel_id}/": { + "get": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + }, + "delete": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + }, + "patch": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": ["channels"] + } + }, + "/auth/register/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["auth"] + } + }, + "/applications/detectable/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["applications"] + } + }, + "/guilds/{guild_id}/members/{member_id}/roles/{role_id}/": { + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": ["guilds"] + }, + "put": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": ["guilds"] + } + }, + "/auth/login/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": ["auth"] } } }, - "externalDocs": { "description": "", "url": "http://docs.fosscord.com/" }, + "externalDocs": { + "url": "http://docs.fosscord.com/" + }, "components": { "schemas": { "Error": { "type": "object", - "properties": { "code": { "type": "integer" }, "message": { "type": "string" } }, + "properties": { + "code": { + "type": "integer" + }, + "message": { + "type": "string" + } + }, "required": ["code", "message"] }, "RateLimit": { "type": "object", - "properties": { "retry_after": { "type": "integer" }, "message": { "type": "string" }, "global": { "type": "boolean" } }, + "properties": { + "retry_after": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "global": { + "type": "boolean" + } + }, "required": ["code", "message", "globa"] }, "User": { "type": "object", "properties": { - "username": { "type": "string" }, - "discriminator": { "type": "string" }, - "avatar": { "type": "string" }, - "accent_color": { "type": "integer" }, - "banner": { "type": "string" }, - "phone": { "type": "string" }, - "desktop": { "type": "boolean" }, - "mobile": { "type": "boolean" }, - "premium": { "type": "boolean" }, - "premium_type": { "type": "integer" }, - "bot": { "type": "boolean" }, - "bio": { "type": "string" }, - "system": { "type": "boolean" }, - "nsfw_allowed": { "type": "boolean" }, - "mfa_enabled": { "type": "boolean" }, - "created_at": { "type": "string", "format": "date-time" }, - "verified": { "type": "boolean" }, - "disabled": { "type": "boolean" }, - "deleted": { "type": "boolean" }, - "email": { "type": "string" }, - "flags": { "type": "string" }, - "public_flags": { "type": "string" }, - "relationships": { "type": "array", "items": { "$ref": "#/components/schemas/Relationship" } }, - "connected_accounts": { "type": "array", "items": { "$ref": "#/components/schemas/ConnectedAccount" } }, + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "desktop": { + "type": "boolean" + }, + "mobile": { + "type": "boolean" + }, + "premium": { + "type": "boolean" + }, + "premium_type": { + "type": "integer" + }, + "bot": { + "type": "boolean" + }, + "bio": { + "type": "string" + }, + "system": { + "type": "boolean" + }, + "nsfw_allowed": { + "type": "boolean" + }, + "mfa_enabled": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "verified": { + "type": "boolean" + }, + "disabled": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "public_flags": { + "type": "string" + }, + "relationships": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Relationship" + } + }, + "connected_accounts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConnectedAccount" + } + }, "data": { "type": "object", - "properties": { "valid_tokens_since": { "type": "string", "format": "date-time" }, "hash": { "type": "string" } }, + "properties": { + "valid_tokens_since": { + "type": "string", + "format": "date-time" + }, + "hash": { + "type": "string" + } + }, "additionalProperties": false, "required": ["valid_tokens_since"] }, - "fingerprints": { "type": "array", "items": { "type": "string" } }, - "settings": { "$ref": "#/components/schemas/UserSettings" }, - "id": { "type": "string" } + "fingerprints": { + "type": "array", + "items": { + "type": "string" + } + }, + "settings": { + "$ref": "#/components/schemas/UserSettings" + }, + "id": { + "type": "string" + } }, "required": [ "bio", @@ -131,29 +2907,64 @@ "Relationship": { "type": "object", "properties": { - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "nickname": { "type": "string" }, - "type": { "$ref": "#/components/schemas/RelationshipType" }, - "id": { "type": "string" } + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "nickname": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/RelationshipType" + }, + "id": { + "type": "string" + } }, "required": ["id", "type", "user", "user_id"] }, - "RelationshipType": { "enum": [1, 2, 3, 4], "type": "number" }, + "RelationshipType": { + "enum": [1, 2, 3, 4], + "type": "number" + }, "ConnectedAccount": { "type": "object", "properties": { - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "access_token": { "type": "string" }, - "friend_sync": { "type": "boolean" }, - "name": { "type": "string" }, - "revoked": { "type": "boolean" }, - "show_activity": { "type": "boolean" }, - "type": { "type": "string" }, - "verifie": { "type": "boolean" }, - "visibility": { "type": "integer" }, - "id": { "type": "string" } + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "access_token": { + "type": "string" + }, + "friend_sync": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "revoked": { + "type": "boolean" + }, + "show_activity": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + }, + "visibility": { + "type": "integer" + }, + "id": { + "type": "string" + } }, "required": [ "access_token", @@ -172,64 +2983,151 @@ "UserSettings": { "type": "object", "properties": { - "afk_timeout": { "type": "integer" }, - "allow_accessibility_detection": { "type": "boolean" }, - "animate_emoji": { "type": "boolean" }, - "animate_stickers": { "type": "integer" }, - "contact_sync_enabled": { "type": "boolean" }, - "convert_emoticons": { "type": "boolean" }, + "afk_timeout": { + "type": "integer" + }, + "allow_accessibility_detection": { + "type": "boolean" + }, + "animate_emoji": { + "type": "boolean" + }, + "animate_stickers": { + "type": "integer" + }, + "contact_sync_enabled": { + "type": "boolean" + }, + "convert_emoticons": { + "type": "boolean" + }, "custom_status": { "type": "object", "properties": { - "emoji_id": { "type": "string" }, - "emoji_name": { "type": "string" }, - "expires_at": { "type": "integer" }, - "text": { "type": "string" } + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "expires_at": { + "type": "integer" + }, + "text": { + "type": "string" + } }, "additionalProperties": false }, - "default_guilds_restricted": { "type": "boolean" }, - "detect_platform_accounts": { "type": "boolean" }, - "developer_mode": { "type": "boolean" }, - "disable_games_tab": { "type": "boolean" }, - "enable_tts_command": { "type": "boolean" }, - "explicit_content_filter": { "type": "integer" }, + "default_guilds_restricted": { + "type": "boolean" + }, + "detect_platform_accounts": { + "type": "boolean" + }, + "developer_mode": { + "type": "boolean" + }, + "disable_games_tab": { + "type": "boolean" + }, + "enable_tts_command": { + "type": "boolean" + }, + "explicit_content_filter": { + "type": "integer" + }, "friend_source_flags": { "type": "object", - "properties": { "all": { "type": "boolean" } }, + "properties": { + "all": { + "type": "boolean" + } + }, "additionalProperties": false, "required": ["all"] }, - "gateway_connected": { "type": "boolean" }, - "gif_auto_play": { "type": "boolean" }, + "gateway_connected": { + "type": "boolean" + }, + "gif_auto_play": { + "type": "boolean" + }, "guild_folders": { "type": "array", "items": { "type": "object", "properties": { - "color": { "type": "integer" }, - "guild_ids": { "type": "array", "items": { "type": "string" } }, - "id": { "type": "integer" }, - "name": { "type": "string" } + "color": { + "type": "integer" + }, + "guild_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } }, "additionalProperties": false, "required": ["color", "guild_ids", "id", "name"] } }, - "guild_positions": { "type": "array", "items": { "type": "string" } }, - "inline_attachment_media": { "type": "boolean" }, - "inline_embed_media": { "type": "boolean" }, - "locale": { "type": "string" }, - "message_display_compact": { "type": "boolean" }, - "native_phone_integration_enabled": { "type": "boolean" }, - "render_embeds": { "type": "boolean" }, - "render_reactions": { "type": "boolean" }, - "restricted_guilds": { "type": "array", "items": { "type": "string" } }, - "show_current_game": { "type": "boolean" }, - "status": { "enum": ["dnd", "idle", "offline", "online"], "type": "string" }, - "stream_notifications_enabled": { "type": "boolean" }, - "theme": { "enum": ["dark", "white"], "type": "string" }, - "timezone_offset": { "type": "integer" } + "guild_positions": { + "type": "array", + "items": { + "type": "string" + } + }, + "inline_attachment_media": { + "type": "boolean" + }, + "inline_embed_media": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "message_display_compact": { + "type": "boolean" + }, + "native_phone_integration_enabled": { + "type": "boolean" + }, + "render_embeds": { + "type": "boolean" + }, + "render_reactions": { + "type": "boolean" + }, + "restricted_guilds": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_current_game": { + "type": "boolean" + }, + "status": { + "enum": ["dnd", "idle", "offline", "online"], + "type": "string" + }, + "stream_notifications_enabled": { + "type": "boolean" + }, + "theme": { + "enum": ["dark", "white"], + "type": "string" + }, + "timezone_offset": { + "type": "integer" + } }, "required": [ "afk_timeout", @@ -268,93 +3166,264 @@ "Team": { "type": "object", "properties": { - "icon": { "type": "string" }, - "members": { "type": "array", "items": { "$ref": "#/components/schemas/TeamMember" } }, - "name": { "type": "string" }, - "owner_user_id": { "type": "string" }, - "owner_user": { "$ref": "#/components/schemas/User" }, - "id": { "type": "string" } + "icon": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamMember" + } + }, + "name": { + "type": "string" + }, + "owner_user_id": { + "type": "string" + }, + "owner_user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } }, "required": ["id", "members", "name", "owner_user", "owner_user_id"] }, "TeamMember": { "type": "object", "properties": { - "membership_state": { "$ref": "#/components/schemas/TeamMemberState" }, - "permissions": { "type": "array", "items": { "type": "string" } }, - "team_id": { "type": "string" }, - "team": { "$ref": "#/components/schemas/Team" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "id": { "type": "string" } + "membership_state": { + "$ref": "#/components/schemas/TeamMemberState" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "team_id": { + "type": "string" + }, + "team": { + "$ref": "#/components/schemas/Team" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } }, "required": ["id", "membership_state", "permissions", "team", "team_id", "user", "user_id"] }, - "TeamMemberState": { "enum": [1, 2], "type": "number" }, + "TeamMemberState": { + "enum": [1, 2], + "type": "number" + }, "Guild": { "type": "object", "properties": { - "afk_channel_id": { "type": "string" }, - "afk_channel": { "$ref": "#/components/schemas/Channel" }, - "afk_timeout": { "type": "integer" }, - "bans": { "type": "array", "items": { "$ref": "#/components/schemas/Ban" } }, - "banner": { "type": "string" }, - "default_message_notifications": { "type": "integer" }, - "description": { "type": "string" }, - "discovery_splash": { "type": "string" }, - "explicit_content_filter": { "type": "integer" }, - "features": { "type": "array", "items": { "type": "string" } }, - "icon": { "type": "string" }, - "large": { "type": "boolean" }, - "max_members": { "type": "integer" }, - "max_presences": { "type": "integer" }, - "max_video_channel_users": { "type": "integer" }, - "member_count": { "type": "integer" }, - "presence_count": { "type": "integer" }, - "members": { "type": "array", "items": { "$ref": "#/components/schemas/Member" } }, - "roles": { "type": "array", "items": { "$ref": "#/components/schemas/Role" } }, - "channels": { "type": "array", "items": { "$ref": "#/components/schemas/Channel" } }, - "template_id": { "type": "string" }, - "template": { "$ref": "#/components/schemas/Template" }, - "emojis": { "type": "array", "items": { "$ref": "#/components/schemas/Emoji" } }, - "stickers": { "type": "array", "items": { "$ref": "#/components/schemas/Sticker" } }, - "invites": { "type": "array", "items": { "$ref": "#/components/schemas/Invite" } }, - "voice_states": { "type": "array", "items": { "$ref": "#/components/schemas/VoiceState" } }, - "webhooks": { "type": "array", "items": { "$ref": "#/components/schemas/Webhook" } }, - "mfa_level": { "type": "integer" }, - "name": { "type": "string" }, - "owner_id": { "type": "string" }, - "owner": { "$ref": "#/components/schemas/User" }, - "preferred_locale": { "type": "string" }, - "premium_subscription_count": { "type": "integer" }, - "premium_tier": { "type": "integer" }, - "public_updates_channel_id": { "type": "string" }, - "public_updates_channel": { "$ref": "#/components/schemas/Channel" }, - "rules_channel_id": { "type": "string" }, - "rules_channel": { "type": "string" }, - "region": { "type": "string" }, - "splash": { "type": "string" }, - "system_channel_id": { "type": "string" }, - "system_channel": { "$ref": "#/components/schemas/Channel" }, - "system_channel_flags": { "type": "integer" }, - "unavailable": { "type": "boolean" }, - "vanity_url_code": { "type": "string" }, - "vanity_url": { "$ref": "#/components/schemas/Invite" }, - "verification_level": { "type": "integer" }, + "afk_channel_id": { + "type": "string" + }, + "afk_channel": { + "$ref": "#/components/schemas/Channel" + }, + "afk_timeout": { + "type": "integer" + }, + "bans": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Ban" + } + }, + "banner": { + "type": "string" + }, + "default_message_notifications": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "discovery_splash": { + "type": "string" + }, + "explicit_content_filter": { + "type": "integer" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "icon": { + "type": "string" + }, + "large": { + "type": "boolean" + }, + "max_members": { + "type": "integer" + }, + "max_presences": { + "type": "integer" + }, + "max_video_channel_users": { + "type": "integer" + }, + "member_count": { + "type": "integer" + }, + "presence_count": { + "type": "integer" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Member" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Channel" + } + }, + "template_id": { + "type": "string" + }, + "template": { + "$ref": "#/components/schemas/Template" + }, + "emojis": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Emoji" + } + }, + "stickers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sticker" + } + }, + "invites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Invite" + } + }, + "voice_states": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VoiceState" + } + }, + "webhooks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Webhook" + } + }, + "mfa_level": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "preferred_locale": { + "type": "string" + }, + "premium_subscription_count": { + "type": "integer" + }, + "premium_tier": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "public_updates_channel": { + "$ref": "#/components/schemas/Channel" + }, + "rules_channel_id": { + "type": "string" + }, + "rules_channel": { + "type": "string" + }, + "region": { + "type": "string" + }, + "splash": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "system_channel": { + "$ref": "#/components/schemas/Channel" + }, + "system_channel_flags": { + "type": "integer" + }, + "unavailable": { + "type": "boolean" + }, + "vanity_url_code": { + "type": "string" + }, + "vanity_url": { + "$ref": "#/components/schemas/Invite" + }, + "verification_level": { + "type": "integer" + }, "welcome_screen": { "type": "object", "properties": { - "enabled": { "type": "boolean" }, - "description": { "type": "string" }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + }, "welcome_channels": { "type": "array", "items": { "type": "object", "properties": { - "description": { "type": "string" }, - "emoji_id": { "type": "string" }, - "emoji_name": { "type": "string" }, - "channel_id": { "type": "string" } + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "channel_id": { + "type": "string" + } }, "additionalProperties": false, "required": ["channel_id", "description", "emoji_name"] @@ -364,10 +3433,18 @@ "additionalProperties": false, "required": ["description", "enabled", "welcome_channels"] }, - "widget_channel_id": { "type": "string" }, - "widget_channel": { "$ref": "#/components/schemas/Channel" }, - "widget_enabled": { "type": "boolean" }, - "id": { "type": "string" } + "widget_channel_id": { + "type": "string" + }, + "widget_channel": { + "$ref": "#/components/schemas/Channel" + }, + "widget_enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + } }, "required": [ "bans", @@ -393,29 +3470,82 @@ "Channel": { "type": "object", "properties": { - "created_at": { "type": "string", "format": "date-time" }, - "name": { "type": "string" }, - "type": { "$ref": "#/components/schemas/ChannelType" }, - "recipients": { "type": "array", "items": { "$ref": "#/components/schemas/Recipient" } }, - "last_message_id": { "type": "string" }, - "last_message": { "$ref": "#/components/schemas/Message" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "parent_id": { "type": "string" }, - "parent": { "$ref": "#/components/schemas/Channel" }, - "owner_id": { "type": "string" }, - "owner": { "$ref": "#/components/schemas/User" }, - "last_pin_timestamp": { "type": "integer" }, - "default_auto_archive_duration": { "type": "integer" }, - "position": { "type": "integer" }, - "permission_overwrites": { "type": "array", "items": { "$ref": "#/components/schemas/ChannelPermissionOverwrite" } }, - "video_quality_mode": { "type": "integer" }, - "bitrate": { "type": "integer" }, - "user_limit": { "type": "integer" }, - "nsfw": { "type": "boolean" }, - "rate_limit_per_user": { "type": "integer" }, - "topic": { "type": "string" }, - "id": { "type": "string" } + "created_at": { + "type": "string", + "format": "date-time" + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelType" + }, + "recipients": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recipient" + } + }, + "last_message_id": { + "type": "string" + }, + "last_message": { + "$ref": "#/components/schemas/Message" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "parent_id": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/Channel" + }, + "owner_id": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "last_pin_timestamp": { + "type": "integer" + }, + "default_auto_archive_duration": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelPermissionOverwrite" + } + }, + "video_quality_mode": { + "type": "integer" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "nsfw": { + "type": "boolean" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "topic": { + "type": "string" + }, + "id": { + "type": "string" + } }, "required": [ "created_at", @@ -431,78 +3561,198 @@ "type" ] }, - "ChannelType": { "enum": [0, 1, 2, 3, 4, 5, 6], "type": "number" }, + "ChannelType": { + "enum": [0, 1, 2, 3, 4, 5, 6], + "type": "number" + }, "Recipient": { "type": "object", "properties": { - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "user": { "$ref": "#/components/schemas/User" }, - "id": { "type": "string" } + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } }, "required": ["channel", "channel_id", "id", "user"] }, "Message": { "type": "object", "properties": { - "id": { "type": "string" }, - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "author_id": { "type": "string" }, - "author": { "$ref": "#/components/schemas/User" }, - "member_id": { "type": "string" }, - "member": { "$ref": "#/components/schemas/Member" }, - "webhook_id": { "type": "string" }, - "webhook": { "$ref": "#/components/schemas/Webhook" }, - "application_id": { "type": "string" }, - "application": { "$ref": "#/components/schemas/Application" }, - "content": { "type": "string" }, - "timestamp": { "type": "string", "format": "date-time" }, - "edited_timestamp": { "type": "string", "format": "date-time" }, - "tts": { "type": "boolean" }, - "mention_everyone": { "type": "boolean" }, - "mentions": { "type": "array", "items": { "$ref": "#/components/schemas/User" } }, - "mention_roles": { "type": "array", "items": { "$ref": "#/components/schemas/Role" } }, - "mention_channels": { "type": "array", "items": { "$ref": "#/components/schemas/Channel" } }, - "sticker_items": { "type": "array", "items": { "$ref": "#/components/schemas/Sticker" } }, - "attachments": { "type": "array", "items": { "$ref": "#/components/schemas/Attachment" } }, - "embeds": { "type": "array", "items": { "$ref": "#/components/schemas/Embed" } }, - "reactions": { "type": "array", "items": { "$ref": "#/components/schemas/Reaction" } }, - "nonce": { "type": "string" }, - "pinned": { "type": "boolean" }, - "type": { "$ref": "#/components/schemas/MessageType" }, + "id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "author_id": { + "type": "string" + }, + "author": { + "$ref": "#/components/schemas/User" + }, + "member_id": { + "type": "string" + }, + "member": { + "$ref": "#/components/schemas/Member" + }, + "webhook_id": { + "type": "string" + }, + "webhook": { + "$ref": "#/components/schemas/Webhook" + }, + "application_id": { + "type": "string" + }, + "application": { + "$ref": "#/components/schemas/Application" + }, + "content": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "edited_timestamp": { + "type": "string", + "format": "date-time" + }, + "tts": { + "type": "boolean" + }, + "mention_everyone": { + "type": "boolean" + }, + "mentions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "mention_roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "mention_channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Channel" + } + }, + "sticker_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sticker" + } + }, + "attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Attachment" + } + }, + "embeds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Embed" + } + }, + "reactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Reaction" + } + }, + "nonce": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "type": { + "$ref": "#/components/schemas/MessageType" + }, "activity": { "type": "object", - "properties": { "type": { "type": "integer" }, "party_id": { "type": "string" } }, + "properties": { + "type": { + "type": "integer" + }, + "party_id": { + "type": "string" + } + }, "additionalProperties": false, "required": ["party_id", "type"] }, - "flags": { "type": "string" }, + "flags": { + "type": "string" + }, "message_reference": { "type": "object", "properties": { - "message_id": { "type": "string" }, - "channel_id": { "type": "string" }, - "guild_id": { "type": "string" } + "message_id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + } }, "additionalProperties": false, "required": ["message_id"] }, - "referenced_message": { "$ref": "#/components/schemas/Message" }, + "referenced_message": { + "$ref": "#/components/schemas/Message" + }, "interaction": { "type": "object", "properties": { - "id": { "type": "string" }, - "type": { "$ref": "#/components/schemas/InteractionType" }, - "name": { "type": "string" }, - "user_id": { "type": "string" } + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/InteractionType" + }, + "name": { + "type": "string" + }, + "user_id": { + "type": "string" + } }, "additionalProperties": false, "required": ["id", "name", "type", "user_id"] }, - "components": { "type": "array", "items": { "$ref": "#/components/schemas/MessageComponent" } } + "components": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageComponent" + } + } }, "required": [ "application_id", @@ -524,44 +3774,100 @@ "Member": { "type": "object", "properties": { - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "nick": { "type": "string" }, - "roles": { "type": "array", "items": { "$ref": "#/components/schemas/Role" } }, - "joined_at": { "type": "string", "format": "date-time" }, - "premium_since": { "type": "integer" }, - "deaf": { "type": "boolean" }, - "mute": { "type": "boolean" }, - "pending": { "type": "boolean" }, - "settings": { "$ref": "#/components/schemas/UserGuildSettings" }, - "id": { "type": "string" } + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "nick": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "joined_at": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "integer" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "pending": { + "type": "boolean" + }, + "settings": { + "$ref": "#/components/schemas/UserGuildSettings" + }, + "id": { + "type": "string" + } }, "required": ["deaf", "guild", "guild_id", "id", "joined_at", "mute", "pending", "roles", "settings", "user", "user_id"] }, "Role": { "type": "object", "properties": { - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "color": { "type": "integer" }, - "hoist": { "type": "boolean" }, - "managed": { "type": "boolean" }, - "mentionable": { "type": "boolean" }, - "name": { "type": "string" }, - "permissions": { "type": "string" }, - "position": { "type": "integer" }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "managed": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "position": { + "type": "integer" + }, "tags": { "type": "object", "properties": { - "bot_id": { "type": "string" }, - "integration_id": { "type": "string" }, - "premium_subscriber": { "type": "boolean" } + "bot_id": { + "type": "string" + }, + "integration_id": { + "type": "string" + }, + "premium_subscriber": { + "type": "boolean" + } }, "additionalProperties": false }, - "id": { "type": "string" } + "id": { + "type": "string" + } }, "required": ["color", "guild", "guild_id", "hoist", "id", "managed", "mentionable", "name", "permissions", "position"] }, @@ -573,22 +3879,44 @@ "items": { "type": "object", "properties": { - "channel_id": { "type": "string" }, - "message_notifications": { "type": "integer" }, - "mute_config": { "$ref": "#/components/schemas/MuteConfig" }, - "muted": { "type": "boolean" } + "channel_id": { + "type": "string" + }, + "message_notifications": { + "type": "integer" + }, + "mute_config": { + "$ref": "#/components/schemas/MuteConfig" + }, + "muted": { + "type": "boolean" + } }, "additionalProperties": false, "required": ["channel_id", "message_notifications", "mute_config", "muted"] } }, - "message_notifications": { "type": "integer" }, - "mobile_push": { "type": "boolean" }, - "mute_config": { "$ref": "#/components/schemas/MuteConfig" }, - "muted": { "type": "boolean" }, - "suppress_everyone": { "type": "boolean" }, - "suppress_roles": { "type": "boolean" }, - "version": { "type": "integer" } + "message_notifications": { + "type": "integer" + }, + "mobile_push": { + "type": "boolean" + }, + "mute_config": { + "$ref": "#/components/schemas/MuteConfig" + }, + "muted": { + "type": "boolean" + }, + "suppress_everyone": { + "type": "boolean" + }, + "suppress_roles": { + "type": "boolean" + }, + "version": { + "type": "integer" + } }, "required": [ "channel_overrides", @@ -603,27 +3931,64 @@ }, "MuteConfig": { "type": "object", - "properties": { "end_time": { "type": "integer" }, "selected_time_window": { "type": "integer" } }, + "properties": { + "end_time": { + "type": "integer" + }, + "selected_time_window": { + "type": "integer" + } + }, "required": ["end_time", "selected_time_window"] }, "Webhook": { "type": "object", "properties": { - "id": { "type": "string" }, - "type": { "$ref": "#/components/schemas/WebhookType" }, - "name": { "type": "string" }, - "avatar": { "type": "string" }, - "token": { "type": "string" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "application_id": { "type": "string" }, - "application": { "$ref": "#/components/schemas/Application" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "source_guild_id": { "type": "string" }, - "source_guild": { "$ref": "#/components/schemas/Guild" } + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/WebhookType" + }, + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "token": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "application_id": { + "type": "string" + }, + "application": { + "$ref": "#/components/schemas/Application" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "source_guild_id": { + "type": "string" + }, + "source_guild": { + "$ref": "#/components/schemas/Guild" + } }, "required": [ "application", @@ -640,98 +4005,225 @@ "user_id" ] }, - "WebhookType": { "enum": [1, 2], "type": "number" }, + "WebhookType": { + "enum": [1, 2], + "type": "number" + }, "Application": { "type": "object", "properties": { - "name": { "type": "string" }, - "icon": { "type": "string" }, - "description": { "type": "string" }, - "rpc_origins": { "type": "array", "items": { "type": "string" } }, - "bot_public": { "type": "boolean" }, - "bot_require_code_grant": { "type": "boolean" }, - "terms_of_service_url": { "type": "string" }, - "privacy_policy_url": { "type": "string" }, - "owner": { "$ref": "#/components/schemas/User" }, - "summary": { "type": "string" }, - "verify_key": { "type": "string" }, - "team": { "$ref": "#/components/schemas/Team" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "primary_sku_id": { "type": "string" }, - "slug": { "type": "string" }, - "cover_image": { "type": "string" }, - "flags": { "type": "string" }, - "id": { "type": "string" } + "name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rpc_origins": { + "type": "array", + "items": { + "type": "string" + } + }, + "bot_public": { + "type": "boolean" + }, + "bot_require_code_grant": { + "type": "boolean" + }, + "terms_of_service_url": { + "type": "string" + }, + "privacy_policy_url": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "summary": { + "type": "string" + }, + "verify_key": { + "type": "string" + }, + "team": { + "$ref": "#/components/schemas/Team" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "primary_sku_id": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "cover_image": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "id": { + "type": "string" + } }, "required": ["bot_public", "bot_require_code_grant", "description", "flags", "guild", "id", "name", "verify_key"] }, "Sticker": { "type": "object", "properties": { - "name": { "type": "string" }, - "description": { "type": "string" }, - "tags": { "type": "string" }, - "pack_id": { "type": "string" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "type": { "$ref": "#/components/schemas/StickerType" }, - "format_type": { "$ref": "#/components/schemas/StickerFormatType" }, - "id": { "type": "string" } + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "pack_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "type": { + "$ref": "#/components/schemas/StickerType" + }, + "format_type": { + "$ref": "#/components/schemas/StickerFormatType" + }, + "id": { + "type": "string" + } }, "required": ["format_type", "id", "name", "pack_id", "tags", "type"] }, - "StickerType": { "enum": [1, 2], "type": "number" }, - "StickerFormatType": { "enum": [1, 2, 3], "type": "number" }, + "StickerType": { + "enum": [1, 2], + "type": "number" + }, + "StickerFormatType": { + "enum": [1, 2, 3], + "type": "number" + }, "Attachment": { "type": "object", "properties": { - "filename": { "type": "string" }, - "size": { "type": "integer" }, - "url": { "type": "string" }, - "proxy_url": { "type": "string" }, - "height": { "type": "integer" }, - "width": { "type": "integer" }, - "content_type": { "type": "string" }, - "message_id": { "type": "string" }, - "message": { "$ref": "#/components/schemas/Message" }, - "id": { "type": "string" } + "filename": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + }, + "content_type": { + "type": "string" + }, + "message_id": { + "type": "string" + }, + "message": { + "$ref": "#/components/schemas/Message" + }, + "id": { + "type": "string" + } }, "required": ["filename", "id", "message", "message_id", "proxy_url", "size", "url"] }, "Embed": { "type": "object", "properties": { - "title": { "type": "string" }, - "type": { "$ref": "#/components/schemas/EmbedType" }, - "description": { "type": "string" }, - "url": { "type": "string" }, - "timestamp": { "type": "string", "format": "date-time" }, - "color": { "type": "integer" }, + "title": { + "type": "string" + }, + "type": { + "enum": ["article", "gifv", "image", "link", "rich", "video"], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, "footer": { "type": "object", "properties": { - "text": { "type": "string" }, - "icon_url": { "type": "string" }, - "proxy_icon_url": { "type": "string" } + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } }, "additionalProperties": false, "required": ["text"] }, - "image": { "$ref": "#/components/schemas/EmbedImage" }, - "thumbnail": { "$ref": "#/components/schemas/EmbedImage" }, - "video": { "$ref": "#/components/schemas/EmbedImage" }, + "image": { + "$ref": "#/components/schemas/EmbedImage" + }, + "thumbnail": { + "$ref": "#/components/schemas/EmbedImage" + }, + "video": { + "$ref": "#/components/schemas/EmbedImage" + }, "provider": { "type": "object", - "properties": { "name": { "type": "string" }, "url": { "type": "string" } }, + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, "additionalProperties": false }, "author": { "type": "object", "properties": { - "name": { "type": "string" }, - "url": { "type": "string" }, - "icon_url": { "type": "string" }, - "proxy_icon_url": { "type": "string" } + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } }, "additionalProperties": false }, @@ -739,94 +4231,214 @@ "type": "array", "items": { "type": "object", - "properties": { "name": { "type": "string" }, "value": { "type": "string" }, "inline": { "type": "boolean" } }, + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, "additionalProperties": false, "required": ["name", "value"] } } } }, - "EmbedType": { "enum": ["article", "gifv", "image", "link", "rich", "video"], "type": "string" }, + "EmbedType": { + "enum": ["article", "gifv", "image", "link", "rich", "video"], + "type": "string" + }, "EmbedImage": { "type": "object", "properties": { - "url": { "type": "string" }, - "proxy_url": { "type": "string" }, - "height": { "type": "integer" }, - "width": { "type": "integer" } + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } } }, "Reaction": { "type": "object", "properties": { - "count": { "type": "integer" }, - "emoji": { "$ref": "#/components/schemas/PartialEmoji" }, - "user_ids": { "type": "array", "items": { "type": "string" } } + "count": { + "type": "integer" + }, + "emoji": { + "$ref": "#/components/schemas/PartialEmoji" + }, + "user_ids": { + "type": "array", + "items": { + "type": "string" + } + } }, "required": ["count", "emoji", "user_ids"] }, "PartialEmoji": { "type": "object", - "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "animated": { "type": "boolean" } }, + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "animated": { + "type": "boolean" + } + }, "required": ["name"] }, - "MessageType": { "enum": [0, 1, 10, 11, 12, 14, 15, 19, 2, 20, 3, 4, 5, 6, 7, 8, 9], "type": "number" }, - "InteractionType": { "enum": [1, 2], "type": "number" }, + "MessageType": { + "enum": [0, 1, 10, 11, 12, 14, 15, 19, 2, 20, 3, 4, 5, 6, 7, 8, 9], + "type": "number" + }, + "InteractionType": { + "enum": [1, 2], + "type": "number" + }, "MessageComponent": { "type": "object", "properties": { - "type": { "type": "integer" }, - "style": { "type": "integer" }, - "label": { "type": "string" }, - "emoji": { "$ref": "#/components/schemas/PartialEmoji" }, - "custom_id": { "type": "string" }, - "url": { "type": "string" }, - "disabled": { "type": "boolean" }, - "components": { "type": "array", "items": { "$ref": "#/components/schemas/MessageComponent" } } + "type": { + "type": "integer" + }, + "style": { + "type": "integer" + }, + "label": { + "type": "string" + }, + "emoji": { + "$ref": "#/components/schemas/PartialEmoji" + }, + "custom_id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "components": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageComponent" + } + } }, "required": ["components", "type"] }, "ChannelPermissionOverwrite": { "type": "object", "properties": { - "allow": { "type": "number" }, - "deny": { "type": "number" }, - "id": { "type": "string" }, - "type": { "$ref": "#/components/schemas/ChannelPermissionOverwriteType" } + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + } }, "required": ["allow", "deny", "id", "type"] }, - "ChannelPermissionOverwriteType": { "enum": [0, 1], "type": "number" }, + "ChannelPermissionOverwriteType": { + "enum": [0, 1], + "type": "number" + }, "Ban": { "type": "object", "properties": { - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "executor_id": { "type": "string" }, - "executor": { "$ref": "#/components/schemas/User" }, - "ip": { "type": "string" }, - "reason": { "type": "string" }, - "id": { "type": "string" } + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "executor_id": { + "type": "string" + }, + "executor": { + "$ref": "#/components/schemas/User" + }, + "ip": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "id": { + "type": "string" + } }, "required": ["executor", "executor_id", "guild", "guild_id", "id", "ip", "user", "user_id"] }, "Template": { "type": "object", "properties": { - "code": { "type": "string" }, - "name": { "type": "string" }, - "description": { "type": "string" }, - "usage_count": { "type": "integer" }, - "creator_id": { "type": "string" }, - "creator": { "$ref": "#/components/schemas/User" }, - "created_at": { "type": "string", "format": "date-time" }, - "updated_at": { "type": "string", "format": "date-time" }, - "source_guild_id": { "type": "string" }, - "source_guild": { "$ref": "#/components/schemas/Guild" }, - "serialized_source_guild": { "$ref": "#/components/schemas/Guild" }, - "id": { "type": "string" } + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "usage_count": { + "type": "integer" + }, + "creator_id": { + "type": "string" + }, + "creator": { + "$ref": "#/components/schemas/User" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "source_guild_id": { + "type": "string" + }, + "source_guild": { + "$ref": "#/components/schemas/Guild" + }, + "serialized_source_guild": { + "$ref": "#/components/schemas/Guild" + }, + "id": { + "type": "string" + } }, "required": [ "code", @@ -844,37 +4456,89 @@ "Emoji": { "type": "object", "properties": { - "animated": { "type": "boolean" }, - "available": { "type": "boolean" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "managed": { "type": "boolean" }, - "name": { "type": "string" }, - "require_colons": { "type": "boolean" }, - "id": { "type": "string" } + "animated": { + "type": "boolean" + }, + "available": { + "type": "boolean" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "managed": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "require_colons": { + "type": "boolean" + }, + "id": { + "type": "string" + } }, "required": ["animated", "available", "guild", "guild_id", "id", "managed", "name", "require_colons"] }, "Invite": { "type": "object", "properties": { - "code": { "type": "string" }, - "temporary": { "type": "boolean" }, - "uses": { "type": "integer" }, - "max_uses": { "type": "integer" }, - "max_age": { "type": "integer" }, - "created_at": { "type": "string", "format": "date-time" }, - "expires_at": { "type": "string", "format": "date-time" }, - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "inviter_id": { "type": "string" }, - "inviter": { "$ref": "#/components/schemas/User" }, - "target_user_id": { "type": "string" }, - "target_user": { "type": "string" }, - "target_user_type": { "type": "integer" }, - "id": { "type": "string" } + "code": { + "type": "string" + }, + "temporary": { + "type": "boolean" + }, + "uses": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "max_age": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "inviter_id": { + "type": "string" + }, + "inviter": { + "$ref": "#/components/schemas/User" + }, + "target_user_id": { + "type": "string" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + }, + "id": { + "type": "string" + } }, "required": [ "channel", @@ -897,21 +4561,51 @@ "VoiceState": { "type": "object", "properties": { - "guild_id": { "type": "string" }, - "guild": { "$ref": "#/components/schemas/Guild" }, - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "session_id": { "type": "string" }, - "deaf": { "type": "boolean" }, - "mute": { "type": "boolean" }, - "self_deaf": { "type": "boolean" }, - "self_mute": { "type": "boolean" }, - "self_stream": { "type": "boolean" }, - "self_video": { "type": "boolean" }, - "suppress": { "type": "boolean" }, - "id": { "type": "string" } + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "session_id": { + "type": "string" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_mute": { + "type": "boolean" + }, + "self_stream": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" + }, + "suppress": { + "type": "boolean" + }, + "id": { + "type": "string" + } }, "required": [ "channel", @@ -939,151 +4633,386 @@ "AuditLogChange": { "type": "object", "properties": { - "new_value": { "$ref": "#/components/schemas/AuditLogChangeValue" }, - "old_value": { "$ref": "#/components/schemas/AuditLogChangeValue" }, - "key": { "type": "string" } + "new_value": { + "$ref": "#/components/schemas/AuditLogChangeValue" + }, + "old_value": { + "$ref": "#/components/schemas/AuditLogChangeValue" + }, + "key": { + "type": "string" + } }, "required": ["key"] }, "AuditLogChangeValue": { "type": "object", "properties": { - "name": { "type": "string" }, - "description": { "type": "string" }, - "icon_hash": { "type": "string" }, - "splash_hash": { "type": "string" }, - "discovery_splash_hash": { "type": "string" }, - "banner_hash": { "type": "string" }, - "owner_id": { "type": "string" }, - "region": { "type": "string" }, - "preferred_locale": { "type": "string" }, - "afk_channel_id": { "type": "string" }, - "afk_timeout": { "type": "integer" }, - "rules_channel_id": { "type": "string" }, - "public_updates_channel_id": { "type": "string" }, - "mfa_level": { "type": "integer" }, - "verification_level": { "type": "integer" }, - "explicit_content_filter": { "type": "integer" }, - "default_message_notifications": { "type": "integer" }, - "vanity_url_code": { "type": "string" }, - "$add": { "type": "array", "items": { "type": "object", "properties": {} } }, - "$remove": { "type": "array", "items": { "type": "object", "properties": {} } }, - "prune_delete_days": { "type": "integer" }, - "widget_enabled": { "type": "boolean" }, - "widget_channel_id": { "type": "string" }, - "system_channel_id": { "type": "string" }, - "position": { "type": "integer" }, - "topic": { "type": "string" }, - "bitrate": { "type": "integer" }, - "permission_overwrites": { "type": "array", "items": { "$ref": "#/components/schemas/ChannelPermissionOverwrite" } }, - "nsfw": { "type": "boolean" }, - "application_id": { "type": "string" }, - "rate_limit_per_user": { "type": "integer" }, - "permissions": { "type": "string" }, - "color": { "type": "integer" }, - "hoist": { "type": "boolean" }, - "mentionable": { "type": "boolean" }, - "allow": { "type": "string" }, - "deny": { "type": "string" }, - "code": { "type": "string" }, - "channel_id": { "type": "string" }, - "inviter_id": { "type": "string" }, - "max_uses": { "type": "integer" }, - "uses": { "type": "integer" }, - "max_age": { "type": "integer" }, - "temporary": { "type": "boolean" }, - "deaf": { "type": "boolean" }, - "mute": { "type": "boolean" }, - "nick": { "type": "string" }, - "avatar_hash": { "type": "string" }, - "id": { "type": "string" }, - "type": { "type": "integer" }, - "enable_emoticons": { "type": "boolean" }, - "expire_behavior": { "type": "integer" }, - "expire_grace_period": { "type": "integer" }, - "user_limit": { "type": "integer" } + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "icon_hash": { + "type": "string" + }, + "splash_hash": { + "type": "string" + }, + "discovery_splash_hash": { + "type": "string" + }, + "banner_hash": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "region": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "afk_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "rules_channel_id": { + "type": "string" + }, + "public_updates_channel_id": { + "type": "string" + }, + "mfa_level": { + "type": "integer" + }, + "verification_level": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "vanity_url_code": { + "type": "string" + }, + "$add": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "$remove": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "prune_delete_days": { + "type": "integer" + }, + "widget_enabled": { + "type": "boolean" + }, + "widget_channel_id": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelPermissionOverwrite" + } + }, + "nsfw": { + "type": "boolean" + }, + "application_id": { + "type": "string" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "permissions": { + "type": "string" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + }, + "code": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "inviter_id": { + "type": "string" + }, + "max_uses": { + "type": "integer" + }, + "uses": { + "type": "integer" + }, + "max_age": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "nick": { + "type": "string" + }, + "avatar_hash": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "enable_emoticons": { + "type": "boolean" + }, + "expire_behavior": { + "type": "integer" + }, + "expire_grace_period": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + } } }, "AuditLog": { "type": "object", "properties": { - "target": { "$ref": "#/components/schemas/User" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "action_type": { "$ref": "#/components/schemas/AuditLogEvents" }, + "target": { + "$ref": "#/components/schemas/User" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "action_type": { + "$ref": "#/components/schemas/AuditLogEvents" + }, "options": { "type": "object", "properties": { - "delete_member_days": { "type": "string" }, - "members_removed": { "type": "string" }, - "channel_id": { "type": "string" }, - "messaged_id": { "type": "string" }, - "count": { "type": "string" }, - "id": { "type": "string" }, - "type": { "type": "string" }, - "role_name": { "type": "string" } + "delete_member_days": { + "type": "string" + }, + "members_removed": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "messaged_id": { + "type": "string" + }, + "count": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "role_name": { + "type": "string" + } }, "additionalProperties": false }, - "changes": { "type": "array", "items": { "$ref": "#/components/schemas/AuditLogChange" } }, - "reason": { "type": "string" }, - "id": { "type": "string" } + "changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLogChange" + } + }, + "reason": { + "type": "string" + }, + "id": { + "type": "string" + } }, "required": ["action_type", "changes", "id", "user", "user_id"] }, "ReadState": { "type": "object", "properties": { - "channel_id": { "type": "string" }, - "channel": { "$ref": "#/components/schemas/Channel" }, - "user_id": { "type": "string" }, - "user": { "$ref": "#/components/schemas/User" }, - "last_message_id": { "type": "string" }, - "last_message": { "$ref": "#/components/schemas/Message" }, - "last_pin_timestamp": { "type": "string", "format": "date-time" }, - "mention_count": { "type": "integer" }, - "manual": { "type": "boolean" }, - "id": { "type": "string" } + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "last_message_id": { + "type": "string" + }, + "last_message": { + "$ref": "#/components/schemas/Message" + }, + "last_pin_timestamp": { + "type": "string", + "format": "date-time" + }, + "mention_count": { + "type": "integer" + }, + "manual": { + "type": "boolean" + }, + "id": { + "type": "string" + } }, "required": ["channel", "channel_id", "id", "last_message_id", "manual", "mention_count", "user", "user_id"] }, "UserPublic": { "type": "object", "properties": { - "username": { "type": "string" }, - "discriminator": { "type": "string" }, - "id": { "type": "string" }, - "public_flags": { "type": "string" }, - "avatar": { "type": "string" }, - "accent_color": { "type": "integer" }, - "banner": { "type": "string" }, - "bio": { "type": "string" }, - "bot": { "type": "boolean" } + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } }, "required": ["bio", "bot", "discriminator", "id", "public_flags", "username"] }, "UserPrivate": { "type": "object", "properties": { - "locale": { "type": "string" }, - "disabled": { "type": "boolean" }, - "username": { "type": "string" }, - "discriminator": { "type": "string" }, - "id": { "type": "string" }, - "public_flags": { "type": "string" }, - "avatar": { "type": "string" }, - "accent_color": { "type": "integer" }, - "banner": { "type": "string" }, - "bio": { "type": "string" }, - "bot": { "type": "boolean" }, - "flags": { "type": "string" }, - "mfa_enabled": { "type": "boolean" }, - "email": { "type": "string" }, - "phone": { "type": "string" }, - "verified": { "type": "boolean" }, - "nsfw_allowed": { "type": "boolean" }, - "premium": { "type": "boolean" }, - "premium_type": { "type": "integer" } + "locale": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "mfa_enabled": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "verified": { + "type": "boolean" + }, + "nsfw_allowed": { + "type": "boolean" + }, + "premium": { + "type": "boolean" + }, + "premium_type": { + "type": "integer" + } }, "required": [ "bio", @@ -1104,50 +5033,111 @@ }, "BanCreateSchema": { "type": "object", - "properties": { "delete_message_days": { "type": "string" }, "reason": { "type": "string" } } + "properties": { + "delete_message_days": { + "type": "string" + }, + "reason": { + "type": "string" + } + } }, "DmChannelCreateSchema": { "type": "object", - "properties": { "name": { "type": "string" }, "recipients": { "type": "array", "items": { "type": "string" } } }, + "properties": { + "name": { + "type": "string" + }, + "recipients": { + "type": "array", + "items": { + "type": "string" + } + } + }, "required": ["recipients"] }, "ChannelModifySchema": { "type": "object", "properties": { - "name": { "type": "string" }, - "type": { "type": "integer" }, - "topic": { "type": "string" }, - "bitrate": { "type": "integer" }, - "user_limit": { "type": "integer" }, - "rate_limit_per_user": { "type": "integer" }, - "position": { "type": "integer" }, + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [0, 1, 10, 11, 12, 13, 2, 3, 4, 5, 6], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, "permission_overwrites": { "type": "array", "items": { "type": "object", "properties": { - "id": { "type": "string" }, - "type": { "type": "integer" }, - "allow": { "type": "number" }, - "deny": { "type": "number" } + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + } }, "additionalProperties": false, "required": ["allow", "deny", "id", "type"] } }, - "parent_id": { "type": "string" }, - "id": { "type": "string" }, - "nsfw": { "type": "boolean" }, - "rtc_region": { "type": "string" }, - "default_auto_archive_duration": { "type": "integer" } - }, - "required": ["name", "type"] + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + } }, "ChannelGuildPositionUpdateSchema": { "type": "array", "items": { "type": "object", - "properties": { "id": { "type": "string" }, "position": { "type": "integer" } }, + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, "additionalProperties": false, "required": ["id"] } @@ -1155,52 +5145,131 @@ "EmojiCreateSchema": { "type": "object", "properties": { - "name": { "type": "string" }, - "image": { "type": "string" }, - "roles": { "type": "array", "items": { "type": "string" } } + "name": { + "type": "string" + }, + "image": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } }, "required": ["image", "name"] }, "GuildCreateSchema": { "type": "object", "properties": { - "name": { "type": "string" }, - "region": { "type": "string" }, - "icon": { "type": "string" }, - "channels": { "type": "array", "items": { "$ref": "#/components/requestBodies/ChannelModifySchema" } }, - "guild_template_code": { "type": "string" }, - "system_channel_id": { "type": "string" }, - "rules_channel_id": { "type": "string" } + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } }, "required": ["name"] }, "GuildUpdateSchema": { "type": "object", "properties": { - "banner": { "type": "string" }, - "splash": { "type": "string" }, - "description": { "type": "string" }, - "features": { "type": "array", "items": { "type": "string" } }, - "verification_level": { "type": "integer" }, - "default_message_notifications": { "type": "integer" }, - "system_channel_flags": { "type": "integer" }, - "explicit_content_filter": { "type": "integer" }, - "public_updates_channel_id": { "type": "string" }, - "afk_timeout": { "type": "integer" }, - "afk_channel_id": { "type": "string" }, - "preferred_locale": { "type": "string" }, - "name": { "type": "string" }, - "region": { "type": "string" }, - "icon": { "type": "string" }, - "guild_template_code": { "type": "string" }, - "system_channel_id": { "type": "string" }, - "rules_channel_id": { "type": "string" } + "banner": { + "type": "string", + "nullable": true + }, + "splash": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "verification_level": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "system_channel_flags": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "afk_channel_id": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } }, "required": ["name"] }, "GuildTemplateCreateSchema": { "type": "object", - "properties": { "name": { "type": "string" }, "avatar": { "type": "string" } }, + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string", + "nullable": true + } + }, "required": ["name"] }, "GuildUpdateWelcomeScreenSchema": { @@ -1211,225 +5280,411 @@ "items": { "type": "object", "properties": { - "channel_id": { "type": "string" }, - "description": { "type": "string" }, - "emoji_id": { "type": "string" }, - "emoji_name": { "type": "string" } + "channel_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + } }, "additionalProperties": false, "required": ["channel_id", "description", "emoji_name"] } }, - "enabled": { "type": "boolean" }, - "description": { "type": "string" } + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + } } }, "InviteCreateSchema": { "type": "object", "properties": { - "target_user_id": { "type": "string" }, - "target_type": { "type": "string" }, - "validate": { "type": "string" }, - "max_age": { "type": "integer" }, - "max_uses": { "type": "integer" }, - "temporary": { "type": "boolean" }, - "unique": { "type": "boolean" }, - "target_user": { "type": "string" }, - "target_user_type": { "type": "integer" } + "target_user_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "validate": { + "type": "string" + }, + "max_age": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + } } }, "MemberCreateSchema": { "type": "object", "properties": { - "id": { "type": "string" }, - "nick": { "type": "string" }, - "guild_id": { "type": "string" }, - "joined_at": { "type": "string", "format": "date-time" } + "id": { + "type": "string" + }, + "nick": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "joined_at": { + "type": "string", + "format": "date-time" + } }, "required": ["guild_id", "id", "joined_at", "nick"] }, - "MemberNickChangeSchema": { "type": "object", "properties": { "nick": { "type": "string" } }, "required": ["nick"] }, - "MemberChangeSchema": { "type": "object", "properties": { "roles": { "type": "array", "items": { "type": "string" } } } }, + "MemberNickChangeSchema": { + "type": "object", + "properties": { + "nick": { + "type": "string" + } + }, + "required": ["nick"] + }, + "MemberChangeSchema": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "MessageCreateSchema": { "type": "object", "properties": { - "content": { "type": "string" }, - "nonce": { "type": "string" }, - "tts": { "type": "boolean" }, - "flags": { "type": "string" }, - "embed": { - "additionalProperties": false, - "type": "object", - "properties": { - "title": { "type": "string" }, - "type": { "$ref": "#/components/requestBodies/EmbedType" }, - "description": { "type": "string" }, - "url": { "type": "string" }, - "timestamp": { "type": "string" }, - "color": { "type": "integer" }, - "footer": { - "type": "object", - "properties": { - "text": { "type": "string" }, - "icon_url": { "type": "string" }, - "proxy_icon_url": { "type": "string" } - }, - "additionalProperties": false, - "required": ["text"] - }, - "image": { "$ref": "#/components/requestBodies/EmbedImage" }, - "thumbnail": { "$ref": "#/components/requestBodies/EmbedImage" }, - "video": { "$ref": "#/components/requestBodies/EmbedImage" }, - "provider": { - "type": "object", - "properties": { "name": { "type": "string" }, "url": { "type": "string" } }, - "additionalProperties": false - }, - "author": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "url": { "type": "string" }, - "icon_url": { "type": "string" }, - "proxy_icon_url": { "type": "string" } - }, - "additionalProperties": false - }, - "fields": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "value": { "type": "string" }, - "inline": { "type": "boolean" } - }, - "additionalProperties": false, - "required": ["name", "value"] - } - } + "content": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "tts": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "embeds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Embed" } }, + "embed": { + "$ref": "#/components/schemas/Embed" + }, "allowed_mentions": { "type": "object", "properties": { - "parse": { "type": "array", "items": { "type": "string" } }, - "roles": { "type": "array", "items": { "type": "string" } }, - "users": { "type": "array", "items": { "type": "string" } }, - "replied_user": { "type": "boolean" } + "parse": { + "type": "array", + "items": { + "type": "string" + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + }, + "replied_user": { + "type": "boolean" + } }, "additionalProperties": false }, "message_reference": { "type": "object", "properties": { - "message_id": { "type": "string" }, - "channel_id": { "type": "string" }, - "guild_id": { "type": "string" }, - "fail_if_not_exists": { "type": "boolean" } + "message_id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "fail_if_not_exists": { + "type": "boolean" + } }, "additionalProperties": false, "required": ["channel_id", "message_id"] }, - "payload_json": { "type": "string" }, - "file": {} + "payload_json": { + "type": "string" + }, + "file": {}, + "attachments": { + "type": "array", + "items": {} + } } }, "RoleModifySchema": { "type": "object", "properties": { - "name": { "type": "string" }, - "permissions": { "type": "number" }, - "color": { "type": "integer" }, - "hoist": { "type": "boolean" }, - "mentionable": { "type": "boolean" }, - "position": { "type": "integer" } + "name": { + "type": "string" + }, + "permissions": { + "type": "number" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "position": { + "type": "integer" + } } }, "TemplateCreateSchema": { "type": "object", - "properties": { "name": { "type": "string" }, "description": { "type": "string" } }, + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, "required": ["name"] }, "TemplateModifySchema": { "type": "object", - "properties": { "name": { "type": "string" }, "description": { "type": "string" } }, + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, "required": ["name"] }, "UserModifySchema": { "type": "object", "properties": { - "username": { "type": "string" }, - "avatar": { "type": "string" }, - "bio": { "type": "string" }, - "accent_color": { "type": "integer" }, - "banner": { "type": "string" }, - "password": { "type": "string" }, - "new_password": { "type": "string" }, - "code": { "type": "string" } + "username": { + "minLength": 1, + "maxLength": 100, + "type": "string" + }, + "avatar": { + "type": "string", + "nullable": true + }, + "bio": { + "maxLength": 1024, + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string", + "nullable": true + }, + "password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "code": { + "type": "string" + } } }, "UserSettingsSchema": { "type": "object", "properties": { - "afk_timeout": { "type": "integer" }, - "allow_accessibility_detection": { "type": "boolean" }, - "animate_emoji": { "type": "boolean" }, - "animate_stickers": { "type": "integer" }, - "contact_sync_enabled": { "type": "boolean" }, - "convert_emoticons": { "type": "boolean" }, + "afk_timeout": { + "type": "integer" + }, + "allow_accessibility_detection": { + "type": "boolean" + }, + "animate_emoji": { + "type": "boolean" + }, + "animate_stickers": { + "type": "integer" + }, + "contact_sync_enabled": { + "type": "boolean" + }, + "convert_emoticons": { + "type": "boolean" + }, "custom_status": { "type": "object", "properties": { - "emoji_id": { "type": "string" }, - "emoji_name": { "type": "string" }, - "expires_at": { "type": "integer" }, - "text": { "type": "string" } + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "expires_at": { + "type": "integer" + }, + "text": { + "type": "string" + } }, "additionalProperties": false }, - "default_guilds_restricted": { "type": "boolean" }, - "detect_platform_accounts": { "type": "boolean" }, - "developer_mode": { "type": "boolean" }, - "disable_games_tab": { "type": "boolean" }, - "enable_tts_command": { "type": "boolean" }, - "explicit_content_filter": { "type": "integer" }, + "default_guilds_restricted": { + "type": "boolean" + }, + "detect_platform_accounts": { + "type": "boolean" + }, + "developer_mode": { + "type": "boolean" + }, + "disable_games_tab": { + "type": "boolean" + }, + "enable_tts_command": { + "type": "boolean" + }, + "explicit_content_filter": { + "type": "integer" + }, "friend_source_flags": { "type": "object", - "properties": { "all": { "type": "boolean" } }, + "properties": { + "all": { + "type": "boolean" + } + }, "additionalProperties": false, "required": ["all"] }, - "gateway_connected": { "type": "boolean" }, - "gif_auto_play": { "type": "boolean" }, + "gateway_connected": { + "type": "boolean" + }, + "gif_auto_play": { + "type": "boolean" + }, "guild_folders": { "type": "array", "items": { "type": "object", "properties": { - "color": { "type": "integer" }, - "guild_ids": { "type": "array", "items": { "type": "string" } }, - "id": { "type": "integer" }, - "name": { "type": "string" } + "color": { + "type": "integer" + }, + "guild_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } }, "additionalProperties": false, "required": ["color", "guild_ids", "id", "name"] } }, - "guild_positions": { "type": "array", "items": { "type": "string" } }, - "inline_attachment_media": { "type": "boolean" }, - "inline_embed_media": { "type": "boolean" }, - "locale": { "type": "string" }, - "message_display_compact": { "type": "boolean" }, - "native_phone_integration_enabled": { "type": "boolean" }, - "render_embeds": { "type": "boolean" }, - "render_reactions": { "type": "boolean" }, - "restricted_guilds": { "type": "array", "items": { "type": "string" } }, - "show_current_game": { "type": "boolean" }, - "status": { "enum": ["dnd", "idle", "offline", "online"], "type": "string" }, - "stream_notifications_enabled": { "type": "boolean" }, - "theme": { "enum": ["dark", "white"], "type": "string" }, - "timezone_offset": { "type": "integer" } + "guild_positions": { + "type": "array", + "items": { + "type": "string" + } + }, + "inline_attachment_media": { + "type": "boolean" + }, + "inline_embed_media": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "message_display_compact": { + "type": "boolean" + }, + "native_phone_integration_enabled": { + "type": "boolean" + }, + "render_embeds": { + "type": "boolean" + }, + "render_reactions": { + "type": "boolean" + }, + "restricted_guilds": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_current_game": { + "type": "boolean" + }, + "status": { + "enum": ["dnd", "idle", "offline", "online"], + "type": "string" + }, + "stream_notifications_enabled": { + "type": "boolean" + }, + "theme": { + "enum": ["dark", "white"], + "type": "string" + }, + "timezone_offset": { + "type": "integer" + } }, "required": [ "afk_timeout", @@ -1467,8 +5722,263 @@ }, "WidgetModifySchema": { "type": "object", - "properties": { "enabled": { "type": "boolean" }, "channel_id": { "type": "string" } }, + "properties": { + "enabled": { + "type": "boolean" + }, + "channel_id": { + "type": "string" + } + }, "required": ["channel_id", "enabled"] + }, + "RegisterSchema": { + "type": "object", + "properties": { + "username": { + "minLength": 2, + "maxLength": 32, + "type": "string" + }, + "password": { + "minLength": 1, + "maxLength": 72, + "type": "string" + }, + "consent": { + "type": "boolean" + }, + "email": { + "format": "email", + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "invite": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "gift_code_sku_id": { + "type": "string" + }, + "captcha_key": { + "type": "string" + } + }, + "required": ["consent", "username"] + }, + "LoginSchema": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + }, + "undelete": { + "type": "boolean" + }, + "captcha_key": { + "type": "string" + }, + "login_source": { + "type": "string" + }, + "gift_code_sku_id": { + "type": "string" + } + }, + "required": ["login", "password"] + }, + "MessageAcknowledgeSchema": { + "type": "object", + "properties": { + "manual": { + "type": "boolean" + }, + "mention_count": { + "type": "integer" + } + } + }, + "BulkDeleteSchema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["messages"] + }, + "ChannelPermissionOverwriteSchema": { + "type": "object", + "properties": { + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + } + }, + "required": ["allow", "deny", "id", "type"] + }, + "WebhookCreateSchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 80, + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "required": ["avatar", "name"] + }, + "ChannelReorderSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "lock_permissions": { + "type": "boolean" + }, + "parent_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": ["id"] + } + }, + "RolePositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": ["id", "position"] + } + }, + "VanityUrlSchema": { + "type": "object", + "properties": { + "code": { + "minLength": 1, + "maxLength": 20, + "type": "string" + } + } + }, + "VoiceStateUpdateSchema": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "suppress": { + "type": "boolean" + }, + "request_to_speak_timestamp": { + "type": "string", + "format": "date-time" + }, + "self_mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" + } + }, + "required": ["channel_id"] + }, + "UserProfileResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/UserPublic" + }, + "connected_accounts": { + "$ref": "#/components/schemas/PublicConnectedAccount" + }, + "premium_guild_since": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "required": ["connected_accounts", "user"] + }, + "RelationshipPutSchema": { + "type": "object", + "properties": { + "type": { + "enum": [1, 2, 3, 4], + "type": "number" + } + } + }, + "RelationshipPostSchema": { + "type": "object", + "properties": { + "discriminator": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": ["discriminator", "username"] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "required": ["name", "type", "verifie"] } }, "requestBodies": {}, @@ -1482,4 +5992,4 @@ "links": {}, "callbacks": {} } -} \ No newline at end of file +} diff --git a/api/assets/responses.json b/api/assets/responses.json deleted file mode 100644 index 35645d73..00000000 --- a/api/assets/responses.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "UserProfileResponse": { - "type": "object", - "properties": { - "user": { - "$ref": "#/definitions/UserPublic" - }, - "connected_accounts": { - "$ref": "#/definitions/PublicConnectedAccount" - }, - "premium_guild_since": { - "type": "string", - "format": "date-time" - }, - "premium_since": { - "type": "string", - "format": "date-time" - } - }, - "additionalProperties": false, - "required": [ - "connected_accounts", - "user" - ], - "definitions": { - "UserPublic": { - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "discriminator": { - "type": "string" - }, - "id": { - "type": "string" - }, - "public_flags": { - "type": "string" - }, - "avatar": { - "type": "string" - }, - "accent_color": { - "type": "integer" - }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, - "bot": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "bio", - "bot", - "discriminator", - "id", - "public_flags", - "username" - ] - }, - "PublicConnectedAccount": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "verifie": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "name", - "type", - "verifie" - ] - } - }, - "$schema": "http://json-schema.org/draft-07/schema#" - } -} \ No newline at end of file diff --git a/api/assets/schemas.json b/api/assets/schemas.json index cc45ebb3..76ad3b16 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -385,6 +385,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -698,6 +759,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -960,6 +1082,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1201,6 +1384,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1445,6 +1689,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1698,6 +2003,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -1944,6 +2310,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2185,6 +2612,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2438,6 +2926,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -2704,6 +3253,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3009,6 +3619,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3250,6 +3921,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3491,6 +4223,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3744,6 +4537,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -3992,6 +4846,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4236,6 +5151,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4480,6 +5456,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4720,6 +5757,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -4980,6 +6078,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5247,6 +6406,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5492,6 +6712,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5739,6 +7020,381 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "UserProfileResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/UserPublic" + }, + "connected_accounts": { + "$ref": "#/definitions/PublicConnectedAccount" + }, + "premium_guild_since": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "connected_accounts", + "user" + ], + "definitions": { + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": [ + "null", + "string" + ] + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "bigint" + }, + "deny": { + "type": "bigint" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -5986,6 +7642,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6254,6 +7971,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6498,6 +8276,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -6743,6 +8582,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" @@ -7171,6 +9071,67 @@ } }, "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verifie" + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/api/jest/getRouteDescriptions.ts b/api/jest/getRouteDescriptions.js similarity index 72% rename from api/jest/getRouteDescriptions.ts rename to api/jest/getRouteDescriptions.js index 33922899..4f8d2e75 100644 --- a/api/jest/getRouteDescriptions.ts +++ b/api/jest/getRouteDescriptions.js @@ -1,11 +1,15 @@ -import { traverseDirectory } from "lambert-server"; -import path from "path"; -import express from "express"; -import * as RouteUtility from "../dist/util/route"; -import { RouteOptions } from "../dist/util/route"; +const { traverseDirectory } = require("lambert-server"); +const path = require("path"); +const express = require("express"); +const RouteUtility = require("../dist/util/route"); const Router = express.Router; -const routes = new Map(); +/** + * Some documentation. + * + * @type {Map} + */ +const routes = new Map(); let currentPath = ""; let currentFile = ""; const methods = ["get", "post", "put", "delete", "patch"]; @@ -13,13 +17,13 @@ const methods = ["get", "post", "put", "delete", "patch"]; function registerPath(file, method, prefix, path, ...args) { const urlPath = prefix + path; const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); - const opts: RouteOptions = args.find((x) => typeof x === "object"); + const opts = args.find((x) => typeof x === "object"); if (opts) { routes.set(urlPath + "|" + method, opts); // @ts-ignore opts.file = sourceFile; // console.log(method, urlPath, opts); } else { - console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args); + console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`); } } @@ -42,7 +46,7 @@ express.Router = (opts) => { return router; }; -export default function getRouteDescriptions() { +module.exports = function getRouteDescriptions() { const root = path.join(__dirname, "..", "dist", "routes", "/"); traverseDirectory({ dirname: root, recursive: true }, (file) => { currentFile = file; @@ -52,7 +56,11 @@ export default function getRouteDescriptions() { if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path currentPath = path; - require(file); + try { + require(file); + } catch (error) { + console.error("error loading file " + file, error); + } }); return routes; -} +}; diff --git a/api/scripts/generate_test_schema.ts b/api/scripts/generate_body_schema.js similarity index 75% rename from api/scripts/generate_test_schema.ts rename to api/scripts/generate_body_schema.js index eed77738..22d0b02e 100644 --- a/api/scripts/generate_test_schema.ts +++ b/api/scripts/generate_body_schema.js @@ -4,9 +4,9 @@ import path from "path"; import fs from "fs"; import * as TJS from "typescript-json-schema"; import "missing-native-js-functions"; -const schemaPath = path.join(__dirname, "..", "assets", "responses.json"); +const schemaPath = path.join(__dirname, "..", "assets", "schemas.json"); -const settings: TJS.PartialArgs = { +const settings = { required: true, ignoreErrors: true, excludePrivate: true, @@ -14,10 +14,13 @@ const settings: TJS.PartialArgs = { noExtraProps: true, defaultProps: false }; -const compilerOptions: TJS.CompilerOptions = { +const compilerOptions = { strictNullChecks: true }; -const ExcludedSchemas = [ +const Excluded = [ + "DefaultSchema", + "Schema", + "EntitySchema", "ServerResponse", "Http2ServerResponse", "global.Express.Response", @@ -32,13 +35,13 @@ function main() { const generator = TJS.buildGenerator(program, settings); if (!generator || !program) return; - const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x)); + const schemas = generator.getUserSymbols().filter((x) => (x.endsWith("Schema") || x.endsWith("Response")) && !Excluded.includes(x)); console.log(schemas); - var definitions: any = {}; + var definitions = {}; for (const name of schemas) { - const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); + const part = TJS.generateSchema(program, name, settings, [], generator); if (!part) continue; definitions = { ...definitions, [name]: { ...part } }; @@ -47,11 +50,10 @@ function main() { fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); } -// #/definitions/ main(); -function walk(dir: string) { - var results = [] as string[]; +function walk(dir) { + var results = []; var list = fs.readdirSync(dir); list.forEach(function (file) { file = dir + "/" + file; diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts deleted file mode 100644 index 316e5a69..00000000 --- a/api/scripts/generate_body_schema.ts +++ /dev/null @@ -1,60 +0,0 @@ -// https://mermade.github.io/openapi-gui/# -// https://editor.swagger.io/ -import path from "path"; -import fs from "fs"; -import * as TJS from "typescript-json-schema"; -import "missing-native-js-functions"; -const schemaPath = path.join(__dirname, "..", "assets", "schemas.json"); - -const settings: TJS.PartialArgs = { - required: true, - ignoreErrors: true, - excludePrivate: true, - defaultNumberType: "integer", - noExtraProps: true, - defaultProps: false -}; -const compilerOptions: TJS.CompilerOptions = { - strictNullChecks: true -}; -const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"]; - -function main() { - const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions); - const generator = TJS.buildGenerator(program, settings); - if (!generator || !program) return; - - const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x)); - console.log(schemas); - - var definitions: any = {}; - - for (const name of schemas) { - const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator); - if (!part) continue; - - definitions = { ...definitions, [name]: { ...part } }; - } - - fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); -} - -// #/definitions/ -main(); - -function walk(dir: string) { - var results = [] as string[]; - var list = fs.readdirSync(dir); - list.forEach(function (file) { - file = dir + "/" + file; - var stat = fs.statSync(file); - if (stat && stat.isDirectory()) { - /* Recurse into a subdirectory */ - results = results.concat(walk(file)); - } else { - if (!file.endsWith(".ts")) return; - results.push(file); - } - }); - return results; -} diff --git a/api/scripts/generate_openapi_schema.js b/api/scripts/generate_openapi_schema.js new file mode 100644 index 00000000..eb979f14 --- /dev/null +++ b/api/scripts/generate_openapi_schema.js @@ -0,0 +1,127 @@ +// https://mermade.github.io/openapi-gui/# +// https://editor.swagger.io/ +const getRouteDescriptions = require("../jest/getRouteDescriptions"); +const path = require("path"); +const fs = require("fs"); +require("missing-native-js-functions"); + +const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); +const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json"); +const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); +const specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" })); + +function combineSchemas(schemas) { + var definitions = {}; + + for (const name in schemas) { + definitions = { + ...definitions, + ...schemas[name].definitions, + [name]: { ...schemas[name], definitions: undefined, $schema: undefined } + }; + } + + for (const key in definitions) { + specification.components.schemas[key] = definitions[key]; + delete definitions[key].additionalProperties; + delete definitions[key].$schema; + const definition = definitions[key]; + + if (typeof definition.properties === "object") { + for (const property of Object.values(definition.properties)) { + if (Array.isArray(property.type)) { + if (property.type.includes("null")) { + property.type = property.type.find((x) => x !== "null"); + property.nullable = true; + } + } + } + } + } + + return definitions; +} + +function getTag(key) { + return key.match(/\/([\w-]+)/)[1]; +} + +function apiRoutes() { + const routes = getRouteDescriptions(); + + const tags = Array.from(routes.keys()).map((x) => getTag(x)); + specification.tags = [...specification.tags.map((x) => x.name), ...tags].unique().map((x) => ({ name: x })); + + routes.forEach((route, pathAndMethod) => { + const [p, method] = pathAndMethod.split("|"); + const path = p.replace(/:(\w+)/g, "{$1}"); + + let obj = specification.paths[path]?.[method] || {}; + if (!obj.description) { + const permission = route.permission ? `##### Requires the \`\`${route.permission}\`\` permission\n` : ""; + const event = route.test?.event ? `##### Fires a \`\`${route.test?.event}\`\` event\n` : ""; + obj.description = permission + event; + } + if (route.body) { + obj.requestBody = { + required: true, + content: { + "application/json": { + schema: { $ref: `#/components/schemas/${route.body}` } + } + } + }.merge(obj.requestBody); + } + if (!obj.responses) { + obj.responses = { + default: { + description: "not documented" + } + }; + } + if (route.test?.response) { + const status = route.test.response.status || 200; + obj.responses = { + [status]: { + ...(route.test.response.body + ? { + description: obj.responses[status].description || "", + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${route.test.response.body}` + } + } + } + } + : {}) + } + }.merge(obj.responses); + delete obj.responses.default; + } + if (p.includes(":")) { + obj.parameters = p.match(/:\w+/g)?.map((x) => ({ + name: x.replace(":", ""), + in: "path", + required: true, + schema: { type: "string" }, + description: x.replace(":", "") + })); + } + obj.tags = [...(obj.tags || []), getTag(p)].unique(); + + specification.paths[path] = { ...specification.paths[path], [method]: obj }; + }); +} + +function main() { + combineSchemas(schemas); + apiRoutes(); + + fs.writeFileSync( + openapiPath, + JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number") + ); +} + +main(); diff --git a/api/scripts/generate_openapi_schema.ts b/api/scripts/generate_openapi_schema.ts deleted file mode 100644 index c0995b6c..00000000 --- a/api/scripts/generate_openapi_schema.ts +++ /dev/null @@ -1,92 +0,0 @@ -// https://mermade.github.io/openapi-gui/# -// https://editor.swagger.io/ -import path from "path"; -import fs from "fs"; -import * as TJS from "typescript-json-schema"; -import "missing-native-js-functions"; - -const settings: TJS.PartialArgs = { - required: true, - ignoreErrors: true, - excludePrivate: true, - defaultNumberType: "integer", - noExtraProps: true, - defaultProps: false -}; -const compilerOptions: TJS.CompilerOptions = { - strictNullChecks: false -}; -const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); -var specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" })); - -async function utilSchemas() { - const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "..", "util", "src", "index.ts")], compilerOptions); - const generator = TJS.buildGenerator(program, settings); - - const schemas = ["UserPublic", "UserPrivate", "PublicConnectedAccount"]; - - // @ts-ignore - combineSchemas({ schemas, generator, program }); -} - -function combineSchemas(opts: { program: TJS.Program; generator: TJS.JsonSchemaGenerator; schemas: string[] }) { - var definitions: any = {}; - - for (const name of opts.schemas) { - const part = TJS.generateSchema(opts.program, name, settings, [], opts.generator as TJS.JsonSchemaGenerator); - if (!part) continue; - - definitions = { ...definitions, [name]: { ...part, definitions: undefined, $schema: undefined } }; - } - - for (const key in definitions) { - specification.components.schemas[key] = definitions[key]; - delete definitions[key].additionalProperties; - delete definitions[key].$schema; - } - - return definitions; -} - -const ExcludedSchemas = [ - "DefaultSchema", - "Schema", - "EntitySchema", - "ServerResponse", - "Http2ServerResponse", - "global.Express.Response", - "Response", - "e.Response", - "request.Response", - "supertest.Response" -]; - -function apiSchemas() { - const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "src", "schema", "index.ts")], compilerOptions); - const generator = TJS.buildGenerator(program, settings); - - const schemas = generator - .getUserSymbols() - .filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x)) - .concat(generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x))); - - // @ts-ignore - combineSchemas({ schemas, generator, program }); -} - -function addDefaultResponses() { - Object.values(specification.paths).forEach((path: any) => Object.values(path).forEach((request: any) => {})); -} - -function main() { - addDefaultResponses(); - utilSchemas(); - apiSchemas(); - - fs.writeFileSync( - openapiPath, - JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number") - ); -} - -main(); diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index f672658a..ff04f8aa 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -1,9 +1,7 @@ import { Request, Response, Router } from "express"; import { FieldErrors, route } from "@fosscord/api"; import bcrypt from "bcrypt"; -import jwt from "jsonwebtoken"; -import { Config, User } from "@fosscord/util"; -import { adjustEmail } from "./register"; +import { Config, User, generateToken, adjustEmail } from "@fosscord/util"; const router: Router = Router(); export default router; @@ -68,25 +66,6 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo res.json({ token, settings: user.settings }); }); -export async function generateToken(id: string) { - const iat = Math.floor(Date.now() / 1000); - const algorithm = "HS256"; - - return new Promise((res, rej) => { - jwt.sign( - { id: id, iat }, - Config.get().security.jwtSecret, - { - algorithm - }, - (err, token) => { - if (err) return rej(err); - return res(token); - } - ); - }); -} - /** * POST /auth/login * @argument { login: "email@gmail.com", password: "cleartextpassword", undelete: false, captcha_key: null, login_source: null, gift_code_sku_id: null, } diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 4d3f2860..9c058399 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,10 +1,8 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, Config, defaultSettings, Member, Invite } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, Config, defaultSettings, generateToken, Invite, adjustEmail } from "@fosscord/util"; import bcrypt from "bcrypt"; -import { EMAIL_REGEX, FieldErrors, route } from "@fosscord/api"; +import { FieldErrors, route, getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; import "missing-native-js-functions"; -import { generateToken } from "./login"; -import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; import { HTTPError } from "lambert-server"; const router: Router = Router(); @@ -228,24 +226,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re return res.json({ token: await generateToken(user.id) }); }); -export function adjustEmail(email: string): string | undefined { - if (!email) return email; - // body parser already checked if it is a valid email - const parts = email.match(EMAIL_REGEX); - // @ts-ignore - if (!parts || parts.length < 5) return undefined; - const domain = parts[5]; - const user = parts[1]; - - // TODO: check accounts with uncommon email domains - if (domain === "gmail.com" || domain === "googlemail.com") { - // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator - return user.replace(/[.]|(\+.*)/g, "") + "@gmail.com"; - } - - return email; -} - export default router; /** diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts index c7beeee8..83b62049 100644 --- a/api/src/routes/channels/#channel_id/recipients.ts +++ b/api/src/routes/channels/#channel_id/recipients.ts @@ -1,9 +1,10 @@ import { Request, Response, Router } from "express"; import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util"; +import { route } from "@fosscord/api" const router: Router = Router(); -router.put("/:user_id", async (req: Request, res: Response) => { +router.put("/:user_id", route({}), async (req: Request, res: Response) => { const { channel_id, user_id } = req.params; const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); @@ -39,7 +40,7 @@ router.put("/:user_id", async (req: Request, res: Response) => { } }); -router.delete("/:user_id", async (req: Request, res: Response) => { +router.delete("/:user_id", route({}), async (req: Request, res: Response) => { const { channel_id, user_id } = req.params; const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index ae10be82..8f5ca7ba 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -4,14 +4,14 @@ import { Request, Response, Router } from "express"; const router = Router(); -router.delete("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { +router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; await Member.removeRole(member_id, guild_id, role_id); res.sendStatus(204); }); -router.put("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { +router.put("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { guild_id, role_id, member_id } = req.params; await Member.addRole(member_id, guild_id, role_id); diff --git a/api/src/routes/sticker-packs/#id/index.ts b/api/src/routes/sticker-packs/#id/index.ts index 2344a48f..7f723e97 100644 --- a/api/src/routes/sticker-packs/#id/index.ts +++ b/api/src/routes/sticker-packs/#id/index.ts @@ -1,8 +1,9 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json({ id: "", @@ -15,4 +16,4 @@ router.get("/", async (req: Request, res: Response) => { }).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/routes/sticker-packs/index.ts b/api/src/routes/sticker-packs/index.ts index 6c4e46d8..d671c161 100644 --- a/api/src/routes/sticker-packs/index.ts +++ b/api/src/routes/sticker-packs/index.ts @@ -1,10 +1,11 @@ import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; const router: Router = Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { //TODO res.json({ sticker_packs: [] }).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/api/src/util/String.ts b/api/src/util/String.ts index 2fe32d2c..67d87e37 100644 --- a/api/src/util/String.ts +++ b/api/src/util/String.ts @@ -1,8 +1,6 @@ import { Request } from "express"; import { ntob } from "./Base64"; import { FieldErrors } from "./FieldError"; -export const EMAIL_REGEX = - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export function checkLength(str: string, min: number, max: number, key: string, req: Request) { if (str.length < min || str.length > max) { diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index 0473c579..a9c75df1 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -9,7 +9,7 @@ import addFormats from "ajv-formats"; import fetch from "node-fetch"; import { User } from "@fosscord/util"; -const SchemaPath = join(__dirname, "..", "assets", "responses.json"); +const SchemaPath = join(__dirname, "..", "assets", "schemas.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); export const ajv = new Ajv({ allErrors: true, @@ -64,7 +64,7 @@ describe("Automatic unit tests with route description middleware", () => { routes.forEach((route, pathAndMethod) => { const [path, method] = pathAndMethod.split("|"); - test(path, async (done) => { + test(`${method.toUpperCase()} ${path}`, async (done) => { if (!route.test) { console.log(`${(route as any).file}\nrouter.${method} is missing the test property`); return done(); diff --git a/util/src/util/Email.ts b/util/src/util/Email.ts new file mode 100644 index 00000000..c304f584 --- /dev/null +++ b/util/src/util/Email.ts @@ -0,0 +1,20 @@ +export const EMAIL_REGEX = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + +export function adjustEmail(email: string): string | undefined { + if (!email) return email; + // body parser already checked if it is a valid email + const parts = email.match(EMAIL_REGEX); + // @ts-ignore + if (!parts || parts.length < 5) return undefined; + const domain = parts[5]; + const user = parts[1]; + + // TODO: check accounts with uncommon email domains + if (domain === "gmail.com" || domain === "googlemail.com") { + // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator + return user.replace(/[.]|(\+.*)/g, "") + "@gmail.com"; + } + + return email; +} diff --git a/util/src/util/checkToken.ts b/util/src/util/Token.ts similarity index 72% rename from util/src/util/checkToken.ts rename to util/src/util/Token.ts index 8415e8c0..111d59a2 100644 --- a/util/src/util/checkToken.ts +++ b/util/src/util/Token.ts @@ -1,4 +1,5 @@ import jwt, { VerifyOptions } from "jsonwebtoken"; +import { Config } from "./Config"; import { User } from "../entities"; export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] }; @@ -21,3 +22,22 @@ export function checkToken(token: string, jwtSecret: string): Promise { }); }); } + +export async function generateToken(id: string) { + const iat = Math.floor(Date.now() / 1000); + const algorithm = "HS256"; + + return new Promise((res, rej) => { + jwt.sign( + { id: id, iat }, + Config.get().security.jwtSecret, + { + algorithm, + }, + (err, token) => { + if (err) return rej(err); + return res(token); + } + ); + }); +} diff --git a/util/src/util/index.ts b/util/src/util/index.ts index 3160380f..d73bf4ca 100644 --- a/util/src/util/index.ts +++ b/util/src/util/index.ts @@ -1,11 +1,12 @@ export * from "./ApiError"; export * from "./BitField"; -export * from "./checkToken"; +export * from "./Token"; export * from "./cdn"; export * from "./Config"; export * from "./Constants"; export * from "./Database"; export * from "./Event"; +export * from "./Email"; export * from "./Intents"; export * from "./MessageFlags"; export * from "./Permissions"; From 22c744398c043c64ad738cdaa6f503de365de395 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:13:31 +0200 Subject: [PATCH 82/90] :sparkles: unit tests expect event --- api/assets/openapi.json | 12591 +++++++++++++++++++------------------ api/tests/routes.test.ts | 31 +- util/src/util/Event.ts | 2 +- 3 files changed, 6628 insertions(+), 5996 deletions(-) diff --git a/api/assets/openapi.json b/api/assets/openapi.json index caf572b9..a92fe706 100644 --- a/api/assets/openapi.json +++ b/api/assets/openapi.json @@ -1,5995 +1,6598 @@ { - "openapi": "3.0.0", - "servers": [ - { - "url": "https://api.fosscord.com/v{version}", - "description": "Official fosscord instance", - "variables": { - "version": { - "default": "9", - "enum": ["8", "9"] - } - } - } - ], - "info": { - "description": "Fosscord is a free open source selfhostable discord compatible chat, voice and video platform", - "version": "1.0.0", - "title": "Fosscord HTTP API Routes", - "termsOfService": "", - "contact": { - "name": "Fosscord" - }, - "license": { - "name": "AGPLV3", - "url": "https://www.gnu.org/licenses/agpl-3.0.en.html" - } - }, - "tags": [ - { - "name": "voice" - }, - { - "name": "users" - }, - { - "name": "store" - }, - { - "name": "sticker-packs" - }, - { - "name": "science" - }, - { - "name": "ping" - }, - { - "name": "outbound-promotions" - }, - { - "name": "invites" - }, - { - "name": "guilds" - }, - { - "name": "gateway" - }, - { - "name": "experiments" - }, - { - "name": "discoverable-guilds" - }, - { - "name": "channels" - }, - { - "name": "auth" - }, - { - "name": "applications" - } - ], - "paths": { - "/users/{id}": { - "get": { - "summary": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user id" - } - ], - "operationId": "", - "responses": { - "200": { - "description": "User found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserPublic" - } - } - } - }, - "404": { - "description": "User not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "security": [ - { - "Token": [] - } - ] - } - }, - "/users/@me": { - "get": { - "summary": "", - "parameters": [], - "operationId": "", - "responses": { - "200": { - "description": "Authenticated user", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserPublic" - } - } - } - } - }, - "security": [ - { - "Token": [] - } - ] - } - }, - "/voice/regions/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["voice"] - } - }, - "/users/@me/settings/": { - "patch": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserSettingsSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/relationships/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users", "relationships"] - }, - "post": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RelationshipPostSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/relationships/{id}": { - "put": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RelationshipPutSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "id" - } - ], - "tags": ["users"] - }, - "delete": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "id" - } - ], - "tags": ["users"] - } - }, - "/users/@me/library/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - }, - "patch": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserModifySchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/guilds/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/guilds/{id}": { - "delete": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "id" - } - ], - "tags": ["users"] - } - }, - "/users/@me/disable/": { - "post": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/devices/": { - "post": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/delete/": { - "post": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/connections/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/channels/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - }, - "post": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DmChannelCreateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/billing/subscriptions/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/billing/country-code/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/applications/{app_id}/entitlements/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "app_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "app_id" - } - ], - "tags": ["users"] - } - }, - "/users/@me/affinities/users/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/@me/affinities/guilds/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["users"] - } - }, - "/users/{id}/profile/": { - "get": { - "description": "", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserProfileResponse" - } - } - }, - "description": "" - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "id" - } - ], - "tags": ["users"] - } - }, - "/users/{id}/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "id" - } - ], - "tags": ["users"] - } - }, - "/store/skus/skus/{id}": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "id" - } - ], - "tags": ["store"] - } - }, - "/store/applications/applications/{id}": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "id" - } - ], - "tags": ["store"] - } - }, - "/sticker-packs/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["sticker", "sticker-packs"] - } - }, - "/sticker-packs/{id}/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "id" - } - ], - "tags": ["sticker", "sticker-packs"] - } - }, - "/science/": { - "post": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["science"] - } - }, - "/ping/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["ping"] - } - }, - "/outbound-promotions/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["outbound", "outbound-promotions"] - } - }, - "/invites/{code}": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "code", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "code" - } - ], - "tags": ["invites"] - }, - "post": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "code", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "code" - } - ], - "tags": ["invites"] - }, - "delete": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "code", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "code" - } - ], - "tags": ["invites"] - } - }, - "/guilds/templates/{code}": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "code", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "code" - } - ], - "tags": ["guilds"] - }, - "post": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GuildTemplateCreateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "code", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "code" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/": { - "post": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GuildCreateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/widget.png/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/widget.json/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/widget/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WidgetModifySchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/welcome_screen/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GuildUpdateWelcomeScreenSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/voice-states/{user_id}/": { - "patch": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VoiceStateUpdateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "user_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/vanity-url/": { - "get": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VanityUrlSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/templates/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "post": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TemplateCreateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/templates/{code}": { - "delete": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "code", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "code" - } - ], - "tags": ["guilds"] - }, - "put": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "code", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "code" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TemplateModifySchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "code", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "code" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/roles/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "post": { - "description": "##### Requires the ``MANAGE_ROLES`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RoleModifySchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RolePositionUpdateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/roles/{role_id}": { - "delete": { - "description": "##### Requires the ``MANAGE_ROLES`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "role_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "role_id" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "##### Requires the ``MANAGE_ROLES`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RoleModifySchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "role_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "role_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/regions/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/members/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/members/{member_id}/nick/": { - "patch": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MemberNickChangeSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "member_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "member_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/members/{member_id}/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "member_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "member_id" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MemberChangeSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "member_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "member_id" - } - ], - "tags": ["guilds"] - }, - "put": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "member_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "member_id" - } - ], - "tags": ["guilds"] - }, - "delete": { - "description": "##### Requires the ``KICK_MEMBERS`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "member_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "member_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/invites/": { - "get": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "##### Requires the ``MANAGE_GUILD`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GuildUpdateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/delete/": { - "post": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/channels/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "post": { - "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChannelModifySchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - }, - "patch": { - "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChannelReorderSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/bans/": { - "get": { - "description": "##### Requires the ``BAN_MEMBERS`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/bans/{user}": { - "get": { - "description": "##### Requires the ``BAN_MEMBERS`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "user", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user" - } - ], - "tags": ["guilds"] - } - }, - "/guilds/{guild_id}/bans/{user_id}": { - "put": { - "description": "##### Requires the ``BAN_MEMBERS`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BanCreateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "user_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user_id" - } - ], - "tags": ["guilds"] - }, - "delete": { - "description": "##### Requires the ``BAN_MEMBERS`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "user_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user_id" - } - ], - "tags": ["guilds"] - } - }, - "/gateway/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["gateway"] - } - }, - "/gateway/bot": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["gateway"] - } - }, - "/experiments/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["experiments"] - } - }, - "/discoverable-guilds/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["discoverable", "discoverable-guilds"] - } - }, - "/channels/{channel_id}/webhooks/": { - "post": { - "description": "##### Requires the ``MANAGE_WEBHOOKS`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WebhookCreateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/typing/": { - "post": { - "description": "##### Requires the ``SEND_MESSAGES`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/recipients/{user_id}": { - "put": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "user_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user_id" - } - ], - "tags": ["channels"] - }, - "delete": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "user_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/pins/{message_id}": { - "put": { - "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - } - ], - "tags": ["channels"] - }, - "delete": { - "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/pins/": { - "get": { - "description": "##### Requires the ``READ_MESSAGE_HISTORY`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/permissions/{overwrite_id}": { - "put": { - "description": "##### Requires the ``MANAGE_ROLES`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChannelPermissionOverwriteSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "overwrite_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "overwrite_id" - } - ], - "tags": ["channels"] - }, - "delete": { - "description": "##### Requires the ``MANAGE_ROLES`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "overwrite_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "overwrite_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/messages/bulk-delete/": { - "post": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BulkDeleteSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/messages/{message_id}/reactions/": { - "delete": { - "description": "##### Requires the ``MANAGE_MESSAGES`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}": { - "delete": { - "description": "##### Requires the ``MANAGE_MESSAGES`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - }, - { - "name": "emoji", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "emoji" - } - ], - "tags": ["channels"] - }, - "get": { - "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - }, - { - "name": "emoji", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "emoji" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{user_id}": { - "put": { - "description": "##### Requires the ``READ_MESSAGE_HISTORY`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - }, - { - "name": "emoji", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "emoji" - }, - { - "name": "user_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user_id" - } - ], - "tags": ["channels"] - }, - "delete": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - }, - { - "name": "emoji", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "emoji" - }, - { - "name": "user_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "user_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/messages/{message_id}/": { - "patch": { - "description": "##### Requires the ``SEND_MESSAGES`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MessageCreateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - } - ], - "tags": ["channels"] - }, - "delete": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/messages/{message_id}/ack/": { - "post": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MessageAcknowledgeSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - }, - { - "name": "message_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "message_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/invites/": { - "post": { - "description": "##### Requires the ``CREATE_INSTANT_INVITE`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InviteCreateSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - }, - "get": { - "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - } - }, - "/channels/{channel_id}/": { - "get": { - "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - }, - "delete": { - "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - }, - "patch": { - "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChannelModifySchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "channel_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "channel_id" - } - ], - "tags": ["channels"] - } - }, - "/auth/register/": { - "post": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RegisterSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["auth"] - } - }, - "/applications/detectable/": { - "get": { - "description": "", - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["applications"] - } - }, - "/guilds/{guild_id}/members/{member_id}/roles/{role_id}/": { - "delete": { - "description": "##### Requires the ``MANAGE_ROLES`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "member_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "member_id" - }, - { - "name": "role_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "role_id" - } - ], - "tags": ["guilds"] - }, - "put": { - "description": "##### Requires the ``MANAGE_ROLES`` permission\n", - "responses": { - "default": { - "description": "not documented" - } - }, - "parameters": [ - { - "name": "guild_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "guild_id" - }, - { - "name": "member_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "member_id" - }, - { - "name": "role_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "role_id" - } - ], - "tags": ["guilds"] - } - }, - "/auth/login/": { - "post": { - "description": "", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LoginSchema" - } - } - } - }, - "responses": { - "default": { - "description": "not documented" - } - }, - "tags": ["auth"] - } - } - }, - "externalDocs": { - "url": "http://docs.fosscord.com/" - }, - "components": { - "schemas": { - "Error": { - "type": "object", - "properties": { - "code": { - "type": "integer" - }, - "message": { - "type": "string" - } - }, - "required": ["code", "message"] - }, - "RateLimit": { - "type": "object", - "properties": { - "retry_after": { - "type": "integer" - }, - "message": { - "type": "string" - }, - "global": { - "type": "boolean" - } - }, - "required": ["code", "message", "globa"] - }, - "User": { - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "discriminator": { - "type": "string" - }, - "avatar": { - "type": "string" - }, - "accent_color": { - "type": "integer" - }, - "banner": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "desktop": { - "type": "boolean" - }, - "mobile": { - "type": "boolean" - }, - "premium": { - "type": "boolean" - }, - "premium_type": { - "type": "integer" - }, - "bot": { - "type": "boolean" - }, - "bio": { - "type": "string" - }, - "system": { - "type": "boolean" - }, - "nsfw_allowed": { - "type": "boolean" - }, - "mfa_enabled": { - "type": "boolean" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "verified": { - "type": "boolean" - }, - "disabled": { - "type": "boolean" - }, - "deleted": { - "type": "boolean" - }, - "email": { - "type": "string" - }, - "flags": { - "type": "string" - }, - "public_flags": { - "type": "string" - }, - "relationships": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Relationship" - } - }, - "connected_accounts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ConnectedAccount" - } - }, - "data": { - "type": "object", - "properties": { - "valid_tokens_since": { - "type": "string", - "format": "date-time" - }, - "hash": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["valid_tokens_since"] - }, - "fingerprints": { - "type": "array", - "items": { - "type": "string" - } - }, - "settings": { - "$ref": "#/components/schemas/UserSettings" - }, - "id": { - "type": "string" - } - }, - "required": [ - "bio", - "bot", - "connected_accounts", - "created_at", - "data", - "deleted", - "desktop", - "disabled", - "discriminator", - "fingerprints", - "flags", - "id", - "mfa_enabled", - "mobile", - "nsfw_allowed", - "premium", - "premium_type", - "public_flags", - "relationships", - "settings", - "system", - "username", - "verified" - ] - }, - "Relationship": { - "type": "object", - "properties": { - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "nickname": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/RelationshipType" - }, - "id": { - "type": "string" - } - }, - "required": ["id", "type", "user", "user_id"] - }, - "RelationshipType": { - "enum": [1, 2, 3, 4], - "type": "number" - }, - "ConnectedAccount": { - "type": "object", - "properties": { - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "access_token": { - "type": "string" - }, - "friend_sync": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "revoked": { - "type": "boolean" - }, - "show_activity": { - "type": "boolean" - }, - "type": { - "type": "string" - }, - "verifie": { - "type": "boolean" - }, - "visibility": { - "type": "integer" - }, - "id": { - "type": "string" - } - }, - "required": [ - "access_token", - "friend_sync", - "id", - "name", - "revoked", - "show_activity", - "type", - "user", - "user_id", - "verifie", - "visibility" - ] - }, - "UserSettings": { - "type": "object", - "properties": { - "afk_timeout": { - "type": "integer" - }, - "allow_accessibility_detection": { - "type": "boolean" - }, - "animate_emoji": { - "type": "boolean" - }, - "animate_stickers": { - "type": "integer" - }, - "contact_sync_enabled": { - "type": "boolean" - }, - "convert_emoticons": { - "type": "boolean" - }, - "custom_status": { - "type": "object", - "properties": { - "emoji_id": { - "type": "string" - }, - "emoji_name": { - "type": "string" - }, - "expires_at": { - "type": "integer" - }, - "text": { - "type": "string" - } - }, - "additionalProperties": false - }, - "default_guilds_restricted": { - "type": "boolean" - }, - "detect_platform_accounts": { - "type": "boolean" - }, - "developer_mode": { - "type": "boolean" - }, - "disable_games_tab": { - "type": "boolean" - }, - "enable_tts_command": { - "type": "boolean" - }, - "explicit_content_filter": { - "type": "integer" - }, - "friend_source_flags": { - "type": "object", - "properties": { - "all": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": ["all"] - }, - "gateway_connected": { - "type": "boolean" - }, - "gif_auto_play": { - "type": "boolean" - }, - "guild_folders": { - "type": "array", - "items": { - "type": "object", - "properties": { - "color": { - "type": "integer" - }, - "guild_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["color", "guild_ids", "id", "name"] - } - }, - "guild_positions": { - "type": "array", - "items": { - "type": "string" - } - }, - "inline_attachment_media": { - "type": "boolean" - }, - "inline_embed_media": { - "type": "boolean" - }, - "locale": { - "type": "string" - }, - "message_display_compact": { - "type": "boolean" - }, - "native_phone_integration_enabled": { - "type": "boolean" - }, - "render_embeds": { - "type": "boolean" - }, - "render_reactions": { - "type": "boolean" - }, - "restricted_guilds": { - "type": "array", - "items": { - "type": "string" - } - }, - "show_current_game": { - "type": "boolean" - }, - "status": { - "enum": ["dnd", "idle", "offline", "online"], - "type": "string" - }, - "stream_notifications_enabled": { - "type": "boolean" - }, - "theme": { - "enum": ["dark", "white"], - "type": "string" - }, - "timezone_offset": { - "type": "integer" - } - }, - "required": [ - "afk_timeout", - "allow_accessibility_detection", - "animate_emoji", - "animate_stickers", - "contact_sync_enabled", - "convert_emoticons", - "custom_status", - "default_guilds_restricted", - "detect_platform_accounts", - "developer_mode", - "disable_games_tab", - "enable_tts_command", - "explicit_content_filter", - "friend_source_flags", - "gateway_connected", - "gif_auto_play", - "guild_folders", - "guild_positions", - "inline_attachment_media", - "inline_embed_media", - "locale", - "message_display_compact", - "native_phone_integration_enabled", - "render_embeds", - "render_reactions", - "restricted_guilds", - "show_current_game", - "status", - "stream_notifications_enabled", - "theme", - "timezone_offset" - ] - }, - "Team": { - "type": "object", - "properties": { - "icon": { - "type": "string" - }, - "members": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TeamMember" - } - }, - "name": { - "type": "string" - }, - "owner_user_id": { - "type": "string" - }, - "owner_user": { - "$ref": "#/components/schemas/User" - }, - "id": { - "type": "string" - } - }, - "required": ["id", "members", "name", "owner_user", "owner_user_id"] - }, - "TeamMember": { - "type": "object", - "properties": { - "membership_state": { - "$ref": "#/components/schemas/TeamMemberState" - }, - "permissions": { - "type": "array", - "items": { - "type": "string" - } - }, - "team_id": { - "type": "string" - }, - "team": { - "$ref": "#/components/schemas/Team" - }, - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "id": { - "type": "string" - } - }, - "required": ["id", "membership_state", "permissions", "team", "team_id", "user", "user_id"] - }, - "TeamMemberState": { - "enum": [1, 2], - "type": "number" - }, - "Guild": { - "type": "object", - "properties": { - "afk_channel_id": { - "type": "string" - }, - "afk_channel": { - "$ref": "#/components/schemas/Channel" - }, - "afk_timeout": { - "type": "integer" - }, - "bans": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Ban" - } - }, - "banner": { - "type": "string" - }, - "default_message_notifications": { - "type": "integer" - }, - "description": { - "type": "string" - }, - "discovery_splash": { - "type": "string" - }, - "explicit_content_filter": { - "type": "integer" - }, - "features": { - "type": "array", - "items": { - "type": "string" - } - }, - "icon": { - "type": "string" - }, - "large": { - "type": "boolean" - }, - "max_members": { - "type": "integer" - }, - "max_presences": { - "type": "integer" - }, - "max_video_channel_users": { - "type": "integer" - }, - "member_count": { - "type": "integer" - }, - "presence_count": { - "type": "integer" - }, - "members": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Member" - } - }, - "roles": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Role" - } - }, - "channels": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Channel" - } - }, - "template_id": { - "type": "string" - }, - "template": { - "$ref": "#/components/schemas/Template" - }, - "emojis": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Emoji" - } - }, - "stickers": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Sticker" - } - }, - "invites": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Invite" - } - }, - "voice_states": { - "type": "array", - "items": { - "$ref": "#/components/schemas/VoiceState" - } - }, - "webhooks": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Webhook" - } - }, - "mfa_level": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "owner_id": { - "type": "string" - }, - "owner": { - "$ref": "#/components/schemas/User" - }, - "preferred_locale": { - "type": "string" - }, - "premium_subscription_count": { - "type": "integer" - }, - "premium_tier": { - "type": "integer" - }, - "public_updates_channel_id": { - "type": "string" - }, - "public_updates_channel": { - "$ref": "#/components/schemas/Channel" - }, - "rules_channel_id": { - "type": "string" - }, - "rules_channel": { - "type": "string" - }, - "region": { - "type": "string" - }, - "splash": { - "type": "string" - }, - "system_channel_id": { - "type": "string" - }, - "system_channel": { - "$ref": "#/components/schemas/Channel" - }, - "system_channel_flags": { - "type": "integer" - }, - "unavailable": { - "type": "boolean" - }, - "vanity_url_code": { - "type": "string" - }, - "vanity_url": { - "$ref": "#/components/schemas/Invite" - }, - "verification_level": { - "type": "integer" - }, - "welcome_screen": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "welcome_channels": { - "type": "array", - "items": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "emoji_id": { - "type": "string" - }, - "emoji_name": { - "type": "string" - }, - "channel_id": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["channel_id", "description", "emoji_name"] - } - } - }, - "additionalProperties": false, - "required": ["description", "enabled", "welcome_channels"] - }, - "widget_channel_id": { - "type": "string" - }, - "widget_channel": { - "$ref": "#/components/schemas/Channel" - }, - "widget_enabled": { - "type": "boolean" - }, - "id": { - "type": "string" - } - }, - "required": [ - "bans", - "channels", - "emojis", - "features", - "id", - "invites", - "members", - "name", - "owner", - "owner_id", - "public_updates_channel_id", - "roles", - "stickers", - "template", - "template_id", - "voice_states", - "webhooks", - "welcome_screen" - ] - }, - "Channel": { - "type": "object", - "properties": { - "created_at": { - "type": "string", - "format": "date-time" - }, - "name": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/ChannelType" - }, - "recipients": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Recipient" - } - }, - "last_message_id": { - "type": "string" - }, - "last_message": { - "$ref": "#/components/schemas/Message" - }, - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "parent_id": { - "type": "string" - }, - "parent": { - "$ref": "#/components/schemas/Channel" - }, - "owner_id": { - "type": "string" - }, - "owner": { - "$ref": "#/components/schemas/User" - }, - "last_pin_timestamp": { - "type": "integer" - }, - "default_auto_archive_duration": { - "type": "integer" - }, - "position": { - "type": "integer" - }, - "permission_overwrites": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ChannelPermissionOverwrite" - } - }, - "video_quality_mode": { - "type": "integer" - }, - "bitrate": { - "type": "integer" - }, - "user_limit": { - "type": "integer" - }, - "nsfw": { - "type": "boolean" - }, - "rate_limit_per_user": { - "type": "integer" - }, - "topic": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "created_at", - "guild", - "id", - "last_message_id", - "name", - "owner", - "owner_id", - "parent_id", - "permission_overwrites", - "position", - "type" - ] - }, - "ChannelType": { - "enum": [0, 1, 2, 3, 4, 5, 6], - "type": "number" - }, - "Recipient": { - "type": "object", - "properties": { - "channel_id": { - "type": "string" - }, - "channel": { - "$ref": "#/components/schemas/Channel" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "id": { - "type": "string" - } - }, - "required": ["channel", "channel_id", "id", "user"] - }, - "Message": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "channel_id": { - "type": "string" - }, - "channel": { - "$ref": "#/components/schemas/Channel" - }, - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "author_id": { - "type": "string" - }, - "author": { - "$ref": "#/components/schemas/User" - }, - "member_id": { - "type": "string" - }, - "member": { - "$ref": "#/components/schemas/Member" - }, - "webhook_id": { - "type": "string" - }, - "webhook": { - "$ref": "#/components/schemas/Webhook" - }, - "application_id": { - "type": "string" - }, - "application": { - "$ref": "#/components/schemas/Application" - }, - "content": { - "type": "string" - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "edited_timestamp": { - "type": "string", - "format": "date-time" - }, - "tts": { - "type": "boolean" - }, - "mention_everyone": { - "type": "boolean" - }, - "mentions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/User" - } - }, - "mention_roles": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Role" - } - }, - "mention_channels": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Channel" - } - }, - "sticker_items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Sticker" - } - }, - "attachments": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Attachment" - } - }, - "embeds": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Embed" - } - }, - "reactions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Reaction" - } - }, - "nonce": { - "type": "string" - }, - "pinned": { - "type": "boolean" - }, - "type": { - "$ref": "#/components/schemas/MessageType" - }, - "activity": { - "type": "object", - "properties": { - "type": { - "type": "integer" - }, - "party_id": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["party_id", "type"] - }, - "flags": { - "type": "string" - }, - "message_reference": { - "type": "object", - "properties": { - "message_id": { - "type": "string" - }, - "channel_id": { - "type": "string" - }, - "guild_id": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["message_id"] - }, - "referenced_message": { - "$ref": "#/components/schemas/Message" - }, - "interaction": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/InteractionType" - }, - "name": { - "type": "string" - }, - "user_id": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["id", "name", "type", "user_id"] - }, - "components": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MessageComponent" - } - } - }, - "required": [ - "application_id", - "author_id", - "channel", - "channel_id", - "embeds", - "id", - "member_id", - "mention_channels", - "mention_roles", - "mentions", - "reactions", - "timestamp", - "type", - "webhook_id" - ] - }, - "Member": { - "type": "object", - "properties": { - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "nick": { - "type": "string" - }, - "roles": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Role" - } - }, - "joined_at": { - "type": "string", - "format": "date-time" - }, - "premium_since": { - "type": "integer" - }, - "deaf": { - "type": "boolean" - }, - "mute": { - "type": "boolean" - }, - "pending": { - "type": "boolean" - }, - "settings": { - "$ref": "#/components/schemas/UserGuildSettings" - }, - "id": { - "type": "string" - } - }, - "required": ["deaf", "guild", "guild_id", "id", "joined_at", "mute", "pending", "roles", "settings", "user", "user_id"] - }, - "Role": { - "type": "object", - "properties": { - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "color": { - "type": "integer" - }, - "hoist": { - "type": "boolean" - }, - "managed": { - "type": "boolean" - }, - "mentionable": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "permissions": { - "type": "string" - }, - "position": { - "type": "integer" - }, - "tags": { - "type": "object", - "properties": { - "bot_id": { - "type": "string" - }, - "integration_id": { - "type": "string" - }, - "premium_subscriber": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "id": { - "type": "string" - } - }, - "required": ["color", "guild", "guild_id", "hoist", "id", "managed", "mentionable", "name", "permissions", "position"] - }, - "UserGuildSettings": { - "type": "object", - "properties": { - "channel_overrides": { - "type": "array", - "items": { - "type": "object", - "properties": { - "channel_id": { - "type": "string" - }, - "message_notifications": { - "type": "integer" - }, - "mute_config": { - "$ref": "#/components/schemas/MuteConfig" - }, - "muted": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": ["channel_id", "message_notifications", "mute_config", "muted"] - } - }, - "message_notifications": { - "type": "integer" - }, - "mobile_push": { - "type": "boolean" - }, - "mute_config": { - "$ref": "#/components/schemas/MuteConfig" - }, - "muted": { - "type": "boolean" - }, - "suppress_everyone": { - "type": "boolean" - }, - "suppress_roles": { - "type": "boolean" - }, - "version": { - "type": "integer" - } - }, - "required": [ - "channel_overrides", - "message_notifications", - "mobile_push", - "mute_config", - "muted", - "suppress_everyone", - "suppress_roles", - "version" - ] - }, - "MuteConfig": { - "type": "object", - "properties": { - "end_time": { - "type": "integer" - }, - "selected_time_window": { - "type": "integer" - } - }, - "required": ["end_time", "selected_time_window"] - }, - "Webhook": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/WebhookType" - }, - "name": { - "type": "string" - }, - "avatar": { - "type": "string" - }, - "token": { - "type": "string" - }, - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "channel_id": { - "type": "string" - }, - "channel": { - "$ref": "#/components/schemas/Channel" - }, - "application_id": { - "type": "string" - }, - "application": { - "$ref": "#/components/schemas/Application" - }, - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "source_guild_id": { - "type": "string" - }, - "source_guild": { - "$ref": "#/components/schemas/Guild" - } - }, - "required": [ - "application", - "application_id", - "channel", - "channel_id", - "guild", - "guild_id", - "id", - "source_guild", - "source_guild_id", - "type", - "user", - "user_id" - ] - }, - "WebhookType": { - "enum": [1, 2], - "type": "number" - }, - "Application": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "icon": { - "type": "string" - }, - "description": { - "type": "string" - }, - "rpc_origins": { - "type": "array", - "items": { - "type": "string" - } - }, - "bot_public": { - "type": "boolean" - }, - "bot_require_code_grant": { - "type": "boolean" - }, - "terms_of_service_url": { - "type": "string" - }, - "privacy_policy_url": { - "type": "string" - }, - "owner": { - "$ref": "#/components/schemas/User" - }, - "summary": { - "type": "string" - }, - "verify_key": { - "type": "string" - }, - "team": { - "$ref": "#/components/schemas/Team" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "primary_sku_id": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "cover_image": { - "type": "string" - }, - "flags": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": ["bot_public", "bot_require_code_grant", "description", "flags", "guild", "id", "name", "verify_key"] - }, - "Sticker": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "tags": { - "type": "string" - }, - "pack_id": { - "type": "string" - }, - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "type": { - "$ref": "#/components/schemas/StickerType" - }, - "format_type": { - "$ref": "#/components/schemas/StickerFormatType" - }, - "id": { - "type": "string" - } - }, - "required": ["format_type", "id", "name", "pack_id", "tags", "type"] - }, - "StickerType": { - "enum": [1, 2], - "type": "number" - }, - "StickerFormatType": { - "enum": [1, 2, 3], - "type": "number" - }, - "Attachment": { - "type": "object", - "properties": { - "filename": { - "type": "string" - }, - "size": { - "type": "integer" - }, - "url": { - "type": "string" - }, - "proxy_url": { - "type": "string" - }, - "height": { - "type": "integer" - }, - "width": { - "type": "integer" - }, - "content_type": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "message": { - "$ref": "#/components/schemas/Message" - }, - "id": { - "type": "string" - } - }, - "required": ["filename", "id", "message", "message_id", "proxy_url", "size", "url"] - }, - "Embed": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "type": { - "enum": ["article", "gifv", "image", "link", "rich", "video"], - "type": "string" - }, - "description": { - "type": "string" - }, - "url": { - "type": "string" - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "color": { - "type": "integer" - }, - "footer": { - "type": "object", - "properties": { - "text": { - "type": "string" - }, - "icon_url": { - "type": "string" - }, - "proxy_icon_url": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["text"] - }, - "image": { - "$ref": "#/components/schemas/EmbedImage" - }, - "thumbnail": { - "$ref": "#/components/schemas/EmbedImage" - }, - "video": { - "$ref": "#/components/schemas/EmbedImage" - }, - "provider": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "additionalProperties": false - }, - "author": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "url": { - "type": "string" - }, - "icon_url": { - "type": "string" - }, - "proxy_icon_url": { - "type": "string" - } - }, - "additionalProperties": false - }, - "fields": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - }, - "inline": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": ["name", "value"] - } - } - } - }, - "EmbedType": { - "enum": ["article", "gifv", "image", "link", "rich", "video"], - "type": "string" - }, - "EmbedImage": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "proxy_url": { - "type": "string" - }, - "height": { - "type": "integer" - }, - "width": { - "type": "integer" - } - } - }, - "Reaction": { - "type": "object", - "properties": { - "count": { - "type": "integer" - }, - "emoji": { - "$ref": "#/components/schemas/PartialEmoji" - }, - "user_ids": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["count", "emoji", "user_ids"] - }, - "PartialEmoji": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "animated": { - "type": "boolean" - } - }, - "required": ["name"] - }, - "MessageType": { - "enum": [0, 1, 10, 11, 12, 14, 15, 19, 2, 20, 3, 4, 5, 6, 7, 8, 9], - "type": "number" - }, - "InteractionType": { - "enum": [1, 2], - "type": "number" - }, - "MessageComponent": { - "type": "object", - "properties": { - "type": { - "type": "integer" - }, - "style": { - "type": "integer" - }, - "label": { - "type": "string" - }, - "emoji": { - "$ref": "#/components/schemas/PartialEmoji" - }, - "custom_id": { - "type": "string" - }, - "url": { - "type": "string" - }, - "disabled": { - "type": "boolean" - }, - "components": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MessageComponent" - } - } - }, - "required": ["components", "type"] - }, - "ChannelPermissionOverwrite": { - "type": "object", - "properties": { - "allow": { - "type": "number" - }, - "deny": { - "type": "number" - }, - "id": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/ChannelPermissionOverwriteType" - } - }, - "required": ["allow", "deny", "id", "type"] - }, - "ChannelPermissionOverwriteType": { - "enum": [0, 1], - "type": "number" - }, - "Ban": { - "type": "object", - "properties": { - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "executor_id": { - "type": "string" - }, - "executor": { - "$ref": "#/components/schemas/User" - }, - "ip": { - "type": "string" - }, - "reason": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": ["executor", "executor_id", "guild", "guild_id", "id", "ip", "user", "user_id"] - }, - "Template": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "usage_count": { - "type": "integer" - }, - "creator_id": { - "type": "string" - }, - "creator": { - "$ref": "#/components/schemas/User" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "source_guild_id": { - "type": "string" - }, - "source_guild": { - "$ref": "#/components/schemas/Guild" - }, - "serialized_source_guild": { - "$ref": "#/components/schemas/Guild" - }, - "id": { - "type": "string" - } - }, - "required": [ - "code", - "created_at", - "creator", - "creator_id", - "id", - "name", - "serialized_source_guild", - "source_guild", - "source_guild_id", - "updated_at" - ] - }, - "Emoji": { - "type": "object", - "properties": { - "animated": { - "type": "boolean" - }, - "available": { - "type": "boolean" - }, - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "managed": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "require_colons": { - "type": "boolean" - }, - "id": { - "type": "string" - } - }, - "required": ["animated", "available", "guild", "guild_id", "id", "managed", "name", "require_colons"] - }, - "Invite": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "temporary": { - "type": "boolean" - }, - "uses": { - "type": "integer" - }, - "max_uses": { - "type": "integer" - }, - "max_age": { - "type": "integer" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "expires_at": { - "type": "string", - "format": "date-time" - }, - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "channel_id": { - "type": "string" - }, - "channel": { - "$ref": "#/components/schemas/Channel" - }, - "inviter_id": { - "type": "string" - }, - "inviter": { - "$ref": "#/components/schemas/User" - }, - "target_user_id": { - "type": "string" - }, - "target_user": { - "type": "string" - }, - "target_user_type": { - "type": "integer" - }, - "id": { - "type": "string" - } - }, - "required": [ - "channel", - "channel_id", - "code", - "created_at", - "expires_at", - "guild", - "guild_id", - "id", - "inviter", - "inviter_id", - "max_age", - "max_uses", - "target_user_id", - "temporary", - "uses" - ] - }, - "VoiceState": { - "type": "object", - "properties": { - "guild_id": { - "type": "string" - }, - "guild": { - "$ref": "#/components/schemas/Guild" - }, - "channel_id": { - "type": "string" - }, - "channel": { - "$ref": "#/components/schemas/Channel" - }, - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "session_id": { - "type": "string" - }, - "deaf": { - "type": "boolean" - }, - "mute": { - "type": "boolean" - }, - "self_deaf": { - "type": "boolean" - }, - "self_mute": { - "type": "boolean" - }, - "self_stream": { - "type": "boolean" - }, - "self_video": { - "type": "boolean" - }, - "suppress": { - "type": "boolean" - }, - "id": { - "type": "string" - } - }, - "required": [ - "channel", - "channel_id", - "deaf", - "guild_id", - "id", - "mute", - "self_deaf", - "self_mute", - "self_video", - "session_id", - "suppress", - "user", - "user_id" - ] - }, - "AuditLogEvents": { - "enum": [ - 1, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 40, 41, 42, 50, 51, 52, 60, 61, 62, 72, 73, - 74, 75, 80, 81, 82 - ], - "type": "number" - }, - "AuditLogChange": { - "type": "object", - "properties": { - "new_value": { - "$ref": "#/components/schemas/AuditLogChangeValue" - }, - "old_value": { - "$ref": "#/components/schemas/AuditLogChangeValue" - }, - "key": { - "type": "string" - } - }, - "required": ["key"] - }, - "AuditLogChangeValue": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "icon_hash": { - "type": "string" - }, - "splash_hash": { - "type": "string" - }, - "discovery_splash_hash": { - "type": "string" - }, - "banner_hash": { - "type": "string" - }, - "owner_id": { - "type": "string" - }, - "region": { - "type": "string" - }, - "preferred_locale": { - "type": "string" - }, - "afk_channel_id": { - "type": "string" - }, - "afk_timeout": { - "type": "integer" - }, - "rules_channel_id": { - "type": "string" - }, - "public_updates_channel_id": { - "type": "string" - }, - "mfa_level": { - "type": "integer" - }, - "verification_level": { - "type": "integer" - }, - "explicit_content_filter": { - "type": "integer" - }, - "default_message_notifications": { - "type": "integer" - }, - "vanity_url_code": { - "type": "string" - }, - "$add": { - "type": "array", - "items": { - "type": "object", - "properties": {} - } - }, - "$remove": { - "type": "array", - "items": { - "type": "object", - "properties": {} - } - }, - "prune_delete_days": { - "type": "integer" - }, - "widget_enabled": { - "type": "boolean" - }, - "widget_channel_id": { - "type": "string" - }, - "system_channel_id": { - "type": "string" - }, - "position": { - "type": "integer" - }, - "topic": { - "type": "string" - }, - "bitrate": { - "type": "integer" - }, - "permission_overwrites": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ChannelPermissionOverwrite" - } - }, - "nsfw": { - "type": "boolean" - }, - "application_id": { - "type": "string" - }, - "rate_limit_per_user": { - "type": "integer" - }, - "permissions": { - "type": "string" - }, - "color": { - "type": "integer" - }, - "hoist": { - "type": "boolean" - }, - "mentionable": { - "type": "boolean" - }, - "allow": { - "type": "string" - }, - "deny": { - "type": "string" - }, - "code": { - "type": "string" - }, - "channel_id": { - "type": "string" - }, - "inviter_id": { - "type": "string" - }, - "max_uses": { - "type": "integer" - }, - "uses": { - "type": "integer" - }, - "max_age": { - "type": "integer" - }, - "temporary": { - "type": "boolean" - }, - "deaf": { - "type": "boolean" - }, - "mute": { - "type": "boolean" - }, - "nick": { - "type": "string" - }, - "avatar_hash": { - "type": "string" - }, - "id": { - "type": "string" - }, - "type": { - "type": "integer" - }, - "enable_emoticons": { - "type": "boolean" - }, - "expire_behavior": { - "type": "integer" - }, - "expire_grace_period": { - "type": "integer" - }, - "user_limit": { - "type": "integer" - } - } - }, - "AuditLog": { - "type": "object", - "properties": { - "target": { - "$ref": "#/components/schemas/User" - }, - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "action_type": { - "$ref": "#/components/schemas/AuditLogEvents" - }, - "options": { - "type": "object", - "properties": { - "delete_member_days": { - "type": "string" - }, - "members_removed": { - "type": "string" - }, - "channel_id": { - "type": "string" - }, - "messaged_id": { - "type": "string" - }, - "count": { - "type": "string" - }, - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "role_name": { - "type": "string" - } - }, - "additionalProperties": false - }, - "changes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AuditLogChange" - } - }, - "reason": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": ["action_type", "changes", "id", "user", "user_id"] - }, - "ReadState": { - "type": "object", - "properties": { - "channel_id": { - "type": "string" - }, - "channel": { - "$ref": "#/components/schemas/Channel" - }, - "user_id": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/User" - }, - "last_message_id": { - "type": "string" - }, - "last_message": { - "$ref": "#/components/schemas/Message" - }, - "last_pin_timestamp": { - "type": "string", - "format": "date-time" - }, - "mention_count": { - "type": "integer" - }, - "manual": { - "type": "boolean" - }, - "id": { - "type": "string" - } - }, - "required": ["channel", "channel_id", "id", "last_message_id", "manual", "mention_count", "user", "user_id"] - }, - "UserPublic": { - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "discriminator": { - "type": "string" - }, - "id": { - "type": "string" - }, - "public_flags": { - "type": "integer" - }, - "avatar": { - "type": "string" - }, - "accent_color": { - "type": "integer" - }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, - "bot": { - "type": "boolean" - } - }, - "required": ["bio", "bot", "discriminator", "id", "public_flags", "username"] - }, - "UserPrivate": { - "type": "object", - "properties": { - "locale": { - "type": "string" - }, - "disabled": { - "type": "boolean" - }, - "username": { - "type": "string" - }, - "discriminator": { - "type": "string" - }, - "id": { - "type": "string" - }, - "public_flags": { - "type": "string" - }, - "avatar": { - "type": "string" - }, - "accent_color": { - "type": "integer" - }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, - "bot": { - "type": "boolean" - }, - "flags": { - "type": "string" - }, - "mfa_enabled": { - "type": "boolean" - }, - "email": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "verified": { - "type": "boolean" - }, - "nsfw_allowed": { - "type": "boolean" - }, - "premium": { - "type": "boolean" - }, - "premium_type": { - "type": "integer" - } - }, - "required": [ - "bio", - "bot", - "disabled", - "discriminator", - "flags", - "id", - "locale", - "mfa_enabled", - "nsfw_allowed", - "premium", - "premium_type", - "public_flags", - "username", - "verified" - ] - }, - "BanCreateSchema": { - "type": "object", - "properties": { - "delete_message_days": { - "type": "string" - }, - "reason": { - "type": "string" - } - } - }, - "DmChannelCreateSchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "recipients": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["recipients"] - }, - "ChannelModifySchema": { - "type": "object", - "properties": { - "name": { - "maxLength": 100, - "type": "string" - }, - "type": { - "enum": [0, 1, 10, 11, 12, 13, 2, 3, 4, 5, 6], - "type": "number" - }, - "topic": { - "type": "string" - }, - "icon": { - "type": "string", - "nullable": true - }, - "bitrate": { - "type": "integer" - }, - "user_limit": { - "type": "integer" - }, - "rate_limit_per_user": { - "type": "integer" - }, - "position": { - "type": "integer" - }, - "permission_overwrites": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/ChannelPermissionOverwriteType" - }, - "allow": { - "type": "number" - }, - "deny": { - "type": "number" - } - }, - "additionalProperties": false, - "required": ["allow", "deny", "id", "type"] - } - }, - "parent_id": { - "type": "string" - }, - "id": { - "type": "string" - }, - "nsfw": { - "type": "boolean" - }, - "rtc_region": { - "type": "string" - }, - "default_auto_archive_duration": { - "type": "integer" - } - } - }, - "ChannelGuildPositionUpdateSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "position": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": ["id"] - } - }, - "EmojiCreateSchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "image": { - "type": "string" - }, - "roles": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["image", "name"] - }, - "GuildCreateSchema": { - "type": "object", - "properties": { - "name": { - "maxLength": 100, - "type": "string" - }, - "region": { - "type": "string" - }, - "icon": { - "type": "string", - "nullable": true - }, - "channels": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ChannelModifySchema" - } - }, - "guild_template_code": { - "type": "string" - }, - "system_channel_id": { - "type": "string" - }, - "rules_channel_id": { - "type": "string" - } - }, - "required": ["name"] - }, - "GuildUpdateSchema": { - "type": "object", - "properties": { - "banner": { - "type": "string", - "nullable": true - }, - "splash": { - "type": "string", - "nullable": true - }, - "description": { - "type": "string" - }, - "features": { - "type": "array", - "items": { - "type": "string" - } - }, - "verification_level": { - "type": "integer" - }, - "default_message_notifications": { - "type": "integer" - }, - "system_channel_flags": { - "type": "integer" - }, - "explicit_content_filter": { - "type": "integer" - }, - "public_updates_channel_id": { - "type": "string" - }, - "afk_timeout": { - "type": "integer" - }, - "afk_channel_id": { - "type": "string" - }, - "preferred_locale": { - "type": "string" - }, - "name": { - "maxLength": 100, - "type": "string" - }, - "region": { - "type": "string" - }, - "icon": { - "type": "string", - "nullable": true - }, - "guild_template_code": { - "type": "string" - }, - "system_channel_id": { - "type": "string" - }, - "rules_channel_id": { - "type": "string" - } - }, - "required": ["name"] - }, - "GuildTemplateCreateSchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "avatar": { - "type": "string", - "nullable": true - } - }, - "required": ["name"] - }, - "GuildUpdateWelcomeScreenSchema": { - "type": "object", - "properties": { - "welcome_channels": { - "type": "array", - "items": { - "type": "object", - "properties": { - "channel_id": { - "type": "string" - }, - "description": { - "type": "string" - }, - "emoji_id": { - "type": "string" - }, - "emoji_name": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["channel_id", "description", "emoji_name"] - } - }, - "enabled": { - "type": "boolean" - }, - "description": { - "type": "string" - } - } - }, - "InviteCreateSchema": { - "type": "object", - "properties": { - "target_user_id": { - "type": "string" - }, - "target_type": { - "type": "string" - }, - "validate": { - "type": "string" - }, - "max_age": { - "type": "integer" - }, - "max_uses": { - "type": "integer" - }, - "temporary": { - "type": "boolean" - }, - "unique": { - "type": "boolean" - }, - "target_user": { - "type": "string" - }, - "target_user_type": { - "type": "integer" - } - } - }, - "MemberCreateSchema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "nick": { - "type": "string" - }, - "guild_id": { - "type": "string" - }, - "joined_at": { - "type": "string", - "format": "date-time" - } - }, - "required": ["guild_id", "id", "joined_at", "nick"] - }, - "MemberNickChangeSchema": { - "type": "object", - "properties": { - "nick": { - "type": "string" - } - }, - "required": ["nick"] - }, - "MemberChangeSchema": { - "type": "object", - "properties": { - "roles": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "MessageCreateSchema": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "nonce": { - "type": "string" - }, - "tts": { - "type": "boolean" - }, - "flags": { - "type": "string" - }, - "embeds": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Embed" - } - }, - "embed": { - "$ref": "#/components/schemas/Embed" - }, - "allowed_mentions": { - "type": "object", - "properties": { - "parse": { - "type": "array", - "items": { - "type": "string" - } - }, - "roles": { - "type": "array", - "items": { - "type": "string" - } - }, - "users": { - "type": "array", - "items": { - "type": "string" - } - }, - "replied_user": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "message_reference": { - "type": "object", - "properties": { - "message_id": { - "type": "string" - }, - "channel_id": { - "type": "string" - }, - "guild_id": { - "type": "string" - }, - "fail_if_not_exists": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": ["channel_id", "message_id"] - }, - "payload_json": { - "type": "string" - }, - "file": {}, - "attachments": { - "type": "array", - "items": {} - } - } - }, - "RoleModifySchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "permissions": { - "type": "number" - }, - "color": { - "type": "integer" - }, - "hoist": { - "type": "boolean" - }, - "mentionable": { - "type": "boolean" - }, - "position": { - "type": "integer" - } - } - }, - "TemplateCreateSchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": ["name"] - }, - "TemplateModifySchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": ["name"] - }, - "UserModifySchema": { - "type": "object", - "properties": { - "username": { - "minLength": 1, - "maxLength": 100, - "type": "string" - }, - "avatar": { - "type": "string", - "nullable": true - }, - "bio": { - "maxLength": 1024, - "type": "string" - }, - "accent_color": { - "type": "integer" - }, - "banner": { - "type": "string", - "nullable": true - }, - "password": { - "type": "string" - }, - "new_password": { - "type": "string" - }, - "code": { - "type": "string" - } - } - }, - "UserSettingsSchema": { - "type": "object", - "properties": { - "afk_timeout": { - "type": "integer" - }, - "allow_accessibility_detection": { - "type": "boolean" - }, - "animate_emoji": { - "type": "boolean" - }, - "animate_stickers": { - "type": "integer" - }, - "contact_sync_enabled": { - "type": "boolean" - }, - "convert_emoticons": { - "type": "boolean" - }, - "custom_status": { - "type": "object", - "properties": { - "emoji_id": { - "type": "string" - }, - "emoji_name": { - "type": "string" - }, - "expires_at": { - "type": "integer" - }, - "text": { - "type": "string" - } - }, - "additionalProperties": false - }, - "default_guilds_restricted": { - "type": "boolean" - }, - "detect_platform_accounts": { - "type": "boolean" - }, - "developer_mode": { - "type": "boolean" - }, - "disable_games_tab": { - "type": "boolean" - }, - "enable_tts_command": { - "type": "boolean" - }, - "explicit_content_filter": { - "type": "integer" - }, - "friend_source_flags": { - "type": "object", - "properties": { - "all": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": ["all"] - }, - "gateway_connected": { - "type": "boolean" - }, - "gif_auto_play": { - "type": "boolean" - }, - "guild_folders": { - "type": "array", - "items": { - "type": "object", - "properties": { - "color": { - "type": "integer" - }, - "guild_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["color", "guild_ids", "id", "name"] - } - }, - "guild_positions": { - "type": "array", - "items": { - "type": "string" - } - }, - "inline_attachment_media": { - "type": "boolean" - }, - "inline_embed_media": { - "type": "boolean" - }, - "locale": { - "type": "string" - }, - "message_display_compact": { - "type": "boolean" - }, - "native_phone_integration_enabled": { - "type": "boolean" - }, - "render_embeds": { - "type": "boolean" - }, - "render_reactions": { - "type": "boolean" - }, - "restricted_guilds": { - "type": "array", - "items": { - "type": "string" - } - }, - "show_current_game": { - "type": "boolean" - }, - "status": { - "enum": ["dnd", "idle", "offline", "online"], - "type": "string" - }, - "stream_notifications_enabled": { - "type": "boolean" - }, - "theme": { - "enum": ["dark", "white"], - "type": "string" - }, - "timezone_offset": { - "type": "integer" - } - }, - "required": [ - "afk_timeout", - "allow_accessibility_detection", - "animate_emoji", - "animate_stickers", - "contact_sync_enabled", - "convert_emoticons", - "custom_status", - "default_guilds_restricted", - "detect_platform_accounts", - "developer_mode", - "disable_games_tab", - "enable_tts_command", - "explicit_content_filter", - "friend_source_flags", - "gateway_connected", - "gif_auto_play", - "guild_folders", - "guild_positions", - "inline_attachment_media", - "inline_embed_media", - "locale", - "message_display_compact", - "native_phone_integration_enabled", - "render_embeds", - "render_reactions", - "restricted_guilds", - "show_current_game", - "status", - "stream_notifications_enabled", - "theme", - "timezone_offset" - ] - }, - "WidgetModifySchema": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "channel_id": { - "type": "string" - } - }, - "required": ["channel_id", "enabled"] - }, - "RegisterSchema": { - "type": "object", - "properties": { - "username": { - "minLength": 2, - "maxLength": 32, - "type": "string" - }, - "password": { - "minLength": 1, - "maxLength": 72, - "type": "string" - }, - "consent": { - "type": "boolean" - }, - "email": { - "format": "email", - "type": "string" - }, - "fingerprint": { - "type": "string" - }, - "invite": { - "type": "string" - }, - "date_of_birth": { - "type": "string" - }, - "gift_code_sku_id": { - "type": "string" - }, - "captcha_key": { - "type": "string" - } - }, - "required": ["consent", "username"] - }, - "LoginSchema": { - "type": "object", - "properties": { - "login": { - "type": "string" - }, - "password": { - "type": "string" - }, - "undelete": { - "type": "boolean" - }, - "captcha_key": { - "type": "string" - }, - "login_source": { - "type": "string" - }, - "gift_code_sku_id": { - "type": "string" - } - }, - "required": ["login", "password"] - }, - "MessageAcknowledgeSchema": { - "type": "object", - "properties": { - "manual": { - "type": "boolean" - }, - "mention_count": { - "type": "integer" - } - } - }, - "BulkDeleteSchema": { - "type": "object", - "properties": { - "messages": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["messages"] - }, - "ChannelPermissionOverwriteSchema": { - "type": "object", - "properties": { - "allow": { - "type": "number" - }, - "deny": { - "type": "number" - }, - "id": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/ChannelPermissionOverwriteType" - } - }, - "required": ["allow", "deny", "id", "type"] - }, - "WebhookCreateSchema": { - "type": "object", - "properties": { - "name": { - "maxLength": 80, - "type": "string" - }, - "avatar": { - "type": "string" - } - }, - "required": ["avatar", "name"] - }, - "ChannelReorderSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "position": { - "type": "integer" - }, - "lock_permissions": { - "type": "boolean" - }, - "parent_id": { - "type": "string" - } - }, - "additionalProperties": false, - "required": ["id"] - } - }, - "RolePositionUpdateSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "position": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": ["id", "position"] - } - }, - "VanityUrlSchema": { - "type": "object", - "properties": { - "code": { - "minLength": 1, - "maxLength": 20, - "type": "string" - } - } - }, - "VoiceStateUpdateSchema": { - "type": "object", - "properties": { - "channel_id": { - "type": "string" - }, - "guild_id": { - "type": "string" - }, - "suppress": { - "type": "boolean" - }, - "request_to_speak_timestamp": { - "type": "string", - "format": "date-time" - }, - "self_mute": { - "type": "boolean" - }, - "self_deaf": { - "type": "boolean" - }, - "self_video": { - "type": "boolean" - } - }, - "required": ["channel_id"] - }, - "UserProfileResponse": { - "type": "object", - "properties": { - "user": { - "$ref": "#/components/schemas/UserPublic" - }, - "connected_accounts": { - "$ref": "#/components/schemas/PublicConnectedAccount" - }, - "premium_guild_since": { - "type": "string", - "format": "date-time" - }, - "premium_since": { - "type": "string", - "format": "date-time" - } - }, - "required": ["connected_accounts", "user"] - }, - "RelationshipPutSchema": { - "type": "object", - "properties": { - "type": { - "enum": [1, 2, 3, 4], - "type": "number" - } - } - }, - "RelationshipPostSchema": { - "type": "object", - "properties": { - "discriminator": { - "type": "string" - }, - "username": { - "type": "string" - } - }, - "required": ["discriminator", "username"] - }, - "PublicConnectedAccount": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "verifie": { - "type": "boolean" - } - }, - "required": ["name", "type", "verifie"] - } - }, - "requestBodies": {}, - "securitySchemes": { - "Token": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - } - }, - "links": {}, - "callbacks": {} - } -} + "openapi": "3.0.0", + "servers": [ + { + "url": "https://api.fosscord.com/v{version}", + "description": "Official fosscord instance", + "variables": { + "version": { + "default": "9", + "enum": [ + "8", + "9" + ] + } + } + } + ], + "info": { + "description": "Fosscord is a free open source selfhostable discord compatible chat, voice and video platform", + "version": "1.0.0", + "title": "Fosscord HTTP API Routes", + "termsOfService": "", + "contact": { + "name": "Fosscord" + }, + "license": { + "name": "AGPLV3", + "url": "https://www.gnu.org/licenses/agpl-3.0.en.html" + } + }, + "tags": [ + { + "name": "voice" + }, + { + "name": "users" + }, + { + "name": "store" + }, + { + "name": "sticker-packs" + }, + { + "name": "science" + }, + { + "name": "ping" + }, + { + "name": "outbound-promotions" + }, + { + "name": "invites" + }, + { + "name": "guilds" + }, + { + "name": "gateway" + }, + { + "name": "experiments" + }, + { + "name": "discoverable-guilds" + }, + { + "name": "channels" + }, + { + "name": "auth" + }, + { + "name": "applications" + } + ], + "paths": { + "/users/{id}": { + "get": { + "summary": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user id" + } + ], + "operationId": "", + "responses": { + "200": { + "description": "User found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserPublic" + } + } + } + }, + "404": { + "description": "User not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "Token": [] + } + ] + } + }, + "/users/@me": { + "get": { + "summary": "", + "parameters": [], + "operationId": "", + "responses": { + "200": { + "description": "Authenticated user", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserPublic" + } + } + } + } + }, + "security": [ + { + "Token": [] + } + ] + } + }, + "/voice/regions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "voice" + ] + } + }, + "/users/@me/settings/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSettingsSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/relationships/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users", + "relationships" + ] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipPostSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/relationships/{id}": { + "put": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipPutSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": [ + "users" + ] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": [ + "users" + ] + } + }, + "/users/@me/library/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/guilds/{id}": { + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": [ + "users" + ] + } + }, + "/users/@me/disable/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/devices/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/delete/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/connections/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/channels/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DmChannelCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/billing/subscriptions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/billing/country-code/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/applications/{app_id}/entitlements/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "app_id" + } + ], + "tags": [ + "users" + ] + } + }, + "/users/@me/affinities/users/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/@me/affinities/guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "users" + ] + } + }, + "/users/{id}/profile/": { + "get": { + "description": "", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserProfileResponse" + } + } + }, + "description": "" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": [ + "users" + ] + } + }, + "/users/{id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": [ + "users" + ] + } + }, + "/store/skus/skus/{id}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": [ + "store" + ] + } + }, + "/store/applications/applications/{id}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": [ + "store" + ] + } + }, + "/sticker-packs/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "sticker", + "sticker-packs" + ] + } + }, + "/sticker-packs/{id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "id" + } + ], + "tags": [ + "sticker", + "sticker-packs" + ] + } + }, + "/science/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "science" + ] + } + }, + "/ping/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "ping" + ] + } + }, + "/outbound-promotions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "outbound", + "outbound-promotions" + ] + } + }, + "/invites/{code}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": [ + "invites" + ] + }, + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": [ + "invites" + ] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": [ + "invites" + ] + } + }, + "/guilds/templates/{code}": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": [ + "guilds" + ] + }, + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildTemplateCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/widget.png/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/widget.json/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/widget/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WidgetModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/welcome_screen/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildUpdateWelcomeScreenSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/voice-states/{user_id}/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VoiceStateUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/vanity-url/": { + "get": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VanityUrlSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/templates/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "post": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/templates/{code}": { + "delete": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": [ + "guilds" + ] + }, + "put": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "code" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/roles/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "post": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RolePositionUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/roles/{role_id}": { + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/regions/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/members/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/members/{member_id}/nick/": { + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemberNickChangeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/members/{member_id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemberChangeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": [ + "guilds" + ] + }, + "put": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": [ + "guilds" + ] + }, + "delete": { + "description": "##### Requires the ``KICK_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/invites/": { + "get": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "##### Requires the ``MANAGE_GUILD`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuildUpdateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/delete/": { + "post": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/channels/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "post": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + }, + "patch": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelReorderSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/bans/": { + "get": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/bans/{user}": { + "get": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/guilds/{guild_id}/bans/{user_id}": { + "put": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BanCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": [ + "guilds" + ] + }, + "delete": { + "description": "##### Requires the ``BAN_MEMBERS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/gateway/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "gateway" + ] + } + }, + "/gateway/bot": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "gateway" + ] + } + }, + "/experiments/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "experiments" + ] + } + }, + "/discoverable-guilds/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "discoverable", + "discoverable-guilds" + ] + } + }, + "/channels/{channel_id}/webhooks/": { + "post": { + "description": "##### Requires the ``MANAGE_WEBHOOKS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/typing/": { + "post": { + "description": "##### Requires the ``SEND_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/recipients/{user_id}": { + "put": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": [ + "channels" + ] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/pins/{message_id}": { + "put": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": [ + "channels" + ] + }, + "delete": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/pins/": { + "get": { + "description": "##### Requires the ``READ_MESSAGE_HISTORY`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/permissions/{overwrite_id}": { + "put": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "overwrite_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "overwrite_id" + } + ], + "tags": [ + "channels" + ] + }, + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "overwrite_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "overwrite_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/messages/bulk-delete/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkDeleteSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/": { + "delete": { + "description": "##### Requires the ``MANAGE_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}": { + "delete": { + "description": "##### Requires the ``MANAGE_MESSAGES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + } + ], + "tags": [ + "channels" + ] + }, + "get": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{user_id}": { + "put": { + "description": "##### Requires the ``READ_MESSAGE_HISTORY`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": [ + "channels" + ] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + }, + { + "name": "emoji", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "emoji" + }, + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "user_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/messages/{message_id}/": { + "patch": { + "description": "##### Requires the ``SEND_MESSAGES`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": [ + "channels" + ] + }, + "delete": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/messages/{message_id}/ack/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageAcknowledgeSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + }, + { + "name": "message_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "message_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/invites/": { + "post": { + "description": "##### Requires the ``CREATE_INSTANT_INVITE`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InviteCreateSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + }, + "get": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/channels/{channel_id}/": { + "get": { + "description": "##### Requires the ``VIEW_CHANNEL`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + }, + "delete": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + }, + "patch": { + "description": "##### Requires the ``MANAGE_CHANNELS`` permission\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "channel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "channel_id" + } + ], + "tags": [ + "channels" + ] + } + }, + "/auth/register/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "auth" + ] + } + }, + "/applications/detectable/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "applications" + ] + } + }, + "/guilds/{guild_id}/members/{member_id}/roles/{role_id}/": { + "delete": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": [ + "guilds" + ] + }, + "put": { + "description": "##### Requires the ``MANAGE_ROLES`` permission\n", + "responses": { + "default": { + "description": "not documented" + } + }, + "parameters": [ + { + "name": "guild_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "guild_id" + }, + { + "name": "member_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "member_id" + }, + { + "name": "role_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "role_id" + } + ], + "tags": [ + "guilds" + ] + } + }, + "/auth/login/": { + "post": { + "description": "", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginSchema" + } + } + } + }, + "responses": { + "default": { + "description": "not documented" + } + }, + "tags": [ + "auth" + ] + } + } + }, + "externalDocs": { + "url": "http://docs.fosscord.com/" + }, + "components": { + "schemas": { + "Error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ] + }, + "RateLimit": { + "type": "object", + "properties": { + "retry_after": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "global": { + "type": "boolean" + } + }, + "required": [ + "code", + "message", + "globa" + ] + }, + "User": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "desktop": { + "type": "boolean" + }, + "mobile": { + "type": "boolean" + }, + "premium": { + "type": "boolean" + }, + "premium_type": { + "type": "integer" + }, + "bot": { + "type": "boolean" + }, + "bio": { + "type": "string" + }, + "system": { + "type": "boolean" + }, + "nsfw_allowed": { + "type": "boolean" + }, + "mfa_enabled": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "verified": { + "type": "boolean" + }, + "disabled": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "public_flags": { + "type": "string" + }, + "relationships": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Relationship" + } + }, + "connected_accounts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConnectedAccount" + } + }, + "data": { + "type": "object", + "properties": { + "valid_tokens_since": { + "type": "string", + "format": "date-time" + }, + "hash": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "valid_tokens_since" + ] + }, + "fingerprints": { + "type": "array", + "items": { + "type": "string" + } + }, + "settings": { + "$ref": "#/components/schemas/UserSettings" + }, + "id": { + "type": "string" + } + }, + "required": [ + "bio", + "bot", + "connected_accounts", + "created_at", + "data", + "deleted", + "desktop", + "disabled", + "discriminator", + "fingerprints", + "flags", + "id", + "mfa_enabled", + "mobile", + "nsfw_allowed", + "premium", + "premium_type", + "public_flags", + "relationships", + "settings", + "system", + "username", + "verified" + ] + }, + "Relationship": { + "type": "object", + "properties": { + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "nickname": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/RelationshipType" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id", + "type", + "user", + "user_id" + ] + }, + "RelationshipType": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + }, + "ConnectedAccount": { + "type": "object", + "properties": { + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "access_token": { + "type": "string" + }, + "friend_sync": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "revoked": { + "type": "boolean" + }, + "show_activity": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + }, + "visibility": { + "type": "integer" + }, + "id": { + "type": "string" + } + }, + "required": [ + "access_token", + "friend_sync", + "id", + "name", + "revoked", + "show_activity", + "type", + "user", + "user_id", + "verifie", + "visibility" + ] + }, + "UserSettings": { + "type": "object", + "properties": { + "afk_timeout": { + "type": "integer" + }, + "allow_accessibility_detection": { + "type": "boolean" + }, + "animate_emoji": { + "type": "boolean" + }, + "animate_stickers": { + "type": "integer" + }, + "contact_sync_enabled": { + "type": "boolean" + }, + "convert_emoticons": { + "type": "boolean" + }, + "custom_status": { + "type": "object", + "properties": { + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "expires_at": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "additionalProperties": false + }, + "default_guilds_restricted": { + "type": "boolean" + }, + "detect_platform_accounts": { + "type": "boolean" + }, + "developer_mode": { + "type": "boolean" + }, + "disable_games_tab": { + "type": "boolean" + }, + "enable_tts_command": { + "type": "boolean" + }, + "explicit_content_filter": { + "type": "integer" + }, + "friend_source_flags": { + "type": "object", + "properties": { + "all": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "all" + ] + }, + "gateway_connected": { + "type": "boolean" + }, + "gif_auto_play": { + "type": "boolean" + }, + "guild_folders": { + "type": "array", + "items": { + "type": "object", + "properties": { + "color": { + "type": "integer" + }, + "guild_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "color", + "guild_ids", + "id", + "name" + ] + } + }, + "guild_positions": { + "type": "array", + "items": { + "type": "string" + } + }, + "inline_attachment_media": { + "type": "boolean" + }, + "inline_embed_media": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "message_display_compact": { + "type": "boolean" + }, + "native_phone_integration_enabled": { + "type": "boolean" + }, + "render_embeds": { + "type": "boolean" + }, + "render_reactions": { + "type": "boolean" + }, + "restricted_guilds": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_current_game": { + "type": "boolean" + }, + "status": { + "enum": [ + "dnd", + "idle", + "offline", + "online" + ], + "type": "string" + }, + "stream_notifications_enabled": { + "type": "boolean" + }, + "theme": { + "enum": [ + "dark", + "white" + ], + "type": "string" + }, + "timezone_offset": { + "type": "integer" + } + }, + "required": [ + "afk_timeout", + "allow_accessibility_detection", + "animate_emoji", + "animate_stickers", + "contact_sync_enabled", + "convert_emoticons", + "custom_status", + "default_guilds_restricted", + "detect_platform_accounts", + "developer_mode", + "disable_games_tab", + "enable_tts_command", + "explicit_content_filter", + "friend_source_flags", + "gateway_connected", + "gif_auto_play", + "guild_folders", + "guild_positions", + "inline_attachment_media", + "inline_embed_media", + "locale", + "message_display_compact", + "native_phone_integration_enabled", + "render_embeds", + "render_reactions", + "restricted_guilds", + "show_current_game", + "status", + "stream_notifications_enabled", + "theme", + "timezone_offset" + ] + }, + "Team": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamMember" + } + }, + "name": { + "type": "string" + }, + "owner_user_id": { + "type": "string" + }, + "owner_user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id", + "members", + "name", + "owner_user", + "owner_user_id" + ] + }, + "TeamMember": { + "type": "object", + "properties": { + "membership_state": { + "$ref": "#/components/schemas/TeamMemberState" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "team_id": { + "type": "string" + }, + "team": { + "$ref": "#/components/schemas/Team" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id", + "membership_state", + "permissions", + "team", + "team_id", + "user", + "user_id" + ] + }, + "TeamMemberState": { + "enum": [ + 1, + 2 + ], + "type": "number" + }, + "Guild": { + "type": "object", + "properties": { + "afk_channel_id": { + "type": "string" + }, + "afk_channel": { + "$ref": "#/components/schemas/Channel" + }, + "afk_timeout": { + "type": "integer" + }, + "bans": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Ban" + } + }, + "banner": { + "type": "string" + }, + "default_message_notifications": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "discovery_splash": { + "type": "string" + }, + "explicit_content_filter": { + "type": "integer" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "icon": { + "type": "string" + }, + "large": { + "type": "boolean" + }, + "max_members": { + "type": "integer" + }, + "max_presences": { + "type": "integer" + }, + "max_video_channel_users": { + "type": "integer" + }, + "member_count": { + "type": "integer" + }, + "presence_count": { + "type": "integer" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Member" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Channel" + } + }, + "template_id": { + "type": "string" + }, + "template": { + "$ref": "#/components/schemas/Template" + }, + "emojis": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Emoji" + } + }, + "stickers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sticker" + } + }, + "invites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Invite" + } + }, + "voice_states": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VoiceState" + } + }, + "webhooks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Webhook" + } + }, + "mfa_level": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "preferred_locale": { + "type": "string" + }, + "premium_subscription_count": { + "type": "integer" + }, + "premium_tier": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "public_updates_channel": { + "$ref": "#/components/schemas/Channel" + }, + "rules_channel_id": { + "type": "string" + }, + "rules_channel": { + "type": "string" + }, + "region": { + "type": "string" + }, + "splash": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "system_channel": { + "$ref": "#/components/schemas/Channel" + }, + "system_channel_flags": { + "type": "integer" + }, + "unavailable": { + "type": "boolean" + }, + "vanity_url_code": { + "type": "string" + }, + "vanity_url": { + "$ref": "#/components/schemas/Invite" + }, + "verification_level": { + "type": "integer" + }, + "welcome_screen": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "welcome_channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "channel_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "description", + "emoji_name" + ] + } + } + }, + "additionalProperties": false, + "required": [ + "description", + "enabled", + "welcome_channels" + ] + }, + "widget_channel_id": { + "type": "string" + }, + "widget_channel": { + "$ref": "#/components/schemas/Channel" + }, + "widget_enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + } + }, + "required": [ + "bans", + "channels", + "emojis", + "features", + "id", + "invites", + "members", + "name", + "owner", + "owner_id", + "public_updates_channel_id", + "roles", + "stickers", + "template", + "template_id", + "voice_states", + "webhooks", + "welcome_screen" + ] + }, + "Channel": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelType" + }, + "recipients": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recipient" + } + }, + "last_message_id": { + "type": "string" + }, + "last_message": { + "$ref": "#/components/schemas/Message" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "parent_id": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/Channel" + }, + "owner_id": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "last_pin_timestamp": { + "type": "integer" + }, + "default_auto_archive_duration": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelPermissionOverwrite" + } + }, + "video_quality_mode": { + "type": "integer" + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "nsfw": { + "type": "boolean" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "topic": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "created_at", + "guild", + "id", + "last_message_id", + "name", + "owner", + "owner_id", + "parent_id", + "permission_overwrites", + "position", + "type" + ] + }, + "ChannelType": { + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "Recipient": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "id": { + "type": "string" + } + }, + "required": [ + "channel", + "channel_id", + "id", + "user" + ] + }, + "Message": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "author_id": { + "type": "string" + }, + "author": { + "$ref": "#/components/schemas/User" + }, + "member_id": { + "type": "string" + }, + "member": { + "$ref": "#/components/schemas/Member" + }, + "webhook_id": { + "type": "string" + }, + "webhook": { + "$ref": "#/components/schemas/Webhook" + }, + "application_id": { + "type": "string" + }, + "application": { + "$ref": "#/components/schemas/Application" + }, + "content": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "edited_timestamp": { + "type": "string", + "format": "date-time" + }, + "tts": { + "type": "boolean" + }, + "mention_everyone": { + "type": "boolean" + }, + "mentions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "mention_roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "mention_channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Channel" + } + }, + "sticker_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sticker" + } + }, + "attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Attachment" + } + }, + "embeds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Embed" + } + }, + "reactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Reaction" + } + }, + "nonce": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "type": { + "$ref": "#/components/schemas/MessageType" + }, + "activity": { + "type": "object", + "properties": { + "type": { + "type": "integer" + }, + "party_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "party_id", + "type" + ] + }, + "flags": { + "type": "string" + }, + "message_reference": { + "type": "object", + "properties": { + "message_id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "message_id" + ] + }, + "referenced_message": { + "$ref": "#/components/schemas/Message" + }, + "interaction": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/InteractionType" + }, + "name": { + "type": "string" + }, + "user_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "id", + "name", + "type", + "user_id" + ] + }, + "components": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageComponent" + } + } + }, + "required": [ + "application_id", + "author_id", + "channel", + "channel_id", + "embeds", + "id", + "member_id", + "mention_channels", + "mention_roles", + "mentions", + "reactions", + "timestamp", + "type", + "webhook_id" + ] + }, + "Member": { + "type": "object", + "properties": { + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "nick": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "joined_at": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "integer" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "pending": { + "type": "boolean" + }, + "settings": { + "$ref": "#/components/schemas/UserGuildSettings" + }, + "id": { + "type": "string" + } + }, + "required": [ + "deaf", + "guild", + "guild_id", + "id", + "joined_at", + "mute", + "pending", + "roles", + "settings", + "user", + "user_id" + ] + }, + "Role": { + "type": "object", + "properties": { + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "managed": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "tags": { + "type": "object", + "properties": { + "bot_id": { + "type": "string" + }, + "integration_id": { + "type": "string" + }, + "premium_subscriber": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "id": { + "type": "string" + } + }, + "required": [ + "color", + "guild", + "guild_id", + "hoist", + "id", + "managed", + "mentionable", + "name", + "permissions", + "position" + ] + }, + "UserGuildSettings": { + "type": "object", + "properties": { + "channel_overrides": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "message_notifications": { + "type": "integer" + }, + "mute_config": { + "$ref": "#/components/schemas/MuteConfig" + }, + "muted": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "message_notifications", + "mute_config", + "muted" + ] + } + }, + "message_notifications": { + "type": "integer" + }, + "mobile_push": { + "type": "boolean" + }, + "mute_config": { + "$ref": "#/components/schemas/MuteConfig" + }, + "muted": { + "type": "boolean" + }, + "suppress_everyone": { + "type": "boolean" + }, + "suppress_roles": { + "type": "boolean" + }, + "version": { + "type": "integer" + } + }, + "required": [ + "channel_overrides", + "message_notifications", + "mobile_push", + "mute_config", + "muted", + "suppress_everyone", + "suppress_roles", + "version" + ] + }, + "MuteConfig": { + "type": "object", + "properties": { + "end_time": { + "type": "integer" + }, + "selected_time_window": { + "type": "integer" + } + }, + "required": [ + "end_time", + "selected_time_window" + ] + }, + "Webhook": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/WebhookType" + }, + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "token": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "application_id": { + "type": "string" + }, + "application": { + "$ref": "#/components/schemas/Application" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "source_guild_id": { + "type": "string" + }, + "source_guild": { + "$ref": "#/components/schemas/Guild" + } + }, + "required": [ + "application", + "application_id", + "channel", + "channel_id", + "guild", + "guild_id", + "id", + "source_guild", + "source_guild_id", + "type", + "user", + "user_id" + ] + }, + "WebhookType": { + "enum": [ + 1, + 2 + ], + "type": "number" + }, + "Application": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rpc_origins": { + "type": "array", + "items": { + "type": "string" + } + }, + "bot_public": { + "type": "boolean" + }, + "bot_require_code_grant": { + "type": "boolean" + }, + "terms_of_service_url": { + "type": "string" + }, + "privacy_policy_url": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/User" + }, + "summary": { + "type": "string" + }, + "verify_key": { + "type": "string" + }, + "team": { + "$ref": "#/components/schemas/Team" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "primary_sku_id": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "cover_image": { + "type": "string" + }, + "flags": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "bot_public", + "bot_require_code_grant", + "description", + "flags", + "guild", + "id", + "name", + "verify_key" + ] + }, + "Sticker": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "pack_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "type": { + "$ref": "#/components/schemas/StickerType" + }, + "format_type": { + "$ref": "#/components/schemas/StickerFormatType" + }, + "id": { + "type": "string" + } + }, + "required": [ + "format_type", + "id", + "name", + "pack_id", + "tags", + "type" + ] + }, + "StickerType": { + "enum": [ + 1, + 2 + ], + "type": "number" + }, + "StickerFormatType": { + "enum": [ + 1, + 2, + 3 + ], + "type": "number" + }, + "Attachment": { + "type": "object", + "properties": { + "filename": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + }, + "content_type": { + "type": "string" + }, + "message_id": { + "type": "string" + }, + "message": { + "$ref": "#/components/schemas/Message" + }, + "id": { + "type": "string" + } + }, + "required": [ + "filename", + "id", + "message", + "message_id", + "proxy_url", + "size", + "url" + ] + }, + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/components/schemas/EmbedImage" + }, + "thumbnail": { + "$ref": "#/components/schemas/EmbedImage" + }, + "video": { + "$ref": "#/components/schemas/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + } + }, + "EmbedType": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + } + }, + "Reaction": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "emoji": { + "$ref": "#/components/schemas/PartialEmoji" + }, + "user_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "count", + "emoji", + "user_ids" + ] + }, + "PartialEmoji": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "animated": { + "type": "boolean" + } + }, + "required": [ + "name" + ] + }, + "MessageType": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 14, + 15, + 19, + 2, + 20, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "type": "number" + }, + "InteractionType": { + "enum": [ + 1, + 2 + ], + "type": "number" + }, + "MessageComponent": { + "type": "object", + "properties": { + "type": { + "type": "integer" + }, + "style": { + "type": "integer" + }, + "label": { + "type": "string" + }, + "emoji": { + "$ref": "#/components/schemas/PartialEmoji" + }, + "custom_id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "components": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageComponent" + } + } + }, + "required": [ + "components", + "type" + ] + }, + "ChannelPermissionOverwrite": { + "type": "object", + "properties": { + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + } + }, + "required": [ + "allow", + "deny", + "id", + "type" + ] + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1 + ], + "type": "number" + }, + "Ban": { + "type": "object", + "properties": { + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "executor_id": { + "type": "string" + }, + "executor": { + "$ref": "#/components/schemas/User" + }, + "ip": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "executor", + "executor_id", + "guild", + "guild_id", + "id", + "ip", + "user", + "user_id" + ] + }, + "Template": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "usage_count": { + "type": "integer" + }, + "creator_id": { + "type": "string" + }, + "creator": { + "$ref": "#/components/schemas/User" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "source_guild_id": { + "type": "string" + }, + "source_guild": { + "$ref": "#/components/schemas/Guild" + }, + "serialized_source_guild": { + "$ref": "#/components/schemas/Guild" + }, + "id": { + "type": "string" + } + }, + "required": [ + "code", + "created_at", + "creator", + "creator_id", + "id", + "name", + "serialized_source_guild", + "source_guild", + "source_guild_id", + "updated_at" + ] + }, + "Emoji": { + "type": "object", + "properties": { + "animated": { + "type": "boolean" + }, + "available": { + "type": "boolean" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "managed": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "require_colons": { + "type": "boolean" + }, + "id": { + "type": "string" + } + }, + "required": [ + "animated", + "available", + "guild", + "guild_id", + "id", + "managed", + "name", + "require_colons" + ] + }, + "Invite": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "temporary": { + "type": "boolean" + }, + "uses": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "max_age": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "inviter_id": { + "type": "string" + }, + "inviter": { + "$ref": "#/components/schemas/User" + }, + "target_user_id": { + "type": "string" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + }, + "id": { + "type": "string" + } + }, + "required": [ + "channel", + "channel_id", + "code", + "created_at", + "expires_at", + "guild", + "guild_id", + "id", + "inviter", + "inviter_id", + "max_age", + "max_uses", + "target_user_id", + "temporary", + "uses" + ] + }, + "VoiceState": { + "type": "object", + "properties": { + "guild_id": { + "type": "string" + }, + "guild": { + "$ref": "#/components/schemas/Guild" + }, + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "session_id": { + "type": "string" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_mute": { + "type": "boolean" + }, + "self_stream": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" + }, + "suppress": { + "type": "boolean" + }, + "id": { + "type": "string" + } + }, + "required": [ + "channel", + "channel_id", + "deaf", + "guild_id", + "id", + "mute", + "self_deaf", + "self_mute", + "self_video", + "session_id", + "suppress", + "user", + "user_id" + ] + }, + "AuditLogEvents": { + "enum": [ + 1, + 10, + 11, + 12, + 13, + 14, + 15, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 30, + 31, + 32, + 40, + 41, + 42, + 50, + 51, + 52, + 60, + 61, + 62, + 72, + 73, + 74, + 75, + 80, + 81, + 82 + ], + "type": "number" + }, + "AuditLogChange": { + "type": "object", + "properties": { + "new_value": { + "$ref": "#/components/schemas/AuditLogChangeValue" + }, + "old_value": { + "$ref": "#/components/schemas/AuditLogChangeValue" + }, + "key": { + "type": "string" + } + }, + "required": [ + "key" + ] + }, + "AuditLogChangeValue": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "icon_hash": { + "type": "string" + }, + "splash_hash": { + "type": "string" + }, + "discovery_splash_hash": { + "type": "string" + }, + "banner_hash": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "region": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "afk_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "rules_channel_id": { + "type": "string" + }, + "public_updates_channel_id": { + "type": "string" + }, + "mfa_level": { + "type": "integer" + }, + "verification_level": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "vanity_url_code": { + "type": "string" + }, + "$add": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "$remove": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "prune_delete_days": { + "type": "integer" + }, + "widget_enabled": { + "type": "boolean" + }, + "widget_channel_id": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "topic": { + "type": "string" + }, + "bitrate": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelPermissionOverwrite" + } + }, + "nsfw": { + "type": "boolean" + }, + "application_id": { + "type": "string" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "permissions": { + "type": "string" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + }, + "code": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "inviter_id": { + "type": "string" + }, + "max_uses": { + "type": "integer" + }, + "uses": { + "type": "integer" + }, + "max_age": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "deaf": { + "type": "boolean" + }, + "mute": { + "type": "boolean" + }, + "nick": { + "type": "string" + }, + "avatar_hash": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "enable_emoticons": { + "type": "boolean" + }, + "expire_behavior": { + "type": "integer" + }, + "expire_grace_period": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + } + } + }, + "AuditLog": { + "type": "object", + "properties": { + "target": { + "$ref": "#/components/schemas/User" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "action_type": { + "$ref": "#/components/schemas/AuditLogEvents" + }, + "options": { + "type": "object", + "properties": { + "delete_member_days": { + "type": "string" + }, + "members_removed": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "messaged_id": { + "type": "string" + }, + "count": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "role_name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLogChange" + } + }, + "reason": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "action_type", + "changes", + "id", + "user", + "user_id" + ] + }, + "ReadState": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "channel": { + "$ref": "#/components/schemas/Channel" + }, + "user_id": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "last_message_id": { + "type": "string" + }, + "last_message": { + "$ref": "#/components/schemas/Message" + }, + "last_pin_timestamp": { + "type": "string", + "format": "date-time" + }, + "mention_count": { + "type": "integer" + }, + "manual": { + "type": "boolean" + }, + "id": { + "type": "string" + } + }, + "required": [ + "channel", + "channel_id", + "id", + "last_message_id", + "manual", + "mention_count", + "user", + "user_id" + ] + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + } + }, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "public_flags", + "username" + ] + }, + "UserPrivate": { + "type": "object", + "properties": { + "locale": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "mfa_enabled": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "verified": { + "type": "boolean" + }, + "nsfw_allowed": { + "type": "boolean" + }, + "premium": { + "type": "boolean" + }, + "premium_type": { + "type": "integer" + } + }, + "required": [ + "bio", + "bot", + "disabled", + "discriminator", + "flags", + "id", + "locale", + "mfa_enabled", + "nsfw_allowed", + "premium", + "premium_type", + "public_flags", + "username", + "verified" + ] + }, + "BanCreateSchema": { + "type": "object", + "properties": { + "delete_message_days": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + }, + "DmChannelCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "recipients": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "recipients" + ] + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 2, + 3, + 4, + 5, + 6 + ], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + } + }, + "ChannelGuildPositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id" + ] + } + }, + "EmojiCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "image": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "image", + "name" + ] + }, + "GuildCreateSchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelModifySchema" + } + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "GuildUpdateSchema": { + "type": "object", + "properties": { + "banner": { + "type": "string", + "nullable": true + }, + "splash": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string" + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "verification_level": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "system_channel_flags": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "afk_channel_id": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "GuildTemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string", + "nullable": true + } + }, + "required": [ + "name" + ] + }, + "GuildUpdateWelcomeScreenSchema": { + "type": "object", + "properties": { + "welcome_channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "description", + "emoji_name" + ] + } + }, + "enabled": { + "type": "boolean" + }, + "description": { + "type": "string" + } + } + }, + "InviteCreateSchema": { + "type": "object", + "properties": { + "target_user_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "validate": { + "type": "string" + }, + "max_age": { + "type": "integer" + }, + "max_uses": { + "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + } + } + }, + "MemberCreateSchema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "nick": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "joined_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "guild_id", + "id", + "joined_at", + "nick" + ] + }, + "MemberNickChangeSchema": { + "type": "object", + "properties": { + "nick": { + "type": "string" + } + }, + "required": [ + "nick" + ] + }, + "MemberChangeSchema": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "MessageCreateSchema": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "tts": { + "type": "boolean" + }, + "flags": { + "type": "string" + }, + "embeds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Embed" + } + }, + "embed": { + "$ref": "#/components/schemas/Embed" + }, + "allowed_mentions": { + "type": "object", + "properties": { + "parse": { + "type": "array", + "items": { + "type": "string" + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + }, + "replied_user": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "message_reference": { + "type": "object", + "properties": { + "message_id": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "fail_if_not_exists": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "message_id" + ] + }, + "payload_json": { + "type": "string" + }, + "file": {}, + "attachments": { + "type": "array", + "items": {} + } + } + }, + "RoleModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "permissions": { + "type": "number" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "position": { + "type": "integer" + } + } + }, + "TemplateCreateSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "TemplateModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "UserModifySchema": { + "type": "object", + "properties": { + "username": { + "minLength": 1, + "maxLength": 100, + "type": "string" + }, + "avatar": { + "type": "string", + "nullable": true + }, + "bio": { + "maxLength": 1024, + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string", + "nullable": true + }, + "password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "UserSettingsSchema": { + "type": "object", + "properties": { + "afk_timeout": { + "type": "integer" + }, + "allow_accessibility_detection": { + "type": "boolean" + }, + "animate_emoji": { + "type": "boolean" + }, + "animate_stickers": { + "type": "integer" + }, + "contact_sync_enabled": { + "type": "boolean" + }, + "convert_emoticons": { + "type": "boolean" + }, + "custom_status": { + "type": "object", + "properties": { + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + }, + "expires_at": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "additionalProperties": false + }, + "default_guilds_restricted": { + "type": "boolean" + }, + "detect_platform_accounts": { + "type": "boolean" + }, + "developer_mode": { + "type": "boolean" + }, + "disable_games_tab": { + "type": "boolean" + }, + "enable_tts_command": { + "type": "boolean" + }, + "explicit_content_filter": { + "type": "integer" + }, + "friend_source_flags": { + "type": "object", + "properties": { + "all": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "all" + ] + }, + "gateway_connected": { + "type": "boolean" + }, + "gif_auto_play": { + "type": "boolean" + }, + "guild_folders": { + "type": "array", + "items": { + "type": "object", + "properties": { + "color": { + "type": "integer" + }, + "guild_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "color", + "guild_ids", + "id", + "name" + ] + } + }, + "guild_positions": { + "type": "array", + "items": { + "type": "string" + } + }, + "inline_attachment_media": { + "type": "boolean" + }, + "inline_embed_media": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "message_display_compact": { + "type": "boolean" + }, + "native_phone_integration_enabled": { + "type": "boolean" + }, + "render_embeds": { + "type": "boolean" + }, + "render_reactions": { + "type": "boolean" + }, + "restricted_guilds": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_current_game": { + "type": "boolean" + }, + "status": { + "enum": [ + "dnd", + "idle", + "offline", + "online" + ], + "type": "string" + }, + "stream_notifications_enabled": { + "type": "boolean" + }, + "theme": { + "enum": [ + "dark", + "white" + ], + "type": "string" + }, + "timezone_offset": { + "type": "integer" + } + }, + "required": [ + "afk_timeout", + "allow_accessibility_detection", + "animate_emoji", + "animate_stickers", + "contact_sync_enabled", + "convert_emoticons", + "custom_status", + "default_guilds_restricted", + "detect_platform_accounts", + "developer_mode", + "disable_games_tab", + "enable_tts_command", + "explicit_content_filter", + "friend_source_flags", + "gateway_connected", + "gif_auto_play", + "guild_folders", + "guild_positions", + "inline_attachment_media", + "inline_embed_media", + "locale", + "message_display_compact", + "native_phone_integration_enabled", + "render_embeds", + "render_reactions", + "restricted_guilds", + "show_current_game", + "status", + "stream_notifications_enabled", + "theme", + "timezone_offset" + ] + }, + "WidgetModifySchema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "channel_id": { + "type": "string" + } + }, + "required": [ + "channel_id", + "enabled" + ] + }, + "RegisterSchema": { + "type": "object", + "properties": { + "username": { + "minLength": 2, + "maxLength": 32, + "type": "string" + }, + "password": { + "minLength": 1, + "maxLength": 72, + "type": "string" + }, + "consent": { + "type": "boolean" + }, + "email": { + "format": "email", + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "invite": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "gift_code_sku_id": { + "type": "string" + }, + "captcha_key": { + "type": "string" + } + }, + "required": [ + "consent", + "username" + ] + }, + "LoginSchema": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + }, + "undelete": { + "type": "boolean" + }, + "captcha_key": { + "type": "string" + }, + "login_source": { + "type": "string" + }, + "gift_code_sku_id": { + "type": "string" + } + }, + "required": [ + "login", + "password" + ] + }, + "MessageAcknowledgeSchema": { + "type": "object", + "properties": { + "manual": { + "type": "boolean" + }, + "mention_count": { + "type": "integer" + } + } + }, + "BulkDeleteSchema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "messages" + ] + }, + "ChannelPermissionOverwriteSchema": { + "type": "object", + "properties": { + "allow": { + "type": "number" + }, + "deny": { + "type": "number" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ChannelPermissionOverwriteType" + } + }, + "required": [ + "allow", + "deny", + "id", + "type" + ] + }, + "WebhookCreateSchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 80, + "type": "string" + }, + "avatar": { + "type": "string" + } + }, + "required": [ + "avatar", + "name" + ] + }, + "ChannelReorderSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "lock_permissions": { + "type": "boolean" + }, + "parent_id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "id" + ] + } + }, + "RolePositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id", + "position" + ] + } + }, + "VanityUrlSchema": { + "type": "object", + "properties": { + "code": { + "minLength": 1, + "maxLength": 20, + "type": "string" + } + } + }, + "VoiceStateUpdateSchema": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "suppress": { + "type": "boolean" + }, + "request_to_speak_timestamp": { + "type": "string", + "format": "date-time" + }, + "self_mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" + } + }, + "required": [ + "channel_id" + ] + }, + "UserProfileResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/UserPublic" + }, + "connected_accounts": { + "$ref": "#/components/schemas/PublicConnectedAccount" + }, + "premium_guild_since": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "connected_accounts", + "user" + ] + }, + "RelationshipPutSchema": { + "type": "object", + "properties": { + "type": { + "enum": [ + 1, + 2, + 3, + 4 + ], + "type": "number" + } + } + }, + "RelationshipPostSchema": { + "type": "object", + "properties": { + "discriminator": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "discriminator", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verifie": { + "type": "boolean" + } + }, + "required": [ + "name", + "type", + "verifie" + ] + } + }, + "requestBodies": {}, + "securitySchemes": { + "Token": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + }, + "links": {}, + "callbacks": {} + } +} \ No newline at end of file diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index a9c75df1..fcaa3124 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -7,7 +7,7 @@ import fs from "fs"; import Ajv from "ajv"; import addFormats from "ajv-formats"; import fetch from "node-fetch"; -import { User } from "@fosscord/util"; +import { Event, User, events } from "@fosscord/util"; const SchemaPath = join(__dirname, "..", "assets", "schemas.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); @@ -58,6 +58,12 @@ beforeAll(async (done) => { } }); +const emit = events.emit; +events.emit = (event: string | symbol, ...args: any[]) => { + events.emit("event", args[0]); + return emit(event, ...args); +}; + describe("Automatic unit tests with route description middleware", () => { const routes = getRouteDescriptions(); @@ -77,6 +83,23 @@ describe("Automatic unit tests with route description middleware", () => { } var body = ""; + let eventEmitted = Promise.resolve(); + + if (route.test.event) { + if (!Array.isArray(route.test.event)) route.test.event = [route.test.event]; + + eventEmitted = new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject, 1000); + const received = []; + + events.on("event", (event: Event) => { + if (!route.test.event.includes(event.event)) return; + + received.push(event.event); + if (received.length === route.test.event.length) resolve(); + }); + }); + } try { const response = await fetch(`http://localhost:3001/api${urlPath}`, { @@ -101,6 +124,12 @@ describe("Automatic unit tests with route description middleware", () => { return done(error); } + try { + await eventEmitted; + } catch (error) { + return done(new Error(`Event ${route.test.event} was not emitted`)); + } + return done(); }); }); diff --git a/util/src/util/Event.ts b/util/src/util/Event.ts index 765e5fc7..bf9547b1 100644 --- a/util/src/util/Event.ts +++ b/util/src/util/Event.ts @@ -2,7 +2,7 @@ import { Channel } from "amqplib"; import { RabbitMQ } from "./RabbitMQ"; import EventEmitter from "events"; import { EVENT, Event } from "../interfaces"; -const events = new EventEmitter(); +export const events = new EventEmitter(); export async function emitEvent(payload: Omit) { const id = (payload.channel_id || payload.user_id || payload.guild_id) as string; From e9c3f7ee1cb186f68d5c42add0552e4487d8b792 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:29:17 +0200 Subject: [PATCH 83/90] :sparkles: example value in documentation --- api/package.json | 5 ++--- ...ate_openapi_schema.js => generate_openapi.js} | 16 +++++++++++++--- ...enerate_body_schema.js => generate_schema.js} | 0 3 files changed, 15 insertions(+), 6 deletions(-) rename api/scripts/{generate_openapi_schema.js => generate_openapi.js} (93%) rename api/scripts/{generate_body_schema.js => generate_schema.js} (100%) diff --git a/api/package.json b/api/package.json index 53031071..a1d3b5b2 100644 --- a/api/package.json +++ b/api/package.json @@ -15,9 +15,8 @@ "dev": "tsnd --respawn src/start.ts", "patch": "npx patch-package", "postinstall": "npm run patch", - "generate:docs": "ts-node scripts/generate_openapi_schema.ts", - "generate:test": "ts-node scripts/generate_test_schema.ts", - "generate:schema": "ts-node scripts/generate_body_schema.ts" + "generate:docs": "node scripts/generate_openapi.ts", + "generate:schema": "node scripts/generate_schema.ts" }, "repository": { "type": "git", diff --git a/api/scripts/generate_openapi_schema.js b/api/scripts/generate_openapi.js similarity index 93% rename from api/scripts/generate_openapi_schema.js rename to api/scripts/generate_openapi.js index eb979f14..c9de9fa6 100644 --- a/api/scripts/generate_openapi_schema.js +++ b/api/scripts/generate_openapi.js @@ -81,6 +81,18 @@ function apiRoutes() { } if (route.test?.response) { const status = route.test.response.status || 200; + let schema = { + allOf: [ + { + $ref: `#/components/schemas/${route.test.response.body}` + }, + { + example: route.test.body + } + ] + }; + if (!route.test.body) schema = schema.allOf[0]; + obj.responses = { [status]: { ...(route.test.response.body @@ -88,9 +100,7 @@ function apiRoutes() { description: obj.responses[status].description || "", content: { "application/json": { - schema: { - $ref: `#/components/schemas/${route.test.response.body}` - } + schema: schema } } } diff --git a/api/scripts/generate_body_schema.js b/api/scripts/generate_schema.js similarity index 100% rename from api/scripts/generate_body_schema.js rename to api/scripts/generate_schema.js From c55c949ab983dfe1bda7b683e1dbadee0d6ffe2c Mon Sep 17 00:00:00 2001 From: Slappy826 Date: Wed, 22 Sep 2021 23:04:37 -0500 Subject: [PATCH 84/90] Update README.md fix spelling --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87f90055..feb2f324 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This repository contains: - [WebRTC Server](https://github.com/fosscord/fosscord-server/tree/master/webrtc) - [Admin Dashboard](https://github.com/fosscord/fosscord-server/tree/master/dashboard) -## [Ressources](https://docs.fosscord.com/resources/) +## [Resources](https://docs.fosscord.com/resources/) - [Contributing](https://docs.fosscord.com/contributing/server/) From 15dd4631b43c269ecabaec94b9e073333618a59a Mon Sep 17 00:00:00 2001 From: Chris Chrome Date: Thu, 23 Sep 2021 05:16:15 -0400 Subject: [PATCH 85/90] Use @yukikaze-bot/erlpack discord/erlpack doesnt seem to like installing under linux, this one however, doesnt even need to build :) --- gateway/package-lock.json | Bin 156266 -> 184819 bytes gateway/package.json | 1 + gateway/src/events/Connection.ts | 2 +- gateway/src/events/Message.ts | 2 +- gateway/src/util/Send.ts | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gateway/package-lock.json b/gateway/package-lock.json index ae6009dbd6bc7627f95c985add5825f9376262eb..6b30ee3f97895f5d004b798e66fb446c564432a3 100644 GIT binary patch delta 11917 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 Date: Thu, 23 Sep 2021 07:58:32 -0400 Subject: [PATCH 86/90] Ok, so it DOES build Still, it builds under linux, so i'm happy with it lol --- gateway/package-lock.json | Bin 184819 -> 184819 bytes gateway/package.json | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gateway/package-lock.json b/gateway/package-lock.json index 6b30ee3f97895f5d004b798e66fb446c564432a3..caa5ee78cc5245a81fbea3b1362aa94cbfccd1f2 100644 GIT binary patch delta 40 ycmV+@0N4NXqYLw+3$OwKvnc`pT$k|J0SA);0U(q0NgIPhUWY?o0k=b50 Date: Thu, 23 Sep 2021 16:03:50 +0200 Subject: [PATCH 87/90] :bug: fix (pre) install scripts --- api/package.json | 4 ++-- bundle/package-lock.json | Bin 99097 -> 99567 bytes bundle/package.json | 2 +- cdn/package-lock.json | Bin 361915 -> 361944 bytes cdn/package.json | 10 +++++----- gateway/package-lock.json | Bin 163664 -> 163693 bytes gateway/package.json | 2 +- util/package.json | 6 +++--- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/api/package.json b/api/package.json index a1d3b5b2..580cfa2a 100644 --- a/api/package.json +++ b/api/package.json @@ -5,7 +5,7 @@ "main": "dist/Server.js", "types": "dist/Server.d.ts", "scripts": { - "prepare": "ts-patch install -s", + "prepare": "", "test:only": "jest --coverage --verbose --forceExit ./tests", "test": "npm run build && npm run test:only", "test:watch": "jest --watch", @@ -13,7 +13,7 @@ "build": "npx tsc -b .", "build-docker": "tsc -p tsconfig-docker.json", "dev": "tsnd --respawn src/start.ts", - "patch": "npx patch-package", + "patch": "ts-patch install -s && npx patch-package", "postinstall": "npm run patch", "generate:docs": "node scripts/generate_openapi.ts", "generate:schema": "node scripts/generate_schema.ts" diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 0ed6b03a887e8da24430c92adb434de7e07cee91..aaa0c1eee885087de403cbf3094da6476dbcd8ee 100644 GIT binary patch delta 321 zcmbQ)#`eCGZG%5Mv!$NVkWRgiOF40Y`C`e5%(alXQE>28ORkBi0iZj$R z&@-MK_#|es|INzD=brOz4&atxw8_Gw+(=1>ixUWxz>11W^GY&vA%+>5>lx`8PEO$Y zKly;D#^x;Ebe72_EJl-e^9W3SA{YS@oSen$wz*emDict`Y;war!^wS*1vY1i&OA)e sg`53v3Nk}I)n`qK1NXcro9rOWzB%=-63Bu2o7*3LU delta 126 zcmaFg$u_f%ZG%5MvxT1N#Hm_m7$TT_NrRL;>TXQBKkmTE3!!5x$`3BGT$)du6 zn{D|NSte`n3Qt}j6tP)U_!-k==NHzKbp?1g7m3Y04AQqb;g$(A)U>|MnfIo#Ox7&r cpX~pTf3wfyOU#qgguhSb;kDXa_o8qP0FMsd7#x3q3yY~8}tx`nxQ3(M9mtn94-=c5gq delta 27 jcmcc7Ew;N`Y{JUMW39&+TaPid9%F7j# Date: Thu, 23 Sep 2021 16:30:28 +0200 Subject: [PATCH 88/90] :bug: fix install script --- api/package.json | 1 - bundle/package.json | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/package.json b/api/package.json index 580cfa2a..cc47a45a 100644 --- a/api/package.json +++ b/api/package.json @@ -5,7 +5,6 @@ "main": "dist/Server.js", "types": "dist/Server.d.ts", "scripts": { - "prepare": "", "test:only": "jest --coverage --verbose --forceExit ./tests", "test": "npm run build && npm run test:only", "test:watch": "jest --watch", diff --git a/bundle/package.json b/bundle/package.json index c2b6851e..dfe892a4 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -4,9 +4,9 @@ "description": "", "main": "src/start.js", "scripts": { - "prepare": "ts-patch install -s", - "preinstall": "cd ../util && npm --production=false i && cd ../api && npm --production=false i && cd ../cdn && npm --production=false i && cd ../gateway && npm --production=false i", + "prepare": "cd ../util && npm --production=false i && cd ../api && npm --production=false i && cd ../cdn && npm --production=false i && cd ../gateway && npm --production=false i", "build": "npm run build:util && npm run build:api && npm run build:cdn && npm run build:gateway && npm run build:bundle", + "postinstall": "ts-patch install -s", "build:bundle": "npx tsc -b .", "build:util": "cd ../util/ && npm run build", "build:api": "cd ../api/ && npm run build", From 77ac8a96a75f3561c82ab87627812a1e153741f4 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 23 Sep 2021 16:58:07 +0200 Subject: [PATCH 89/90] :bug: prepare/postinstall only works for packages not local npm install --- bundle/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/package.json b/bundle/package.json index dfe892a4..722d2255 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -4,7 +4,7 @@ "description": "", "main": "src/start.js", "scripts": { - "prepare": "cd ../util && npm --production=false i && cd ../api && npm --production=false i && cd ../cdn && npm --production=false i && cd ../gateway && npm --production=false i", + "setup": "cd ../util && npm --production=false i && cd ../api && npm --production=false i && cd ../cdn && npm --production=false i && cd ../gateway && npm --production=false i && npm install && npm run start", "build": "npm run build:util && npm run build:api && npm run build:cdn && npm run build:gateway && npm run build:bundle", "postinstall": "ts-patch install -s", "build:bundle": "npx tsc -b .", From 955e86f77b2faf97f83568556f214e8825e59de4 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 23 Sep 2021 17:29:00 +0200 Subject: [PATCH 90/90] :art: remove start from setup script --- bundle/package.json | 2 +- gateway/package-lock.json | Bin 417913 -> 514788 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/package.json b/bundle/package.json index 722d2255..fa6fe669 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -4,7 +4,7 @@ "description": "", "main": "src/start.js", "scripts": { - "setup": "cd ../util && npm --production=false i && cd ../api && npm --production=false i && cd ../cdn && npm --production=false i && cd ../gateway && npm --production=false i && npm install && npm run start", + "setup": "cd ../util && npm --production=false i && cd ../api && npm --production=false i && cd ../cdn && npm --production=false i && cd ../gateway && npm --production=false i && npm install", "build": "npm run build:util && npm run build:api && npm run build:cdn && npm run build:gateway && npm run build:bundle", "postinstall": "ts-patch install -s", "build:bundle": "npx tsc -b .", diff --git a/gateway/package-lock.json b/gateway/package-lock.json index fbd44023eac837e3ad0774550919b50124a76f7f..8ea277fe2adc087b9f59083ca68aee2291ce30bf 100644 GIT binary patch delta 20542 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 C1ptr$ delta 11580 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