diff --git a/src/api/routes/users/#id/consents/#service_id/index.ts b/src/api/routes/users/#id/consents/#service_id/index.ts
index 5f37da6e2..22be76941 100644
--- a/src/api/routes/users/#id/consents/#service_id/index.ts
+++ b/src/api/routes/users/#id/consents/#service_id/index.ts
@@ -5,24 +5,23 @@ import { UserConsent } from "@spacebar/util";
const router: Router = Router();
router.delete(
- "/",
- route({
- right: "MANAGE_USERS",
- summary:
- "Revoke consent for a service for the specified user (admin only)",
- responses: { 204: { body: "null" } },
- }),
- async (req: Request, res: Response) => {
- const user_id = req.params.id;
- const service_id = req.params.service_id;
- const existing = await UserConsent.findOne({
- where: { user_id, service_id },
- });
- if (existing) {
- await existing.remove();
- }
- return res.status(204).send();
- },
+ "/",
+ route({
+ right: "MANAGE_USERS",
+ summary: "Revoke consent for a service for the specified user (admin only)",
+ responses: { 204: { body: "null" } },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.params.id;
+ const service_id = req.params.service_id;
+ const existing = await UserConsent.findOne({
+ where: { user_id, service_id },
+ });
+ if (existing) {
+ await existing.remove();
+ }
+ return res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/users/#id/consents/index.ts b/src/api/routes/users/#id/consents/index.ts
index a36047746..2a5fd1d14 100644
--- a/src/api/routes/users/#id/consents/index.ts
+++ b/src/api/routes/users/#id/consents/index.ts
@@ -5,44 +5,44 @@ import { UserConsent } from "@spacebar/util";
const router: Router = Router();
router.get(
- "/",
- route({
- right: "MANAGE_USERS",
- summary: "List consents for a specified user (admin only)",
- responses: {
- 200: { body: "any" },
- 403: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const target_user_id = req.params.id;
- const consents = await UserConsent.find({
- where: { user_id: target_user_id },
- });
- res.json(
- consents.map((c) => ({
- service_id: c.service_id,
- consented_at: c.created_at,
- })),
- );
- },
+ "/",
+ route({
+ right: "MANAGE_USERS",
+ summary: "List consents for a specified user (admin only)",
+ responses: {
+ 200: { body: "any" },
+ 403: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const target_user_id = req.params.id;
+ const consents = await UserConsent.find({
+ where: { user_id: target_user_id },
+ });
+ res.json(
+ consents.map((c) => ({
+ service_id: c.service_id,
+ consented_at: c.created_at,
+ })),
+ );
+ },
);
router.delete(
- "/",
- route({
- right: "OPERATOR",
- summary: "Revoke all consents for a specified user (operator only)",
- responses: {
- 204: { body: "null" },
- 403: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const target_user_id = req.params.id;
- await UserConsent.delete({ user_id: target_user_id });
- return res.status(204).send();
- },
+ "/",
+ route({
+ right: "OPERATOR",
+ summary: "Revoke all consents for a specified user (operator only)",
+ responses: {
+ 204: { body: "null" },
+ 403: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const target_user_id = req.params.id;
+ await UserConsent.delete({ user_id: target_user_id });
+ return res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/consent-grants/index.ts b/src/api/routes/users/@me/consent-grants/index.ts
new file mode 100644
index 000000000..1c8339d4e
--- /dev/null
+++ b/src/api/routes/users/@me/consent-grants/index.ts
@@ -0,0 +1,335 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2023 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+import { route } from "@spacebar/api";
+import { Request, Response, Router } from "express";
+import { ConsentGrant, ConsentGrantStatus, ConsentType, UserConsent, ConsentStatus, DiscordApiErrors, User } from "@spacebar/util";
+import { FindOptionsWhere } from "typeorm";
+
+const router: Router = Router();
+
+router.get(
+ "/",
+ route({
+ summary: "List consent grant requests",
+ description: "Returns all consent grant requests for the authenticated user (both as grantor and requester). Based on GNAP (RFC 9635) grant negotiation principles.",
+ query: {
+ status: {
+ type: "string",
+ required: false,
+ description: "Filter by grant status",
+ values: Object.values(ConsentGrantStatus),
+ },
+ consent_type: {
+ type: "string",
+ required: false,
+ description: "Filter by consent type",
+ values: Object.values(ConsentType),
+ },
+ as_requester: {
+ type: "boolean",
+ required: false,
+ description: "If true, list grants where user is the requester; if false, list grants where user is the grantor",
+ },
+ },
+ responses: {
+ 200: { body: "ConsentGrantListResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const { status, consent_type, as_requester } = req.query;
+
+ const where: FindOptionsWhere = {};
+
+ if (as_requester === "true") {
+ where.requester_id = user_id;
+ } else {
+ where.user_id = user_id;
+ }
+
+ if (status) {
+ where.status = status as ConsentGrantStatus;
+ }
+ if (consent_type) {
+ where.consent_type = consent_type as ConsentType;
+ }
+
+ const grants = await ConsentGrant.find({ where });
+
+ res.json({
+ grants: grants.map((g) => g.toJSON()),
+ });
+ },
+);
+
+router.post(
+ "/",
+ route({
+ summary: "Request a consent grant (GNAP-like)",
+ description:
+ "Initiates a consent grant request to another user. Based on GNAP (RFC 9635) grant negotiation. The target user will receive the request and can approve, deny, or negotiate.",
+ requestBody: "ConsentGrantRequestSchema",
+ responses: {
+ 200: { body: "ConsentGrantResponse" },
+ 404: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const requester_id = req.user_id!;
+ const { user_id, consent_type = ConsentType.CUSTOM, service_id, item_id, requested_access, interaction_url, expires_at, extra_data } = req.body;
+
+ const targetUser = await User.findOne({ where: { id: user_id } });
+ if (!targetUser) {
+ throw DiscordApiErrors.UNKNOWN_USER;
+ }
+
+ const grant = ConsentGrant.create({
+ user_id,
+ requester_id,
+ consent_type,
+ service_id,
+ item_id,
+ status: ConsentGrantStatus.PENDING,
+ requested_access,
+ interaction_url,
+ expires_at: expires_at ? new Date(expires_at) : undefined,
+ extra_data,
+ });
+
+ grant.generateContinueToken();
+ await grant.save();
+
+ res.json(grant.toJSON());
+ },
+);
+
+router.get(
+ "/:grant_id",
+ route({
+ summary: "Get consent grant details",
+ description: "Returns details of a specific consent grant request. Only accessible by the grantor or requester.",
+ responses: {
+ 200: { body: "ConsentGrantResponse" },
+ 404: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const grant_id = req.params.grant_id;
+
+ const grant = await ConsentGrant.findOne({
+ where: [
+ { id: grant_id, user_id },
+ { id: grant_id, requester_id: user_id },
+ ],
+ });
+
+ if (!grant) {
+ throw DiscordApiErrors.UNKNOWN_CONSENT_GRANT;
+ }
+
+ res.json(grant.toJSON());
+ },
+);
+
+router.post(
+ "/:grant_id/approve",
+ route({
+ summary: "Approve a consent grant request",
+ description: "Approves a pending consent grant request. Creates the corresponding UserConsent record. Only the target user (grantor) can approve.",
+ requestBody: "ConsentGrantApproveSchema",
+ responses: {
+ 200: { body: "ConsentGrantResponse" },
+ 404: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const grant_id = req.params.grant_id;
+ const { granted_access, provisional, expires_at, extra_data } = req.body;
+
+ const grant = await ConsentGrant.findOne({
+ where: { id: grant_id, user_id, status: ConsentGrantStatus.PENDING },
+ });
+
+ if (!grant) {
+ throw DiscordApiErrors.UNKNOWN_CONSENT_GRANT;
+ }
+
+ grant.status = ConsentGrantStatus.APPROVED;
+ grant.responded_at = new Date();
+ grant.granted_access = granted_access || grant.requested_access;
+ grant.generateAccessToken();
+
+ if (extra_data) {
+ grant.extra_data = { ...grant.extra_data, ...extra_data };
+ }
+
+ await grant.save();
+
+ const consent = UserConsent.create({
+ user_id,
+ service_id: grant.service_id || "gnap_grant",
+ consent_type: grant.consent_type,
+ item_id: grant.item_id,
+ target_user_id: grant.requester_id,
+ status: provisional ? ConsentStatus.PROVISIONAL : ConsentStatus.GRANTED,
+ granted_at: new Date(),
+ expires_at: expires_at ? new Date(expires_at) : grant.expires_at || undefined,
+ extra_data: {
+ grant_id: grant.id,
+ granted_access: grant.granted_access,
+ },
+ });
+ await consent.save();
+
+ res.json(grant.toJSON());
+ },
+);
+
+router.post(
+ "/:grant_id/deny",
+ route({
+ summary: "Deny a consent grant request",
+ description: "Denies a pending consent grant request. Only the target user (grantor) can deny.",
+ responses: {
+ 200: { body: "ConsentGrantResponse" },
+ 404: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const grant_id = req.params.grant_id;
+
+ const grant = await ConsentGrant.findOne({
+ where: { id: grant_id, user_id, status: ConsentGrantStatus.PENDING },
+ });
+
+ if (!grant) {
+ throw DiscordApiErrors.UNKNOWN_CONSENT_GRANT;
+ }
+
+ grant.status = ConsentGrantStatus.DENIED;
+ grant.responded_at = new Date();
+ await grant.save();
+
+ res.json(grant.toJSON());
+ },
+);
+
+router.post(
+ "/:grant_id/continue",
+ route({
+ summary: "Continue grant negotiation (GNAP-like)",
+ description: "Continues the grant negotiation process using the continuation token. Used for multi-step negotiation flows per GNAP (RFC 9635).",
+ requestBody: "ConsentGrantContinueSchema",
+ responses: {
+ 200: { body: "ConsentGrantResponse" },
+ 404: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const grant_id = req.params.grant_id;
+ const { continue_token, updated_access, interaction_ref } = req.body;
+
+ const grant = await ConsentGrant.findOne({
+ where: [
+ { id: grant_id, user_id },
+ { id: grant_id, requester_id: user_id },
+ ],
+ });
+
+ if (!grant) {
+ throw DiscordApiErrors.UNKNOWN_CONSENT_GRANT;
+ }
+
+ if (grant.continue_token !== continue_token) {
+ throw DiscordApiErrors.UNKNOWN_CONSENT_GRANT;
+ }
+
+ if (updated_access) {
+ grant.requested_access = updated_access;
+ }
+
+ if (interaction_ref) {
+ grant.extra_data = { ...grant.extra_data, interaction_ref };
+ }
+
+ grant.generateContinueToken();
+ await grant.save();
+
+ res.json(grant.toJSON());
+ },
+);
+
+router.delete(
+ "/:grant_id",
+ route({
+ summary: "Cancel/revoke a consent grant",
+ description: "Cancels a pending grant request (if requester) or revokes an approved grant (if grantor). Per GDPR Article 7(3), revocation is as easy as granting.",
+ responses: {
+ 204: { body: "null" },
+ 404: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const grant_id = req.params.grant_id;
+
+ const grant = await ConsentGrant.findOne({
+ where: [
+ { id: grant_id, user_id },
+ { id: grant_id, requester_id: user_id },
+ ],
+ });
+
+ if (!grant) {
+ throw DiscordApiErrors.UNKNOWN_CONSENT_GRANT;
+ }
+
+ if (grant.user_id === user_id) {
+ if (grant.status === ConsentGrantStatus.APPROVED) {
+ const consent = await UserConsent.findOne({
+ where: {
+ user_id,
+ target_user_id: grant.requester_id,
+ consent_type: grant.consent_type,
+ item_id: grant.item_id,
+ },
+ });
+ if (consent) {
+ consent.status = ConsentStatus.RETRACTED;
+ consent.retracted_at = new Date();
+ await consent.save();
+ }
+ }
+ grant.status = ConsentGrantStatus.DENIED;
+ } else {
+ grant.status = ConsentGrantStatus.EXPIRED;
+ }
+
+ grant.responded_at = new Date();
+ await grant.save();
+
+ res.status(204).send();
+ },
+);
+
+export default router;
diff --git a/src/api/routes/users/@me/consents/#service_id/index.ts b/src/api/routes/users/@me/consents/#service_id/index.ts
index f2937b4dc..b1f04ceb2 100644
--- a/src/api/routes/users/@me/consents/#service_id/index.ts
+++ b/src/api/routes/users/@me/consents/#service_id/index.ts
@@ -1,46 +1,191 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2023 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
-import { UserConsent } from "@spacebar/util";
+import { UserConsent, ConsentType, ConsentStatus, DiscordApiErrors } from "@spacebar/util";
const router: Router = Router();
+router.get(
+ "/",
+ route({
+ summary: "Get consent details for a specific service",
+ description:
+ "Returns the consent record for a specific service for the authenticated user. For HB 805 pairwise consents, use item_id and target_user_id to identify specific consent records.",
+ query: {
+ consent_type: {
+ type: "string",
+ required: false,
+ description: "Filter by consent type",
+ values: Object.values(ConsentType),
+ },
+ item_id: {
+ type: "string",
+ required: false,
+ description: "Filter by item ID (for per-item consents)",
+ },
+ target_user_id: {
+ type: "string",
+ required: false,
+ description: "Filter by target user ID (for HB 805 pairwise consents)",
+ },
+ },
+ responses: {
+ 200: { body: "UserConsentResponse" },
+ 404: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const service_id = req.params.service_id;
+ const consent_type = (req.query.consent_type as ConsentType) || ConsentType.CUSTOM;
+ const item_id = req.query.item_id as string | undefined;
+ const target_user_id = req.query.target_user_id as string | undefined;
+
+ const consent = await UserConsent.findOne({
+ where: {
+ user_id,
+ service_id,
+ consent_type,
+ ...(item_id && { item_id }),
+ ...(target_user_id && { target_user_id }),
+ },
+ });
+
+ if (!consent) {
+ throw DiscordApiErrors.UNKNOWN_CONSENT;
+ }
+
+ res.json(consent.toJSON());
+ },
+);
+
router.put(
- "/",
- route({
- summary: "Grant consent for a service for the current user",
- responses: { 204: { body: "null" } },
- }),
- async (req: Request, res: Response) => {
- const user_id = req.user_id!;
- const service_id = req.params.service_id;
- const existing = await UserConsent.findOne({
- where: { user_id, service_id },
- });
- if (!existing) {
- const uc = UserConsent.create({ user_id, service_id });
- await uc.save();
- }
- return res.status(204).send();
- },
+ "/",
+ route({
+ summary: "Grant consent for a service",
+ description:
+ "Grants consent for a service. Supports GDPR-compliant consent with basis documents for off-platform consents. For HB 805 pairwise consents (O(n²) where n is number of persons), use target_user_id to specify who receives consent to view/share. Based on GNAP (RFC 9635) grant negotiation principles.",
+ requestBody: "UserConsentGrantSchema",
+ responses: {
+ 200: { body: "UserConsentResponse" },
+ 204: { body: "null" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const service_id = req.params.service_id;
+ const { consent_type = ConsentType.CUSTOM, item_id, target_user_id, basis_document_url, basis_document_hash, expires_at, provisional, extra_data } = req.body;
+
+ const existing = await UserConsent.findOne({
+ where: {
+ user_id,
+ service_id,
+ consent_type,
+ ...(item_id && { item_id }),
+ ...(target_user_id && { target_user_id }),
+ },
+ });
+
+ if (existing) {
+ existing.status = provisional ? ConsentStatus.PROVISIONAL : ConsentStatus.GRANTED;
+ existing.granted_at = new Date();
+ existing.retracted_at = undefined;
+ existing.basis_document_url = basis_document_url;
+ existing.basis_document_hash = basis_document_hash;
+ existing.expires_at = expires_at ? new Date(expires_at) : undefined;
+ existing.item_id = item_id;
+ existing.target_user_id = target_user_id;
+ existing.extra_data = extra_data;
+ await existing.save();
+ return res.json(existing.toJSON());
+ }
+
+ const consent = UserConsent.create({
+ user_id,
+ service_id,
+ consent_type,
+ item_id,
+ target_user_id,
+ status: provisional ? ConsentStatus.PROVISIONAL : ConsentStatus.GRANTED,
+ basis_document_url,
+ basis_document_hash,
+ granted_at: new Date(),
+ expires_at: expires_at ? new Date(expires_at) : undefined,
+ extra_data,
+ });
+ await consent.save();
+ return res.json(consent.toJSON());
+ },
);
router.delete(
- "/",
- route({
- summary: "Revoke consent for a service for the current user",
- responses: { 204: { body: "null" } },
- }),
- async (req: Request, res: Response) => {
- const user_id = req.user_id!;
- const service_id = req.params.service_id;
- const existing = await UserConsent.findOne({
- where: { user_id, service_id },
- });
- if (existing) {
- await existing.remove();
- }
- return res.status(204).send();
- },
+ "/",
+ route({
+ summary: "Revoke consent for a service (GDPR-compliant)",
+ description:
+ "Revokes consent for a service. Per GDPR Article 7(3), withdrawal of consent is as easy as giving consent. For HB 805 pairwise consents, specify item_id and target_user_id. The consent record is retained with RETRACTED status for audit purposes.",
+ query: {
+ consent_type: {
+ type: "string",
+ required: false,
+ description: "Consent type to revoke",
+ values: Object.values(ConsentType),
+ },
+ item_id: {
+ type: "string",
+ required: false,
+ description: "Item ID for per-item consent revocation",
+ },
+ target_user_id: {
+ type: "string",
+ required: false,
+ description: "Target user ID for HB 805 pairwise consent revocation",
+ },
+ },
+ responses: { 204: { body: "null" } },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const service_id = req.params.service_id;
+ const consent_type = (req.query.consent_type as ConsentType) || ConsentType.CUSTOM;
+ const item_id = req.query.item_id as string | undefined;
+ const target_user_id = req.query.target_user_id as string | undefined;
+
+ const existing = await UserConsent.findOne({
+ where: {
+ user_id,
+ service_id,
+ consent_type,
+ ...(item_id && { item_id }),
+ ...(target_user_id && { target_user_id }),
+ },
+ });
+
+ if (existing) {
+ existing.status = ConsentStatus.RETRACTED;
+ existing.retracted_at = new Date();
+ await existing.save();
+ }
+
+ return res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/consents/index.ts b/src/api/routes/users/@me/consents/index.ts
index 76a5617d3..e6c303296 100644
--- a/src/api/routes/users/@me/consents/index.ts
+++ b/src/api/routes/users/@me/consents/index.ts
@@ -1,27 +1,95 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2023 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
-import { UserConsent } from "@spacebar/util";
+import { UserConsent, ConsentType, ConsentStatus } from "@spacebar/util";
+import { FindOptionsWhere } from "typeorm";
const router: Router = Router();
router.get(
- "/",
- route({
- summary: "List consents for the current user",
- responses: {
- 200: { body: "any" },
- },
- }),
- async (req: Request, res: Response) => {
- const user_id = req.user_id!;
- const consents = await UserConsent.find({ where: { user_id } });
- res.json(
- consents.map((c) => ({
- service_id: c.service_id,
- consented_at: c.created_at,
- })),
- );
- },
+ "/",
+ route({
+ summary: "List consents for the current user",
+ description:
+ "Returns all consents for the authenticated user. Supports filtering by consent_type, status, service_id, item_id, and target_user_id. For HB 805 pairwise consents (O(n²) per item), use item_id and target_user_id filters.",
+ query: {
+ consent_type: {
+ type: "string",
+ required: false,
+ description: "Filter by consent type",
+ values: Object.values(ConsentType),
+ },
+ status: {
+ type: "string",
+ required: false,
+ description: "Filter by consent status",
+ values: Object.values(ConsentStatus),
+ },
+ service_id: {
+ type: "string",
+ required: false,
+ description: "Filter by service ID",
+ },
+ item_id: {
+ type: "string",
+ required: false,
+ description: "Filter by item ID (for per-item consents)",
+ },
+ target_user_id: {
+ type: "string",
+ required: false,
+ description: "Filter by target user ID (for HB 805 pairwise consents)",
+ },
+ },
+ responses: {
+ 200: { body: "UserConsentListResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user_id = req.user_id!;
+ const { consent_type, status, service_id, item_id, target_user_id } = req.query;
+
+ const where: FindOptionsWhere = { user_id };
+
+ if (consent_type) {
+ where.consent_type = consent_type as ConsentType;
+ }
+ if (status) {
+ where.status = status as ConsentStatus;
+ }
+ if (service_id) {
+ where.service_id = service_id as string;
+ }
+ if (item_id) {
+ where.item_id = item_id as string;
+ }
+ if (target_user_id) {
+ where.target_user_id = target_user_id as string;
+ }
+
+ const consents = await UserConsent.find({ where });
+
+ res.json({
+ consents: consents.map((c) => c.toJSON()),
+ });
+ },
);
export default router;
diff --git a/src/util/entities/ConsentGrant.ts b/src/util/entities/ConsentGrant.ts
new file mode 100644
index 000000000..6031c19c9
--- /dev/null
+++ b/src/util/entities/ConsentGrant.ts
@@ -0,0 +1,130 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2023 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
+import { BaseClass } from "./BaseClass";
+import { User } from "./User";
+import { ConsentType } from "./UserConsent";
+import { Snowflake } from "../util/Snowflake";
+
+export enum ConsentGrantStatus {
+ PENDING = "pending",
+ APPROVED = "approved",
+ DENIED = "denied",
+ EXPIRED = "expired",
+}
+
+@Entity({ name: "consent_grants" })
+@Index(["user_id", "requester_id", "service_id", "consent_type"])
+export class ConsentGrant extends BaseClass {
+ @Column()
+ @Index()
+ user_id: string;
+
+ @Column()
+ @Index()
+ requester_id: string;
+
+ @Column({ type: "varchar", default: ConsentType.CUSTOM })
+ consent_type: ConsentType;
+
+ @Column({ nullable: true })
+ service_id?: string;
+
+ @Column({ nullable: true })
+ item_id?: string;
+
+ @Column({ type: "varchar", default: ConsentGrantStatus.PENDING })
+ status: ConsentGrantStatus;
+
+ @Column()
+ requested_at: Date = new Date();
+
+ @Column({ nullable: true })
+ responded_at?: Date;
+
+ @Column({ nullable: true })
+ expires_at?: Date;
+
+ @Column({ nullable: true })
+ interaction_url?: string;
+
+ @Column({ nullable: true })
+ continue_token?: string;
+
+ @Column({ nullable: true })
+ access_token?: string;
+
+ @Column({ type: "simple-json", nullable: true })
+ requested_access?: {
+ type: string;
+ actions?: string[];
+ locations?: string[];
+ datatypes?: string[];
+ }[];
+
+ @Column({ type: "simple-json", nullable: true })
+ granted_access?: {
+ type: string;
+ actions?: string[];
+ locations?: string[];
+ datatypes?: string[];
+ }[];
+
+ @Column({ type: "simple-json", nullable: true })
+ extra_data?: Record;
+
+ @ManyToOne(() => User, { onDelete: "CASCADE" })
+ @JoinColumn({ name: "user_id" })
+ user?: User;
+
+ @ManyToOne(() => User, { onDelete: "CASCADE" })
+ @JoinColumn({ name: "requester_id" })
+ requester?: User;
+
+ generateContinueToken(): string {
+ this.continue_token = Snowflake.generate();
+ return this.continue_token;
+ }
+
+ generateAccessToken(): string {
+ this.access_token = Snowflake.generate();
+ return this.access_token;
+ }
+
+ toJSON() {
+ return {
+ id: this.id,
+ user_id: this.user_id,
+ requester_id: this.requester_id,
+ consent_type: this.consent_type,
+ service_id: this.service_id,
+ item_id: this.item_id,
+ status: this.status,
+ requested_at: this.requested_at,
+ responded_at: this.responded_at,
+ expires_at: this.expires_at,
+ interaction_url: this.interaction_url,
+ continue_token: this.continue_token,
+ access_token: this.access_token,
+ requested_access: this.requested_access,
+ granted_access: this.granted_access,
+ extra_data: this.extra_data,
+ };
+ }
+}
diff --git a/src/util/entities/UserConsent.ts b/src/util/entities/UserConsent.ts
index cbf4197cc..679ee8ed1 100644
--- a/src/util/entities/UserConsent.ts
+++ b/src/util/entities/UserConsent.ts
@@ -1,20 +1,110 @@
-import { Column, Entity, Index, ManyToOne } from "typeorm";
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2023 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
-import { dbEngine } from "../util/Database";
-@Entity({ name: "user_consents", engine: dbEngine })
-@Index(["user_id", "service_id"], { unique: true })
+export enum ConsentType {
+ MEDIA_ITEM = "media_item",
+ MESSAGE_ITEM = "message_item",
+ EXTERNAL_DATA_PROCESSING = "external_data_processing",
+ BOTS_AI = "bots_ai",
+ CUSTOM = "custom",
+}
+
+export enum ConsentStatus {
+ PENDING = "pending",
+ GRANTED = "granted",
+ PROVISIONAL = "provisional",
+ RETRACTED = "retracted",
+}
+
+@Entity({ name: "user_consents" })
+@Index(["user_id", "service_id", "consent_type", "item_id", "target_user_id"], {
+ unique: true,
+})
export class UserConsent extends BaseClass {
- @Column()
- user_id: string;
+ @Column()
+ @Index()
+ user_id: string;
+
+ @Column()
+ service_id: string;
+
+ @Column({ type: "varchar", default: ConsentType.CUSTOM })
+ consent_type: ConsentType;
+
+ @Column({ nullable: true })
+ @Index()
+ item_id?: string;
+
+ @Column({ nullable: true })
+ @Index()
+ target_user_id?: string;
+
+ @Column({ type: "varchar", default: ConsentStatus.GRANTED })
+ status: ConsentStatus;
+
+ @Column({ nullable: true })
+ basis_document_url?: string;
+
+ @Column({ nullable: true })
+ basis_document_hash?: string;
+
+ @Column({ nullable: true })
+ granted_at?: Date;
+
+ @Column({ nullable: true })
+ retracted_at?: Date;
+
+ @Column({ nullable: true })
+ expires_at?: Date;
+
+ @Column({ type: "simple-json", nullable: true })
+ extra_data?: Record;
+
+ @Column()
+ created_at: Date = new Date();
- @Column()
- service_id: string;
+ @ManyToOne(() => User, (user) => user.id, { onDelete: "CASCADE" })
+ @JoinColumn({ name: "user_id" })
+ user?: User;
- @Column()
- created_at: Date = new Date();
+ @ManyToOne(() => User, { onDelete: "CASCADE" })
+ @JoinColumn({ name: "target_user_id" })
+ target_user?: User;
- @ManyToOne(() => User, (user) => user.id, { onDelete: "CASCADE" })
- user?: User;
+ toJSON() {
+ return {
+ id: this.id,
+ service_id: this.service_id,
+ consent_type: this.consent_type,
+ item_id: this.item_id,
+ target_user_id: this.target_user_id,
+ status: this.status,
+ basis_document_url: this.basis_document_url,
+ basis_document_hash: this.basis_document_hash,
+ granted_at: this.granted_at,
+ retracted_at: this.retracted_at,
+ expires_at: this.expires_at,
+ extra_data: this.extra_data,
+ created_at: this.created_at,
+ };
+ }
}
diff --git a/src/util/entities/index.ts b/src/util/entities/index.ts
index 63e20a3a8..1ef24525a 100644
--- a/src/util/entities/index.ts
+++ b/src/util/entities/index.ts
@@ -63,3 +63,4 @@ export * from "./ValidRegistrationTokens";
export * from "./VoiceState";
export * from "./Webhook";
export * from "./UserConsent";
+export * from "./ConsentGrant";
diff --git a/src/util/migration/postgres/1770139822000-ConsentsAPI.ts b/src/util/migration/postgres/1770139822000-ConsentsAPI.ts
new file mode 100644
index 000000000..cd1e2639a
--- /dev/null
+++ b/src/util/migration/postgres/1770139822000-ConsentsAPI.ts
@@ -0,0 +1,142 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2023 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class ConsentsAPI1770139822000 implements MigrationInterface {
+ name = "ConsentsAPI1770139822000";
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ ALTER TABLE "user_consents"
+ ADD COLUMN IF NOT EXISTS "consent_type" character varying DEFAULT 'custom',
+ ADD COLUMN IF NOT EXISTS "item_id" character varying,
+ ADD COLUMN IF NOT EXISTS "target_user_id" character varying,
+ ADD COLUMN IF NOT EXISTS "status" character varying DEFAULT 'granted',
+ ADD COLUMN IF NOT EXISTS "basis_document_url" character varying,
+ ADD COLUMN IF NOT EXISTS "basis_document_hash" character varying,
+ ADD COLUMN IF NOT EXISTS "granted_at" timestamp,
+ ADD COLUMN IF NOT EXISTS "retracted_at" timestamp,
+ ADD COLUMN IF NOT EXISTS "expires_at" timestamp,
+ ADD COLUMN IF NOT EXISTS "extra_data" text
+ `);
+
+ await queryRunner.query(`
+ DROP INDEX IF EXISTS "IDX_user_consents_user_id_service_id"
+ `);
+
+ await queryRunner.query(`
+ CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_consents_user_service_type_item_target"
+ ON "user_consents" ("user_id", "service_id", "consent_type", "item_id", "target_user_id")
+ `);
+
+ await queryRunner.query(`
+ CREATE INDEX IF NOT EXISTS "IDX_user_consents_item_id" ON "user_consents" ("item_id")
+ `);
+
+ await queryRunner.query(`
+ CREATE INDEX IF NOT EXISTS "IDX_user_consents_target_user_id" ON "user_consents" ("target_user_id")
+ `);
+
+ await queryRunner.query(`
+ ALTER TABLE "user_consents"
+ ADD CONSTRAINT "FK_user_consents_target_user_id"
+ FOREIGN KEY ("target_user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION
+ `);
+
+ await queryRunner.query(`
+ CREATE TABLE IF NOT EXISTS "consent_grants" (
+ "id" character varying NOT NULL,
+ "user_id" character varying NOT NULL,
+ "requester_id" character varying NOT NULL,
+ "consent_type" character varying DEFAULT 'custom',
+ "service_id" character varying,
+ "item_id" character varying,
+ "status" character varying DEFAULT 'pending',
+ "requested_at" timestamp NOT NULL DEFAULT now(),
+ "responded_at" timestamp,
+ "expires_at" timestamp,
+ "interaction_url" character varying,
+ "continue_token" character varying,
+ "access_token" character varying,
+ "requested_access" text,
+ "granted_access" text,
+ "extra_data" text,
+ CONSTRAINT "PK_consent_grants" PRIMARY KEY ("id")
+ )
+ `);
+
+ await queryRunner.query(`
+ CREATE INDEX IF NOT EXISTS "IDX_consent_grants_user_id" ON "consent_grants" ("user_id")
+ `);
+
+ await queryRunner.query(`
+ CREATE INDEX IF NOT EXISTS "IDX_consent_grants_requester_id" ON "consent_grants" ("requester_id")
+ `);
+
+ await queryRunner.query(`
+ CREATE INDEX IF NOT EXISTS "IDX_consent_grants_user_requester_service_type"
+ ON "consent_grants" ("user_id", "requester_id", "service_id", "consent_type")
+ `);
+
+ await queryRunner.query(`
+ ALTER TABLE "consent_grants"
+ ADD CONSTRAINT "FK_consent_grants_user_id"
+ FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION
+ `);
+
+ await queryRunner.query(`
+ ALTER TABLE "consent_grants"
+ ADD CONSTRAINT "FK_consent_grants_requester_id"
+ FOREIGN KEY ("requester_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION
+ `);
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`ALTER TABLE "consent_grants" DROP CONSTRAINT IF EXISTS "FK_consent_grants_requester_id"`);
+ await queryRunner.query(`ALTER TABLE "consent_grants" DROP CONSTRAINT IF EXISTS "FK_consent_grants_user_id"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_consent_grants_user_requester_service_type"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_consent_grants_requester_id"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_consent_grants_user_id"`);
+ await queryRunner.query(`DROP TABLE IF EXISTS "consent_grants"`);
+
+ await queryRunner.query(`ALTER TABLE "user_consents" DROP CONSTRAINT IF EXISTS "FK_user_consents_target_user_id"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_user_consents_target_user_id"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_user_consents_item_id"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_user_consents_user_service_type_item_target"`);
+
+ await queryRunner.query(`
+ ALTER TABLE "user_consents"
+ DROP COLUMN IF EXISTS "consent_type",
+ DROP COLUMN IF EXISTS "item_id",
+ DROP COLUMN IF EXISTS "target_user_id",
+ DROP COLUMN IF EXISTS "status",
+ DROP COLUMN IF EXISTS "basis_document_url",
+ DROP COLUMN IF EXISTS "basis_document_hash",
+ DROP COLUMN IF EXISTS "granted_at",
+ DROP COLUMN IF EXISTS "retracted_at",
+ DROP COLUMN IF EXISTS "expires_at",
+ DROP COLUMN IF EXISTS "extra_data"
+ `);
+
+ await queryRunner.query(`
+ CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_consents_user_id_service_id"
+ ON "user_consents" ("user_id", "service_id")
+ `);
+ }
+}
diff --git a/src/util/util/Constants.ts b/src/util/util/Constants.ts
index ae0df0ece..2b81c87a6 100644
--- a/src/util/util/Constants.ts
+++ b/src/util/util/Constants.ts
@@ -19,12 +19,12 @@
import { ApiError } from "./ApiError";
export const WSCodes = {
- 1000: "WS_CLOSE_REQUESTED",
- 4004: "TOKEN_INVALID",
- 4010: "SHARDING_INVALID",
- 4011: "SHARDING_REQUIRED",
- 4013: "INVALID_INTENTS",
- 4014: "DISALLOWED_INTENTS",
+ 1000: "WS_CLOSE_REQUESTED",
+ 4004: "TOKEN_INVALID",
+ 4010: "SHARDING_INVALID",
+ 4011: "SHARDING_REQUIRED",
+ 4013: "INVALID_INTENTS",
+ 4014: "DISALLOWED_INTENTS",
};
/**
@@ -41,102 +41,102 @@ export const WSCodes = {
* @typedef {number} Status
*/
export const WsStatus = {
- READY: 0,
- CONNECTING: 1,
- RECONNECTING: 2,
- IDLE: 3,
- NEARLY: 4,
- DISCONNECTED: 5,
- WAITING_FOR_GUILDS: 6,
- IDENTIFYING: 7,
- RESUMING: 8,
+ READY: 0,
+ CONNECTING: 1,
+ RECONNECTING: 2,
+ IDLE: 3,
+ NEARLY: 4,
+ DISCONNECTED: 5,
+ WAITING_FOR_GUILDS: 6,
+ IDENTIFYING: 7,
+ RESUMING: 8,
};
export const OPCodes = {
- DISPATCH: 0,
- HEARTBEAT: 1,
- IDENTIFY: 2,
- STATUS_UPDATE: 3,
- VOICE_STATE_UPDATE: 4,
- VOICE_GUILD_PING: 5,
- RESUME: 6,
- RECONNECT: 7,
- REQUEST_GUILD_MEMBERS: 8,
- INVALID_SESSION: 9,
- HELLO: 10,
- HEARTBEAT_ACK: 11,
+ DISPATCH: 0,
+ HEARTBEAT: 1,
+ IDENTIFY: 2,
+ STATUS_UPDATE: 3,
+ VOICE_STATE_UPDATE: 4,
+ VOICE_GUILD_PING: 5,
+ RESUME: 6,
+ RECONNECT: 7,
+ REQUEST_GUILD_MEMBERS: 8,
+ INVALID_SESSION: 9,
+ HELLO: 10,
+ HEARTBEAT_ACK: 11,
};
export const Events = {
- RATE_LIMIT: "rateLimit",
- CLIENT_READY: "ready",
- GUILD_CREATE: "guildCreate",
- GUILD_DELETE: "guildDelete",
- GUILD_UPDATE: "guildUpdate",
- GUILD_UNAVAILABLE: "guildUnavailable",
- GUILD_AVAILABLE: "guildAvailable",
- GUILD_MEMBER_ADD: "guildMemberAdd",
- GUILD_MEMBER_REMOVE: "guildMemberRemove",
- GUILD_MEMBER_UPDATE: "guildMemberUpdate",
- GUILD_MEMBER_AVAILABLE: "guildMemberAvailable",
- GUILD_MEMBER_SPEAKING: "guildMemberSpeaking",
- GUILD_MEMBERS_CHUNK: "guildMembersChunk",
- GUILD_INTEGRATIONS_UPDATE: "guildIntegrationsUpdate",
- GUILD_ROLE_CREATE: "roleCreate",
- GUILD_ROLE_DELETE: "roleDelete",
- INVITE_CREATE: "inviteCreate",
- INVITE_DELETE: "inviteDelete",
- GUILD_ROLE_UPDATE: "roleUpdate",
- GUILD_EMOJI_CREATE: "emojiCreate",
- GUILD_EMOJI_DELETE: "emojiDelete",
- GUILD_EMOJI_UPDATE: "emojiUpdate",
- GUILD_BAN_ADD: "guildBanAdd",
- GUILD_BAN_REMOVE: "guildBanRemove",
- CHANNEL_CREATE: "channelCreate",
- CHANNEL_DELETE: "channelDelete",
- CHANNEL_UPDATE: "channelUpdate",
- CHANNEL_PINS_UPDATE: "channelPinsUpdate",
- MESSAGE_CREATE: "message",
- MESSAGE_DELETE: "messageDelete",
- MESSAGE_UPDATE: "messageUpdate",
- MESSAGE_BULK_DELETE: "messageDeleteBulk",
- MESSAGE_REACTION_ADD: "messageReactionAdd",
- MESSAGE_REACTION_REMOVE: "messageReactionRemove",
- MESSAGE_REACTION_REMOVE_ALL: "messageReactionRemoveAll",
- MESSAGE_REACTION_REMOVE_EMOJI: "messageReactionRemoveEmoji",
- USER_UPDATE: "userUpdate",
- PRESENCE_UPDATE: "presenceUpdate",
- VOICE_SERVER_UPDATE: "voiceServerUpdate",
- VOICE_STATE_UPDATE: "voiceStateUpdate",
- VOICE_BROADCAST_SUBSCRIBE: "subscribe",
- VOICE_BROADCAST_UNSUBSCRIBE: "unsubscribe",
- TYPING_START: "typingStart",
- TYPING_STOP: "typingStop",
- WEBHOOKS_UPDATE: "webhookUpdate",
- LOBBY_CREATE: "lobbyCreate",
- LOBBY_UPDATE: "lobbyUpdate",
- LOBBY_DELETE: "lobbyDelete",
- LOBBY_MEMBER_ADD: "lobbyMemberAdd",
- LOBBY_MEMBER_REMOVE: "lobbyMemberRemove",
- ERROR: "error",
- WARN: "warn",
- DEBUG: "debug",
- SHARD_DISCONNECT: "shardDisconnect",
- SHARD_ERROR: "shardError",
- SHARD_RECONNECTING: "shardReconnecting",
- SHARD_READY: "shardReady",
- SHARD_RESUME: "shardResume",
- INVALIDATED: "invalidated",
- RAW: "raw",
+ RATE_LIMIT: "rateLimit",
+ CLIENT_READY: "ready",
+ GUILD_CREATE: "guildCreate",
+ GUILD_DELETE: "guildDelete",
+ GUILD_UPDATE: "guildUpdate",
+ GUILD_UNAVAILABLE: "guildUnavailable",
+ GUILD_AVAILABLE: "guildAvailable",
+ GUILD_MEMBER_ADD: "guildMemberAdd",
+ GUILD_MEMBER_REMOVE: "guildMemberRemove",
+ GUILD_MEMBER_UPDATE: "guildMemberUpdate",
+ GUILD_MEMBER_AVAILABLE: "guildMemberAvailable",
+ GUILD_MEMBER_SPEAKING: "guildMemberSpeaking",
+ GUILD_MEMBERS_CHUNK: "guildMembersChunk",
+ GUILD_INTEGRATIONS_UPDATE: "guildIntegrationsUpdate",
+ GUILD_ROLE_CREATE: "roleCreate",
+ GUILD_ROLE_DELETE: "roleDelete",
+ INVITE_CREATE: "inviteCreate",
+ INVITE_DELETE: "inviteDelete",
+ GUILD_ROLE_UPDATE: "roleUpdate",
+ GUILD_EMOJI_CREATE: "emojiCreate",
+ GUILD_EMOJI_DELETE: "emojiDelete",
+ GUILD_EMOJI_UPDATE: "emojiUpdate",
+ GUILD_BAN_ADD: "guildBanAdd",
+ GUILD_BAN_REMOVE: "guildBanRemove",
+ CHANNEL_CREATE: "channelCreate",
+ CHANNEL_DELETE: "channelDelete",
+ CHANNEL_UPDATE: "channelUpdate",
+ CHANNEL_PINS_UPDATE: "channelPinsUpdate",
+ MESSAGE_CREATE: "message",
+ MESSAGE_DELETE: "messageDelete",
+ MESSAGE_UPDATE: "messageUpdate",
+ MESSAGE_BULK_DELETE: "messageDeleteBulk",
+ MESSAGE_REACTION_ADD: "messageReactionAdd",
+ MESSAGE_REACTION_REMOVE: "messageReactionRemove",
+ MESSAGE_REACTION_REMOVE_ALL: "messageReactionRemoveAll",
+ MESSAGE_REACTION_REMOVE_EMOJI: "messageReactionRemoveEmoji",
+ USER_UPDATE: "userUpdate",
+ PRESENCE_UPDATE: "presenceUpdate",
+ VOICE_SERVER_UPDATE: "voiceServerUpdate",
+ VOICE_STATE_UPDATE: "voiceStateUpdate",
+ VOICE_BROADCAST_SUBSCRIBE: "subscribe",
+ VOICE_BROADCAST_UNSUBSCRIBE: "unsubscribe",
+ TYPING_START: "typingStart",
+ TYPING_STOP: "typingStop",
+ WEBHOOKS_UPDATE: "webhookUpdate",
+ LOBBY_CREATE: "lobbyCreate",
+ LOBBY_UPDATE: "lobbyUpdate",
+ LOBBY_DELETE: "lobbyDelete",
+ LOBBY_MEMBER_ADD: "lobbyMemberAdd",
+ LOBBY_MEMBER_REMOVE: "lobbyMemberRemove",
+ ERROR: "error",
+ WARN: "warn",
+ DEBUG: "debug",
+ SHARD_DISCONNECT: "shardDisconnect",
+ SHARD_ERROR: "shardError",
+ SHARD_RECONNECTING: "shardReconnecting",
+ SHARD_READY: "shardReady",
+ SHARD_RESUME: "shardResume",
+ INVALIDATED: "invalidated",
+ RAW: "raw",
};
export const ShardEvents = {
- CLOSE: "close",
- DESTROYED: "destroyed",
- INVALID_SESSION: "invalidSession",
- READY: "ready",
- RESUMED: "resumed",
- ALL_READY: "allReady",
+ CLOSE: "close",
+ DESTROYED: "destroyed",
+ INVALID_SESSION: "invalidSession",
+ READY: "ready",
+ RESUMED: "resumed",
+ ALL_READY: "allReady",
};
/**
@@ -150,13 +150,7 @@ export const ShardEvents = {
* sidebar for more information.
* @typedef {string} PartialType
*/
-export const PartialTypes = keyMirror([
- "USER",
- "CHANNEL",
- "GUILD_MEMBER",
- "MESSAGE",
- "REACTION",
-]);
+export const PartialTypes = keyMirror(["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"]);
/**
* The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events:
@@ -199,42 +193,42 @@ export const PartialTypes = keyMirror([
* @typedef {string} WSEventType
*/
export const WSEvents = keyMirror([
- "READY",
- "RESUMED",
- "GUILD_CREATE",
- "GUILD_DELETE",
- "GUILD_UPDATE",
- "INVITE_CREATE",
- "INVITE_DELETE",
- "GUILD_MEMBER_ADD",
- "GUILD_MEMBER_REMOVE",
- "GUILD_MEMBER_UPDATE",
- "GUILD_MEMBERS_CHUNK",
- "GUILD_INTEGRATIONS_UPDATE",
- "GUILD_ROLE_CREATE",
- "GUILD_ROLE_DELETE",
- "GUILD_ROLE_UPDATE",
- "GUILD_BAN_ADD",
- "GUILD_BAN_REMOVE",
- "GUILD_EMOJIS_UPDATE",
- "CHANNEL_CREATE",
- "CHANNEL_DELETE",
- "CHANNEL_UPDATE",
- "CHANNEL_PINS_UPDATE",
- "MESSAGE_CREATE",
- "MESSAGE_DELETE",
- "MESSAGE_UPDATE",
- "MESSAGE_DELETE_BULK",
- "MESSAGE_REACTION_ADD",
- "MESSAGE_REACTION_REMOVE",
- "MESSAGE_REACTION_REMOVE_ALL",
- "MESSAGE_REACTION_REMOVE_EMOJI",
- "USER_UPDATE",
- "PRESENCE_UPDATE",
- "TYPING_START",
- "VOICE_STATE_UPDATE",
- "VOICE_SERVER_UPDATE",
- "WEBHOOKS_UPDATE",
+ "READY",
+ "RESUMED",
+ "GUILD_CREATE",
+ "GUILD_DELETE",
+ "GUILD_UPDATE",
+ "INVITE_CREATE",
+ "INVITE_DELETE",
+ "GUILD_MEMBER_ADD",
+ "GUILD_MEMBER_REMOVE",
+ "GUILD_MEMBER_UPDATE",
+ "GUILD_MEMBERS_CHUNK",
+ "GUILD_INTEGRATIONS_UPDATE",
+ "GUILD_ROLE_CREATE",
+ "GUILD_ROLE_DELETE",
+ "GUILD_ROLE_UPDATE",
+ "GUILD_BAN_ADD",
+ "GUILD_BAN_REMOVE",
+ "GUILD_EMOJIS_UPDATE",
+ "CHANNEL_CREATE",
+ "CHANNEL_DELETE",
+ "CHANNEL_UPDATE",
+ "CHANNEL_PINS_UPDATE",
+ "MESSAGE_CREATE",
+ "MESSAGE_DELETE",
+ "MESSAGE_UPDATE",
+ "MESSAGE_DELETE_BULK",
+ "MESSAGE_REACTION_ADD",
+ "MESSAGE_REACTION_REMOVE",
+ "MESSAGE_REACTION_REMOVE_ALL",
+ "MESSAGE_REACTION_REMOVE_EMOJI",
+ "USER_UPDATE",
+ "PRESENCE_UPDATE",
+ "TYPING_START",
+ "VOICE_STATE_UPDATE",
+ "VOICE_SERVER_UPDATE",
+ "WEBHOOKS_UPDATE",
]);
/**
@@ -258,26 +252,26 @@ export const WSEvents = keyMirror([
* @typedef {string} MessageType
*/
export const MessageTypes = [
- "DEFAULT",
- "RECIPIENT_ADD",
- "RECIPIENT_REMOVE",
- "CALL",
- "CHANNEL_NAME_CHANGE",
- "CHANNEL_ICON_CHANGE",
- "PINS_ADD",
- "GUILD_MEMBER_JOIN",
- "USER_PREMIUM_GUILD_SUBSCRIPTION",
- "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1",
- "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2",
- "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3",
- "CHANNEL_FOLLOW_ADD",
- null,
- "GUILD_DISCOVERY_DISQUALIFIED",
- "GUILD_DISCOVERY_REQUALIFIED",
- null,
- null,
- null,
- "REPLY",
+ "DEFAULT",
+ "RECIPIENT_ADD",
+ "RECIPIENT_REMOVE",
+ "CALL",
+ "CHANNEL_NAME_CHANGE",
+ "CHANNEL_ICON_CHANGE",
+ "PINS_ADD",
+ "GUILD_MEMBER_JOIN",
+ "USER_PREMIUM_GUILD_SUBSCRIPTION",
+ "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1",
+ "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2",
+ "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3",
+ "CHANNEL_FOLLOW_ADD",
+ null,
+ "GUILD_DISCOVERY_DISQUALIFIED",
+ "GUILD_DISCOVERY_REQUALIFIED",
+ null,
+ null,
+ null,
+ "REPLY",
];
/**
@@ -286,9 +280,7 @@ export const MessageTypes = [
* * REPLY
* @typedef {string} SystemMessageType
*/
-export const SystemMessageTypes = MessageTypes.filter(
- (type: string | null) => type && type !== "DEFAULT" && type !== "REPLY",
-);
+export const SystemMessageTypes = MessageTypes.filter((type: string | null) => type && type !== "DEFAULT" && type !== "REPLY");
/**
* Bots cannot set a `CUSTOM_STATUS`, it is only for custom statuses received from users
@@ -301,80 +293,73 @@ export const SystemMessageTypes = MessageTypes.filter(
* * COMPETING
* @typedef {string} ActivityType
*/
-export const ActivityTypes = [
- "PLAYING",
- "STREAMING",
- "LISTENING",
- "WATCHING",
- "CUSTOM_STATUS",
- "COMPETING",
-];
+export const ActivityTypes = ["PLAYING", "STREAMING", "LISTENING", "WATCHING", "CUSTOM_STATUS", "COMPETING"];
export const ChannelTypes = {
- TEXT: 0,
- DM: 1,
- VOICE: 2,
- GROUP: 3,
- CATEGORY: 4,
- NEWS: 5,
- STORE: 6,
+ TEXT: 0,
+ DM: 1,
+ VOICE: 2,
+ GROUP: 3,
+ CATEGORY: 4,
+ NEWS: 5,
+ STORE: 6,
};
export const ClientApplicationAssetTypes = {
- SMALL: 1,
- BIG: 2,
+ SMALL: 1,
+ BIG: 2,
};
export const AutomodActionTypes = {
- BLOCK_MESSAGE: 1,
- SEND_ALERT: 2,
- TIMEOUT_MEMBER: 3,
+ BLOCK_MESSAGE: 1,
+ SEND_ALERT: 2,
+ TIMEOUT_MEMBER: 3,
};
// Automod trigger types mapping: 1–6 follow discord-api-docs; 30+ are Spacebar extensions.
export const AutomodTriggerTypes = {
- CUSTOM_WORDS: 1,
- HARMFUL_LINKS: 2,
- SUSPECTED_SPAM_CONTENT: 3,
- COMMONLY_FLAGGED_WORDS: 4,
- MENTION_SPAM: 5,
- MEMBER_PROFILE: 6,
- PROHIBITED_LANGUAGES: 30,
- SIMILARITY: 31,
- ARTIFICIAL_CONTENT: 32,
- EXECUTABLE_FILES: 33,
+ CUSTOM_WORDS: 1,
+ HARMFUL_LINKS: 2,
+ SUSPECTED_SPAM_CONTENT: 3,
+ COMMONLY_FLAGGED_WORDS: 4,
+ MENTION_SPAM: 5,
+ MEMBER_PROFILE: 6,
+ PROHIBITED_LANGUAGES: 30,
+ SIMILARITY: 31,
+ ARTIFICIAL_CONTENT: 32,
+ EXECUTABLE_FILES: 33,
};
export const Colors = {
- DEFAULT: 0x000000,
- WHITE: 0xffffff,
- AQUA: 0x1abc9c,
- GREEN: 0x2ecc71,
- BLUE: 0x3498db,
- YELLOW: 0xffff00,
- PURPLE: 0x9b59b6,
- LUMINOUS_VIVID_PINK: 0xe91e63,
- GOLD: 0xf1c40f,
- ORANGE: 0xe67e22,
- RED: 0xe74c3c,
- GREY: 0x95a5a6,
- NAVY: 0x34495e,
- DARK_AQUA: 0x11806a,
- DARK_GREEN: 0x1f8b4c,
- DARK_BLUE: 0x206694,
- DARK_PURPLE: 0x71368a,
- DARK_VIVID_PINK: 0xad1457,
- DARK_GOLD: 0xc27c0e,
- DARK_ORANGE: 0xa84300,
- DARK_RED: 0x992d22,
- DARK_GREY: 0x979c9f,
- DARKER_GREY: 0x7f8c8d,
- LIGHT_GREY: 0xbcc0c0,
- DARK_NAVY: 0x2c3e50,
- BLURPLE: 0x7289da,
- GREYPLE: 0x99aab5,
- DARK_BUT_NOT_BLACK: 0x2c2f33,
- NOT_QUITE_BLACK: 0x23272a,
+ DEFAULT: 0x000000,
+ WHITE: 0xffffff,
+ AQUA: 0x1abc9c,
+ GREEN: 0x2ecc71,
+ BLUE: 0x3498db,
+ YELLOW: 0xffff00,
+ PURPLE: 0x9b59b6,
+ LUMINOUS_VIVID_PINK: 0xe91e63,
+ GOLD: 0xf1c40f,
+ ORANGE: 0xe67e22,
+ RED: 0xe74c3c,
+ GREY: 0x95a5a6,
+ NAVY: 0x34495e,
+ DARK_AQUA: 0x11806a,
+ DARK_GREEN: 0x1f8b4c,
+ DARK_BLUE: 0x206694,
+ DARK_PURPLE: 0x71368a,
+ DARK_VIVID_PINK: 0xad1457,
+ DARK_GOLD: 0xc27c0e,
+ DARK_ORANGE: 0xa84300,
+ DARK_RED: 0x992d22,
+ DARK_GREY: 0x979c9f,
+ DARKER_GREY: 0x7f8c8d,
+ LIGHT_GREY: 0xbcc0c0,
+ DARK_NAVY: 0x2c3e50,
+ BLURPLE: 0x7289da,
+ GREYPLE: 0x99aab5,
+ DARK_BUT_NOT_BLACK: 0x2c2f33,
+ NOT_QUITE_BLACK: 0x23272a,
};
/**
@@ -384,11 +369,7 @@ export const Colors = {
* * ALL_MEMBERS
* @typedef {string} ExplicitContentFilterLevel
*/
-export const ExplicitContentFilterLevels = [
- "DISABLED",
- "MEMBERS_WITHOUT_ROLES",
- "ALL_MEMBERS",
-];
+export const ExplicitContentFilterLevels = ["DISABLED", "MEMBERS_WITHOUT_ROLES", "ALL_MEMBERS"];
/**
* The value set for the verification levels for a guild:
@@ -399,13 +380,7 @@ export const ExplicitContentFilterLevels = [
* * VERY_HIGH
* @typedef {string} VerificationLevel
*/
-export const VerificationLevels = [
- "NONE",
- "LOW",
- "MEDIUM",
- "HIGH",
- "VERY_HIGH",
-];
+export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"];
/**
* An error encountered while performing an API request. Here are the potential errors:
@@ -551,556 +526,187 @@ export const VerificationLevels = [
* @typedef {string} APIError
*/
export const DiscordApiErrors = {
- //https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes
- GENERAL_ERROR: new ApiError(
- "General error (such as a malformed request body, amongst other things)",
- 0,
- ),
- UNKNOWN_ACCOUNT: new ApiError("Unknown account", 10001),
- UNKNOWN_APPLICATION: new ApiError("Unknown application", 10002),
- UNKNOWN_CHANNEL: new ApiError("Unknown channel", 10003),
- UNKNOWN_GUILD: new ApiError("Unknown guild", 10004),
- UNKNOWN_INTEGRATION: new ApiError("Unknown integration", 10005),
- UNKNOWN_INVITE: new ApiError("Unknown invite", 10006),
- UNKNOWN_MEMBER: new ApiError("Unknown member", 10007),
- UNKNOWN_MESSAGE: new ApiError("Unknown message", 10008),
- UNKNOWN_OVERWRITE: new ApiError("Unknown permission overwrite", 10009),
- UNKNOWN_PROVIDER: new ApiError("Unknown provider", 10010),
- UNKNOWN_ROLE: new ApiError("Unknown role", 10011),
- UNKNOWN_TOKEN: new ApiError("Unknown token", 10012),
- UNKNOWN_USER: new ApiError("Unknown user", 10013),
- UNKNOWN_EMOJI: new ApiError("Unknown emoji", 10014),
- UNKNOWN_WEBHOOK: new ApiError("Unknown webhook", 10015, 404),
- UNKNOWN_WEBHOOK_SERVICE: new ApiError("Unknown webhook service", 10016),
- UNKNOWN_CONNECTION: new ApiError("Unknown connection", 10017, 400),
- UNKNOWN_SESSION: new ApiError("Unknown session", 10020),
- UNKNOWN_BAN: new ApiError("Unknown ban", 10026),
- UNKNOWN_SKU: new ApiError("Unknown SKU", 10027),
- UNKNOWN_STORE_LISTING: new ApiError("Unknown Store Listing", 10028),
- UNKNOWN_ENTITLEMENT: new ApiError("Unknown entitlement", 10029),
- UNKNOWN_BUILD: new ApiError("Unknown build", 10030),
- UNKNOWN_LOBBY: new ApiError("Unknown lobby", 10031),
- UNKNOWN_BRANCH: new ApiError("Unknown branch", 10032),
- UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError(
- "Unknown store directory layout",
- 10033,
- ),
- UNKNOWN_REDISTRIBUTABLE: new ApiError("Unknown redistributable", 10036),
- UNKNOWN_GIFT_CODE: new ApiError("Unknown gift code", 10038),
- UNKNOWN_STREAM: new ApiError("Unknown stream", 10049),
- UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError(
- "Unknown premium server subscribe cooldown",
- 10050,
- ),
- UNKNOWN_GUILD_TEMPLATE: new ApiError("Unknown guild template", 10057),
- UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError(
- "Unknown discoverable server category",
- 10059,
- ),
- UNKNOWN_STICKER: new ApiError("Unknown sticker", 10060),
- UNKNOWN_INTERACTION: new ApiError("Unknown interaction", 10062),
- UNKNOWN_APPLICATION_COMMAND: new ApiError(
- "Unknown application command",
- 10063,
- ),
- UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError(
- "Unknown application command permissions",
- 10066,
- ),
- UNKNOWN_STAGE_INSTANCE: new ApiError("Unknown Stage Instance", 10067),
- UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError(
- "Unknown Guild Member Verification Form",
- 10068,
- ),
- UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError(
- "Unknown Guild Welcome Screen",
- 10069,
- ),
- UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError(
- "Unknown Guild Scheduled Event",
- 10070,
- ),
- UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError(
- "Unknown Guild Scheduled Event User",
- 10071,
- ),
- BOT_PROHIBITED_ENDPOINT: new ApiError(
- "Bots cannot use this endpoint",
- 20001,
- ),
- BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002),
- EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError(
- "Explicit content cannot be sent to the desired recipient(s)",
- 20009,
- ),
- ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError(
- "You are not authorized to perform this action on this application",
- 20012,
- ),
- SLOWMODE_RATE_LIMIT: new ApiError(
- "This action cannot be performed due to slowmode rate limit",
- 20016,
- ),
- ONLY_OWNER: new ApiError(
- "Only the owner of this account can perform this action",
- 20018,
- ),
- ANNOUNCEMENT_RATE_LIMITS: new ApiError(
- "This message cannot be edited due to announcement rate limits",
- 20022,
- ),
- CHANNEL_WRITE_RATELIMIT: new ApiError(
- "The channel you are writing has hit the write rate limit",
- 20028,
- ),
- WORDS_NOT_ALLOWED: new ApiError(
- "Your Stage topic, server name, server description, or channel names contain words that are not allowed",
- 20031,
- ),
- GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError(
- "Guild premium subscription level too low",
- 20035,
- ),
- MAXIMUM_GUILDS: new ApiError(
- "Maximum number of guilds reached ({})",
- 30001,
- undefined,
- ["100"],
- ),
- MAXIMUM_FRIENDS: new ApiError(
- "Maximum number of friends reached ({})",
- 30002,
- undefined,
- ["1000"],
- ),
- MAXIMUM_PINS: new ApiError(
- "Maximum number of pins reached for the channel ({})",
- 30003,
- undefined,
- ["50"],
- ),
- MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError(
- "Maximum number of recipients reached ({})",
- 30004,
- undefined,
- ["10"],
- ),
- MAXIMUM_ROLES: new ApiError(
- "Maximum number of guild roles reached ({})",
- 30005,
- undefined,
- ["250"],
- ),
- MAXIMUM_WEBHOOKS: new ApiError(
- "Maximum number of webhooks reached ({})",
- 30007,
- undefined,
- ["10"],
- ),
- MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError(
- "Maximum number of emojis reached",
- 30008,
- ),
- MAXIMUM_REACTIONS: new ApiError(
- "Maximum number of reactions reached ({})",
- 30010,
- undefined,
- ["20"],
- ),
- MAXIMUM_CHANNELS: new ApiError(
- "Maximum number of guild channels reached ({})",
- 30013,
- undefined,
- ["500"],
- ),
- MAXIMUM_ATTACHMENTS: new ApiError(
- "Maximum number of attachments in a message reached ({})",
- 30015,
- undefined,
- ["10"],
- ),
- MAXIMUM_INVITES: new ApiError(
- "Maximum number of invites reached ({})",
- 30016,
- undefined,
- ["1000"],
- ),
- MAXIMUM_ANIMATED_EMOJIS: new ApiError(
- "Maximum number of animated emojis reached",
- 30018,
- ),
- MAXIMUM_SERVER_MEMBERS: new ApiError(
- "Maximum number of server members reached",
- 30019,
- ),
- MAXIMUM_SERVER_CATEGORIES: new ApiError(
- "Maximum number of server categories has been reached ({})",
- 30030,
- undefined,
- ["5"],
- ),
- GUILD_ALREADY_HAS_TEMPLATE: new ApiError(
- "Guild already has a template",
- 30031,
- ),
- MAXIMUM_THREAD_PARTICIPANTS: new ApiError(
- "Max number of thread participants has been reached",
- 30033,
- ),
- MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError(
- "Maximum number of bans for non-guild members have been exceeded",
- 30035,
- ),
- MAXIMUM_BANS_FETCHES: new ApiError(
- "Maximum number of bans fetches has been reached",
- 30037,
- ),
- MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039),
- MAXIMUM_PRUNE_REQUESTS: new ApiError(
- "Maximum number of prune requests has been reached. Try again later",
- 30040,
- ),
- UNAUTHORIZED: new ApiError(
- "Unauthorized. Provide a valid token and try again",
- 40001,
- ),
- ACCOUNT_VERIFICATION_REQUIRED: new ApiError(
- "You need to verify your account in order to perform this action",
- 40002,
- ),
- OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError(
- "You are opening direct messages too fast",
- 40003,
- ),
- REQUEST_ENTITY_TOO_LARGE: new ApiError(
- "Request entity too large. Try sending something smaller in size",
- 40005,
- ),
- FEATURE_TEMPORARILY_DISABLED: new ApiError(
- "This feature has been temporarily disabled server-side",
- 40006,
- ),
- USER_BANNED: new ApiError("The user is banned from this guild", 40007),
- CONNECTION_REVOKED: new ApiError(
- "The connection has been revoked",
- 40012,
- 400,
- ),
- TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError(
- "Target user is not connected to voice",
- 40032,
- ),
- ALREADY_CROSSPOSTED: new ApiError(
- "This message has already been crossposted",
- 40033,
- ),
- APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError(
- "An application command with that name already exists",
- 40041,
- ),
- MISSING_ACCESS: new ApiError("Missing access", 50001),
- INVALID_ACCOUNT_TYPE: new ApiError("Invalid account type", 50002),
- CANNOT_EXECUTE_ON_DM: new ApiError(
- "Cannot execute action on a DM channel",
- 50003,
- ),
- EMBED_DISABLED: new ApiError("Widget Disabled", 50004),
- CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError(
- "Cannot edit a message authored by another user",
- 50005,
- ),
- CANNOT_SEND_EMPTY_MESSAGE: new ApiError(
- "Cannot send an empty message",
- 50006,
- ),
- CANNOT_MESSAGE_USER: new ApiError(
- "Cannot send messages to this user",
- 50007,
- ),
- CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError(
- "Cannot send messages in a voice channel",
- 50008,
- ),
- CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError(
- "Channel verification level is too high for you to gain access",
- 50009,
- ),
- OAUTH2_APPLICATION_BOT_ABSENT: new ApiError(
- "OAuth2 application does not have a bot",
- 50010,
- ),
- MAXIMUM_OAUTH2_APPLICATIONS: new ApiError(
- "OAuth2 application limit reached",
- 50011,
- ),
- INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012),
- MISSING_PERMISSIONS: new ApiError(
- "You lack permissions to perform that action ({})",
- 50013,
- undefined,
- [""],
- ),
- INVALID_AUTHENTICATION_TOKEN: new ApiError(
- "Invalid authentication token provided",
- 50014,
- ),
- NOTE_TOO_LONG: new ApiError("Note was too long", 50015),
- INVALID_BULK_DELETE_QUANTITY: new ApiError(
- "Provided too few or too many messages to delete. Must provide at least {} and fewer than {} messages to delete",
- 50016,
- undefined,
- ["2", "100"],
- ),
- CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError(
- "A message can only be pinned to the channel it was sent in",
- 50019,
- ),
- INVALID_OR_TAKEN_INVITE_CODE: new ApiError(
- "Invite code was either invalid or taken",
- 50020,
- ),
- CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError(
- "Cannot execute action on a system message",
- 50021,
- ),
- CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError(
- "Cannot execute action on this channel type",
- 50024,
- ),
- INVALID_OAUTH_TOKEN: new ApiError(
- "Invalid OAuth2 access token provided",
- 50025,
- ),
- MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError(
- "Missing required OAuth2 scope",
- 50026,
- ),
- INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError(
- "Invalid webhook token provided",
- 50027,
- ),
- INVALID_ROLE: new ApiError("Invalid role", 50028),
- INVALID_RECIPIENT: new ApiError("Invalid Recipient(s)", 50033),
- BULK_DELETE_MESSAGE_TOO_OLD: new ApiError(
- "A message provided was too old to bulk delete",
- 50034,
- ),
- INVALID_FORM_BODY: new ApiError(
- "Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided",
- 50035,
- ),
- INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError(
- "An invite was accepted to a guild the application's bot is not in",
- 50036,
- ),
- INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041),
- FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError(
- "File uploaded exceeds the maximum size",
- 50045,
- ),
- INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046),
- CANNOT_SELF_REDEEM_GIFT: new ApiError(
- "Cannot self-redeem this gift",
- 50054,
- ),
- PAYMENT_SOURCE_REQUIRED: new ApiError(
- "Payment source required to redeem gift",
- 50070,
- ),
- CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError(
- "Cannot delete a channel required for Community guilds",
- 50074,
- ),
- INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081),
- CANNOT_EDIT_ARCHIVED_THREAD: new ApiError(
- "Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread",
- 50083,
- ),
- INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError(
- "Invalid thread notification settings",
- 50084,
- ),
- BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError(
- "before value is earlier than the thread creation date",
- 50085,
- ),
- SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError(
- "This server is not available in your location",
- 50095,
- ),
- SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError(
- "This server needs monetization enabled in order to perform this action",
- 50097,
- ),
- TWO_FACTOR_REQUIRED: new ApiError(
- "Two factor is required for this operation",
- 60003,
- ),
- NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError(
- "No users with DiscordTag exist",
- 80004,
- ),
- REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001),
- RESOURCE_OVERLOADED: new ApiError(
- "API resource is currently overloaded. Try again a little later",
- 130000,
- ),
- STAGE_ALREADY_OPEN: new ApiError("The Stage is already open", 150006),
- THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError(
- "A thread has already been created for this message",
- 160004,
- ),
- THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005),
- MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError(
- "Maximum number of active threads reached",
- 160006,
- ),
- MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError(
- "Maximum number of active announcement threads reached",
- 160007,
- ),
- INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError(
- "Invalid JSON for uploaded Lottie file",
- 170001,
- ),
- LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError(
- "Uploaded Lotties cannot contain rasterized images such as PNG or JPEG",
- 170002,
- ),
- STICKER_MAXIMUM_FRAMERATE: new ApiError(
- "Sticker maximum framerate exceeded",
- 170003,
- ),
- STICKER_MAXIMUM_FRAME_COUNT: new ApiError(
- "Sticker frame count exceeds maximum of {} frames",
- 170004,
- undefined,
- ["1000"],
- ),
- LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError(
- "Lottie animation maximum dimensions exceeded",
- 170005,
- ),
- STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError(
- "Sticker frame rate is either too small or too large",
- 170006,
- ),
- STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError(
- "Sticker animation duration exceeds maximum of {} seconds",
- 170007,
- undefined,
- ["5"],
- ),
- AUTOMODERATOR_BLOCK: new ApiError(
- "Message was blocked by automatic moderation",
- 200000,
- ),
- BULK_BAN_FAILED: new ApiError("Failed to ban users", 500000),
+ //https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes
+ GENERAL_ERROR: new ApiError("General error (such as a malformed request body, amongst other things)", 0),
+ UNKNOWN_ACCOUNT: new ApiError("Unknown account", 10001),
+ UNKNOWN_APPLICATION: new ApiError("Unknown application", 10002),
+ UNKNOWN_CHANNEL: new ApiError("Unknown channel", 10003),
+ UNKNOWN_GUILD: new ApiError("Unknown guild", 10004),
+ UNKNOWN_INTEGRATION: new ApiError("Unknown integration", 10005),
+ UNKNOWN_INVITE: new ApiError("Unknown invite", 10006),
+ UNKNOWN_MEMBER: new ApiError("Unknown member", 10007),
+ UNKNOWN_MESSAGE: new ApiError("Unknown message", 10008),
+ UNKNOWN_OVERWRITE: new ApiError("Unknown permission overwrite", 10009),
+ UNKNOWN_PROVIDER: new ApiError("Unknown provider", 10010),
+ UNKNOWN_ROLE: new ApiError("Unknown role", 10011),
+ UNKNOWN_TOKEN: new ApiError("Unknown token", 10012),
+ UNKNOWN_USER: new ApiError("Unknown user", 10013),
+ UNKNOWN_EMOJI: new ApiError("Unknown emoji", 10014),
+ UNKNOWN_WEBHOOK: new ApiError("Unknown webhook", 10015, 404),
+ UNKNOWN_WEBHOOK_SERVICE: new ApiError("Unknown webhook service", 10016),
+ UNKNOWN_CONNECTION: new ApiError("Unknown connection", 10017, 400),
+ UNKNOWN_SESSION: new ApiError("Unknown session", 10020),
+ UNKNOWN_BAN: new ApiError("Unknown ban", 10026),
+ UNKNOWN_SKU: new ApiError("Unknown SKU", 10027),
+ UNKNOWN_STORE_LISTING: new ApiError("Unknown Store Listing", 10028),
+ UNKNOWN_ENTITLEMENT: new ApiError("Unknown entitlement", 10029),
+ UNKNOWN_BUILD: new ApiError("Unknown build", 10030),
+ UNKNOWN_LOBBY: new ApiError("Unknown lobby", 10031),
+ UNKNOWN_BRANCH: new ApiError("Unknown branch", 10032),
+ UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError("Unknown store directory layout", 10033),
+ UNKNOWN_REDISTRIBUTABLE: new ApiError("Unknown redistributable", 10036),
+ UNKNOWN_GIFT_CODE: new ApiError("Unknown gift code", 10038),
+ UNKNOWN_STREAM: new ApiError("Unknown stream", 10049),
+ UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError("Unknown premium server subscribe cooldown", 10050),
+ UNKNOWN_GUILD_TEMPLATE: new ApiError("Unknown guild template", 10057),
+ UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError("Unknown discoverable server category", 10059),
+ UNKNOWN_STICKER: new ApiError("Unknown sticker", 10060),
+ UNKNOWN_INTERACTION: new ApiError("Unknown interaction", 10062),
+ UNKNOWN_APPLICATION_COMMAND: new ApiError("Unknown application command", 10063),
+ UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError("Unknown application command permissions", 10066),
+ UNKNOWN_STAGE_INSTANCE: new ApiError("Unknown Stage Instance", 10067),
+ UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError("Unknown Guild Member Verification Form", 10068),
+ UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError("Unknown Guild Welcome Screen", 10069),
+ UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError("Unknown Guild Scheduled Event", 10070),
+ UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071),
+ UNKNOWN_CONSENT: new ApiError("Unknown consent", 10080, 404),
+ UNKNOWN_CONSENT_GRANT: new ApiError("Unknown consent grant", 10081, 404),
+ BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001),
+ BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002),
+ EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError("Explicit content cannot be sent to the desired recipient(s)", 20009),
+ ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError("You are not authorized to perform this action on this application", 20012),
+ SLOWMODE_RATE_LIMIT: new ApiError("This action cannot be performed due to slowmode rate limit", 20016),
+ ONLY_OWNER: new ApiError("Only the owner of this account can perform this action", 20018),
+ ANNOUNCEMENT_RATE_LIMITS: new ApiError("This message cannot be edited due to announcement rate limits", 20022),
+ CHANNEL_WRITE_RATELIMIT: new ApiError("The channel you are writing has hit the write rate limit", 20028),
+ WORDS_NOT_ALLOWED: new ApiError("Your Stage topic, server name, server description, or channel names contain words that are not allowed", 20031),
+ GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError("Guild premium subscription level too low", 20035),
+ MAXIMUM_GUILDS: new ApiError("Maximum number of guilds reached ({})", 30001, undefined, ["100"]),
+ MAXIMUM_FRIENDS: new ApiError("Maximum number of friends reached ({})", 30002, undefined, ["1000"]),
+ MAXIMUM_PINS: new ApiError("Maximum number of pins reached for the channel ({})", 30003, undefined, ["50"]),
+ MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, ["10"]),
+ MAXIMUM_ROLES: new ApiError("Maximum number of guild roles reached ({})", 30005, undefined, ["250"]),
+ MAXIMUM_WEBHOOKS: new ApiError("Maximum number of webhooks reached ({})", 30007, undefined, ["10"]),
+ MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError("Maximum number of emojis reached", 30008),
+ MAXIMUM_REACTIONS: new ApiError("Maximum number of reactions reached ({})", 30010, undefined, ["20"]),
+ MAXIMUM_CHANNELS: new ApiError("Maximum number of guild channels reached ({})", 30013, undefined, ["500"]),
+ MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, ["10"]),
+ MAXIMUM_INVITES: new ApiError("Maximum number of invites reached ({})", 30016, undefined, ["1000"]),
+ MAXIMUM_ANIMATED_EMOJIS: new ApiError("Maximum number of animated emojis reached", 30018),
+ MAXIMUM_SERVER_MEMBERS: new ApiError("Maximum number of server members reached", 30019),
+ MAXIMUM_SERVER_CATEGORIES: new ApiError("Maximum number of server categories has been reached ({})", 30030, undefined, ["5"]),
+ GUILD_ALREADY_HAS_TEMPLATE: new ApiError("Guild already has a template", 30031),
+ MAXIMUM_THREAD_PARTICIPANTS: new ApiError("Max number of thread participants has been reached", 30033),
+ MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError("Maximum number of bans for non-guild members have been exceeded", 30035),
+ MAXIMUM_BANS_FETCHES: new ApiError("Maximum number of bans fetches has been reached", 30037),
+ MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039),
+ MAXIMUM_PRUNE_REQUESTS: new ApiError("Maximum number of prune requests has been reached. Try again later", 30040),
+ UNAUTHORIZED: new ApiError("Unauthorized. Provide a valid token and try again", 40001),
+ ACCOUNT_VERIFICATION_REQUIRED: new ApiError("You need to verify your account in order to perform this action", 40002),
+ OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError("You are opening direct messages too fast", 40003),
+ REQUEST_ENTITY_TOO_LARGE: new ApiError("Request entity too large. Try sending something smaller in size", 40005),
+ FEATURE_TEMPORARILY_DISABLED: new ApiError("This feature has been temporarily disabled server-side", 40006),
+ USER_BANNED: new ApiError("The user is banned from this guild", 40007),
+ CONNECTION_REVOKED: new ApiError("The connection has been revoked", 40012, 400),
+ TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError("Target user is not connected to voice", 40032),
+ ALREADY_CROSSPOSTED: new ApiError("This message has already been crossposted", 40033),
+ APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError("An application command with that name already exists", 40041),
+ MISSING_ACCESS: new ApiError("Missing access", 50001),
+ INVALID_ACCOUNT_TYPE: new ApiError("Invalid account type", 50002),
+ CANNOT_EXECUTE_ON_DM: new ApiError("Cannot execute action on a DM channel", 50003),
+ EMBED_DISABLED: new ApiError("Widget Disabled", 50004),
+ CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError("Cannot edit a message authored by another user", 50005),
+ CANNOT_SEND_EMPTY_MESSAGE: new ApiError("Cannot send an empty message", 50006),
+ CANNOT_MESSAGE_USER: new ApiError("Cannot send messages to this user", 50007),
+ CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError("Cannot send messages in a voice channel", 50008),
+ CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError("Channel verification level is too high for you to gain access", 50009),
+ OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010),
+ MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011),
+ INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012),
+ MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action ({})", 50013, undefined, [""]),
+ INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014),
+ NOTE_TOO_LONG: new ApiError("Note was too long", 50015),
+ INVALID_BULK_DELETE_QUANTITY: new ApiError("Provided too few or too many messages to delete. Must provide at least {} and fewer than {} messages to delete", 50016, undefined, [
+ "2",
+ "100",
+ ]),
+ CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError("A message can only be pinned to the channel it was sent in", 50019),
+ INVALID_OR_TAKEN_INVITE_CODE: new ApiError("Invite code was either invalid or taken", 50020),
+ CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError("Cannot execute action on a system message", 50021),
+ CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError("Cannot execute action on this channel type", 50024),
+ INVALID_OAUTH_TOKEN: new ApiError("Invalid OAuth2 access token provided", 50025),
+ MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError("Missing required OAuth2 scope", 50026),
+ INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError("Invalid webhook token provided", 50027),
+ INVALID_ROLE: new ApiError("Invalid role", 50028),
+ INVALID_RECIPIENT: new ApiError("Invalid Recipient(s)", 50033),
+ BULK_DELETE_MESSAGE_TOO_OLD: new ApiError("A message provided was too old to bulk delete", 50034),
+ INVALID_FORM_BODY: new ApiError("Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided", 50035),
+ INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError("An invite was accepted to a guild the application's bot is not in", 50036),
+ INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041),
+ FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045),
+ INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046),
+ CANNOT_SELF_REDEEM_GIFT: new ApiError("Cannot self-redeem this gift", 50054),
+ PAYMENT_SOURCE_REQUIRED: new ApiError("Payment source required to redeem gift", 50070),
+ CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError("Cannot delete a channel required for Community guilds", 50074),
+ INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081),
+ CANNOT_EDIT_ARCHIVED_THREAD: new ApiError("Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread", 50083),
+ INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError("Invalid thread notification settings", 50084),
+ BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError("before value is earlier than the thread creation date", 50085),
+ SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError("This server is not available in your location", 50095),
+ SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError("This server needs monetization enabled in order to perform this action", 50097),
+ TWO_FACTOR_REQUIRED: new ApiError("Two factor is required for this operation", 60003),
+ NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError("No users with DiscordTag exist", 80004),
+ REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001),
+ RESOURCE_OVERLOADED: new ApiError("API resource is currently overloaded. Try again a little later", 130000),
+ STAGE_ALREADY_OPEN: new ApiError("The Stage is already open", 150006),
+ THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004),
+ THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005),
+ MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError("Maximum number of active threads reached", 160006),
+ MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError("Maximum number of active announcement threads reached", 160007),
+ INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError("Invalid JSON for uploaded Lottie file", 170001),
+ LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError("Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", 170002),
+ STICKER_MAXIMUM_FRAMERATE: new ApiError("Sticker maximum framerate exceeded", 170003),
+ STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, ["1000"]),
+ LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError("Lottie animation maximum dimensions exceeded", 170005),
+ STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError("Sticker frame rate is either too small or too large", 170006),
+ STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError("Sticker animation duration exceeds maximum of {} seconds", 170007, undefined, ["5"]),
+ AUTOMODERATOR_BLOCK: new ApiError("Message was blocked by automatic moderation", 200000),
+ BULK_BAN_FAILED: new ApiError("Failed to ban users", 500000),
- //Other errors
- UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404),
+ //Other errors
+ UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404),
};
export const TicketFlags = {
- RESOLVED: 1 << 0,
- ARCHIVED: 1 << 1,
+ RESOLVED: 1 << 0,
+ ARCHIVED: 1 << 1,
} as const;
/**
* An error encountered while performing an API request (Spacebar only). Here are the potential errors:
*/
export const SpacebarApiErrors = {
- MANUALLY_TRIGGERED_ERROR: new ApiError(
- "This is an artificial error",
- 1,
- 500,
- ),
- PREMIUM_DISABLED_FOR_GUILD: new ApiError(
- "This guild cannot be boosted",
- 25001,
- ),
- NO_FURTHER_PREMIUM: new ApiError(
- "This guild does not receive further boosts",
- 25002,
- ),
- GUILD_PREMIUM_DISABLED_FOR_YOU: new ApiError(
- "This guild cannot be boosted by you",
- 25003,
- 403,
- ),
- CANNOT_FRIEND_SELF: new ApiError("Cannot friend oneself", 25009),
- USER_SPECIFIC_INVITE_WRONG_RECIPIENT: new ApiError(
- "This invite is not meant for you",
- 25010,
- ),
- USER_SPECIFIC_INVITE_FAILED: new ApiError("Failed to invite user", 25011),
- CANNOT_MODIFY_USER_GROUP: new ApiError(
- "This user cannot manipulate this group",
- 25050,
- 403,
- ),
- CANNOT_REMOVE_SELF_FROM_GROUP: new ApiError(
- "This user cannot remove oneself from user group",
- 25051,
- ),
- CANNOT_BAN_OPERATOR: new ApiError(
- "Non-OPERATOR cannot ban OPERATOR from instance",
- 25052,
- ),
- CANNOT_LEAVE_GUILD: new ApiError(
- "You are not allowed to leave guilds that you joined by yourself",
- 25059,
- 403,
- ),
- EDITS_DISABLED: new ApiError(
- "You are not allowed to edit your own messages",
- 25060,
- 403,
- ),
- DELETE_MESSAGE_DISABLED: new ApiError(
- "You are not allowed to delete your own messages",
- 25061,
- 403,
- ),
- FEATURE_PERMANENTLY_DISABLED: new ApiError(
- "This feature has been disabled server-side",
- 45006,
- 501,
- ),
- FEATURE_IS_IMMUTABLE: new ApiError(
- "The feature ({}) cannot be edited.",
- 45007,
- 403,
- ),
- MISSING_RIGHTS: new ApiError(
- "You lack rights to perform that action ({})",
- 50013,
- undefined,
- [""],
- ),
- CANNOT_REPLACE_BY_BACKFILL: new ApiError(
- "Cannot backfill to message ID that already exists",
- 55002,
- 409,
- ),
- CANNOT_BACKFILL_TO_THE_FUTURE: new ApiError(
- "You cannot backfill messages in the future",
- 55003,
- ),
- CANNOT_GRANT_PERMISSIONS_EXCEEDING_RIGHTS: new ApiError(
- "You cannot grant permissions exceeding your own rights",
- 50050,
- ),
- ROUTES_LOOPING: new ApiError(
- "Loops in the route definition ({})",
- 50060,
- undefined,
- [""],
- ),
- CANNOT_REMOVE_ROUTE: new ApiError(
- "Cannot remove message route while it is in effect and being used",
- 50061,
- ),
+ MANUALLY_TRIGGERED_ERROR: new ApiError("This is an artificial error", 1, 500),
+ PREMIUM_DISABLED_FOR_GUILD: new ApiError("This guild cannot be boosted", 25001),
+ NO_FURTHER_PREMIUM: new ApiError("This guild does not receive further boosts", 25002),
+ GUILD_PREMIUM_DISABLED_FOR_YOU: new ApiError("This guild cannot be boosted by you", 25003, 403),
+ CANNOT_FRIEND_SELF: new ApiError("Cannot friend oneself", 25009),
+ USER_SPECIFIC_INVITE_WRONG_RECIPIENT: new ApiError("This invite is not meant for you", 25010),
+ USER_SPECIFIC_INVITE_FAILED: new ApiError("Failed to invite user", 25011),
+ CANNOT_MODIFY_USER_GROUP: new ApiError("This user cannot manipulate this group", 25050, 403),
+ CANNOT_REMOVE_SELF_FROM_GROUP: new ApiError("This user cannot remove oneself from user group", 25051),
+ CANNOT_BAN_OPERATOR: new ApiError("Non-OPERATOR cannot ban OPERATOR from instance", 25052),
+ CANNOT_LEAVE_GUILD: new ApiError("You are not allowed to leave guilds that you joined by yourself", 25059, 403),
+ EDITS_DISABLED: new ApiError("You are not allowed to edit your own messages", 25060, 403),
+ DELETE_MESSAGE_DISABLED: new ApiError("You are not allowed to delete your own messages", 25061, 403),
+ FEATURE_PERMANENTLY_DISABLED: new ApiError("This feature has been disabled server-side", 45006, 501),
+ FEATURE_IS_IMMUTABLE: new ApiError("The feature ({}) cannot be edited.", 45007, 403),
+ MISSING_RIGHTS: new ApiError("You lack rights to perform that action ({})", 50013, undefined, [""]),
+ CANNOT_REPLACE_BY_BACKFILL: new ApiError("Cannot backfill to message ID that already exists", 55002, 409),
+ CANNOT_BACKFILL_TO_THE_FUTURE: new ApiError("You cannot backfill messages in the future", 55003),
+ CANNOT_GRANT_PERMISSIONS_EXCEEDING_RIGHTS: new ApiError("You cannot grant permissions exceeding your own rights", 50050),
+ ROUTES_LOOPING: new ApiError("Loops in the route definition ({})", 50060, undefined, [""]),
+ CANNOT_REMOVE_ROUTE: new ApiError("Cannot remove message route while it is in effect and being used", 50061),
};
/**
@@ -1131,7 +737,7 @@ export const MembershipStates = ["INSERTED", "INVITED", "ACCEPTED"];
export const WebhookTypes = ["Custom", "Incoming", "Channel Follower"];
function keyMirror(arr: string[]) {
- const tmp = Object.create(null);
- for (const value of arr) tmp[value] = value;
- return tmp;
+ const tmp = Object.create(null);
+ for (const value of arr) tmp[value] = value;
+ return tmp;
}