Merge branch 'master' into feat/refactorIdentify
This commit is contained in:
commit
8a3989c297
@ -10,7 +10,8 @@
|
|||||||
"EMAIL_INVALID": "Invalid Email",
|
"EMAIL_INVALID": "Invalid Email",
|
||||||
"EMAIL_ALREADY_REGISTERED": "Email is already registered",
|
"EMAIL_ALREADY_REGISTERED": "Email is already registered",
|
||||||
"DATE_OF_BIRTH_UNDERAGE": "You need to be {{years}} years or older",
|
"DATE_OF_BIRTH_UNDERAGE": "You need to be {{years}} years or older",
|
||||||
"CONSENT_REQUIRED": "You must agree to the Terms of Service and Privacy Policy.",
|
"PASSWORD_REQUIREMENTS_MIN_LENGTH": "Must be at least {{min}} characters long.",
|
||||||
|
"CONSENT_REQUIRED": "You must agree to the Terms of Service and Privacy Policy.",
|
||||||
"USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another"
|
"USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14212
assets/openapi.json
14212
assets/openapi.json
File diff suppressed because it is too large
Load Diff
496357
assets/schemas.json
496357
assets/schemas.json
File diff suppressed because it is too large
Load Diff
24
package-lock.json
generated
24
package-lock.json
generated
@ -38,6 +38,7 @@
|
|||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"murmurhash-js": "^1.0.0",
|
||||||
"node-2fa": "^2.0.3",
|
"node-2fa": "^2.0.3",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"node-os-utils": "^1.3.7",
|
"node-os-utils": "^1.3.7",
|
||||||
@ -64,6 +65,7 @@
|
|||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
"@types/morgan": "^1.9.3",
|
"@types/morgan": "^1.9.3",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
|
"@types/murmurhash-js": "^1.0.4",
|
||||||
"@types/node": "^18.7.20",
|
"@types/node": "^18.7.20",
|
||||||
"@types/node-fetch": "^2.6.2",
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/node-os-utils": "^1.3.0",
|
"@types/node-os-utils": "^1.3.0",
|
||||||
@ -2017,6 +2019,12 @@
|
|||||||
"@types/express": "*"
|
"@types/express": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/murmurhash-js": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/murmurhash-js/-/murmurhash-js-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-lTFERysuLTbtxv/GTcBDV3j3UR1C9WTNiU7rY9QvEUn1G60q7HRXj6c+eFGo0ymMFOlb6kqZsO2WYyzc15oGHA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/needle": {
|
"node_modules/@types/needle": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/needle/-/needle-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/needle/-/needle-3.2.0.tgz",
|
||||||
@ -5780,6 +5788,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/murmurhash-js": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw=="
|
||||||
|
},
|
||||||
"node_modules/mz": {
|
"node_modules/mz": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
@ -9755,6 +9768,12 @@
|
|||||||
"@types/express": "*"
|
"@types/express": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/murmurhash-js": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/murmurhash-js/-/murmurhash-js-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-lTFERysuLTbtxv/GTcBDV3j3UR1C9WTNiU7rY9QvEUn1G60q7HRXj6c+eFGo0ymMFOlb6kqZsO2WYyzc15oGHA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/needle": {
|
"@types/needle": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/needle/-/needle-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/needle/-/needle-3.2.0.tgz",
|
||||||
@ -12602,6 +12621,11 @@
|
|||||||
"minimatch": "^3.0.4"
|
"minimatch": "^3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"murmurhash-js": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw=="
|
||||||
|
},
|
||||||
"mz": {
|
"mz": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
"@types/morgan": "^1.9.3",
|
"@types/morgan": "^1.9.3",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
|
"@types/murmurhash-js": "^1.0.4",
|
||||||
"@types/node": "^18.7.20",
|
"@types/node": "^18.7.20",
|
||||||
"@types/node-fetch": "^2.6.2",
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/node-os-utils": "^1.3.0",
|
"@types/node-os-utils": "^1.3.0",
|
||||||
@ -94,6 +95,7 @@
|
|||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"murmurhash-js": "^1.0.0",
|
||||||
"node-2fa": "^2.0.3",
|
"node-2fa": "^2.0.3",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"node-os-utils": "^1.3.7",
|
"node-os-utils": "^1.3.7",
|
||||||
|
@ -27,34 +27,46 @@ require("missing-native-js-functions");
|
|||||||
|
|
||||||
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
|
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
|
||||||
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
||||||
let schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
|
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
|
||||||
|
// const specification = JSON.parse(
|
||||||
for (var schema in schemas) {
|
// fs.readFileSync(openapiPath, { encoding: "utf8" }),
|
||||||
const part = schemas[schema];
|
// );
|
||||||
for (var key in part.properties) {
|
let specification = {
|
||||||
if (part.properties[key].anyOf) {
|
openapi: "3.1.0",
|
||||||
const nullIndex = part.properties[key].anyOf.findIndex(
|
info: {
|
||||||
(x) => x.type == "null",
|
title: "Spacebar Server",
|
||||||
);
|
description:
|
||||||
if (nullIndex != -1) {
|
"Spacebar is a free open source selfhostable discord compatible chat, voice and video platform",
|
||||||
part.properties[key].nullable = true;
|
license: {
|
||||||
part.properties[key].anyOf.splice(nullIndex, 1);
|
name: "AGPLV3",
|
||||||
|
url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
|
||||||
if (part.properties[key].anyOf.length == 1) {
|
},
|
||||||
Object.assign(
|
version: "1.0.0",
|
||||||
part.properties[key],
|
},
|
||||||
part.properties[key].anyOf[0],
|
externalDocs: {
|
||||||
);
|
description: "Spacebar Docs",
|
||||||
delete part.properties[key].anyOf;
|
url: "https://docs.spacebar.chat",
|
||||||
}
|
},
|
||||||
}
|
servers: [
|
||||||
}
|
{
|
||||||
}
|
url: "https://old.server.spacebar.chat/api/",
|
||||||
}
|
description: "Official Spacebar Instance",
|
||||||
|
},
|
||||||
const specification = JSON.parse(
|
],
|
||||||
fs.readFileSync(openapiPath, { encoding: "utf8" }),
|
components: {
|
||||||
);
|
securitySchemes: {
|
||||||
|
bearer: {
|
||||||
|
type: "http",
|
||||||
|
scheme: "bearer",
|
||||||
|
description: "Bearer/Bot prefixes are not required.",
|
||||||
|
bearerFormat: "JWT",
|
||||||
|
in: "header",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: [],
|
||||||
|
paths: {},
|
||||||
|
};
|
||||||
|
|
||||||
function combineSchemas(schemas) {
|
function combineSchemas(schemas) {
|
||||||
var definitions = {};
|
var definitions = {};
|
||||||
@ -72,6 +84,11 @@ function combineSchemas(schemas) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const key in definitions) {
|
for (const key in definitions) {
|
||||||
|
const reg = new RegExp(/^[a-zA-Z0-9.\-_]+$/, "gm");
|
||||||
|
if (!reg.test(key)) {
|
||||||
|
console.error(`Invalid schema name: ${key} (${reg.test(key)})`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
specification.components = specification.components || {};
|
specification.components = specification.components || {};
|
||||||
specification.components.schemas =
|
specification.components.schemas =
|
||||||
specification.components.schemas || {};
|
specification.components.schemas || {};
|
||||||
@ -102,30 +119,20 @@ function getTag(key) {
|
|||||||
function apiRoutes() {
|
function apiRoutes() {
|
||||||
const routes = getRouteDescriptions();
|
const routes = getRouteDescriptions();
|
||||||
|
|
||||||
const tags = Array.from(routes.keys()).map((x) => getTag(x));
|
// populate tags
|
||||||
specification.tags = specification.tags || [];
|
const tags = Array.from(routes.keys())
|
||||||
specification.tags = [...specification.tags.map((x) => x.name), ...tags]
|
.map((x) => getTag(x))
|
||||||
.unique()
|
.sort((a, b) => a.localeCompare(b));
|
||||||
.map((x) => ({ name: x }));
|
specification.tags = tags.unique().map((x) => ({ name: x }));
|
||||||
|
|
||||||
specification.components = specification.components || {};
|
|
||||||
specification.components.securitySchemes = {
|
|
||||||
bearer: {
|
|
||||||
type: "http",
|
|
||||||
scheme: "bearer",
|
|
||||||
description: "Bearer/Bot prefixes are not required.",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
routes.forEach((route, pathAndMethod) => {
|
routes.forEach((route, pathAndMethod) => {
|
||||||
const [p, method] = pathAndMethod.split("|");
|
const [p, method] = pathAndMethod.split("|");
|
||||||
const path = p.replace(/:(\w+)/g, "{$1}");
|
const path = p.replace(/:(\w+)/g, "{$1}");
|
||||||
|
|
||||||
specification.paths = specification.paths || {};
|
|
||||||
let obj = specification.paths[path]?.[method] || {};
|
let obj = specification.paths[path]?.[method] || {};
|
||||||
obj["x-right-required"] = route.right;
|
obj["x-right-required"] = route.right;
|
||||||
obj["x-permission-required"] = route.permission;
|
obj["x-permission-required"] = route.permission;
|
||||||
obj["x-fires-event"] = route.test?.event;
|
obj["x-fires-event"] = route.event;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!NO_AUTHORIZATION_ROUTES.some((x) => {
|
!NO_AUTHORIZATION_ROUTES.some((x) => {
|
||||||
@ -136,48 +143,56 @@ function apiRoutes() {
|
|||||||
obj.security = [{ bearer: [] }];
|
obj.security = [{ bearer: [] }];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.body) {
|
if (route.description) obj.description = route.description;
|
||||||
|
if (route.summary) obj.summary = route.summary;
|
||||||
|
if (route.deprecated) obj.deprecated = route.deprecated;
|
||||||
|
|
||||||
|
if (route.requestBody) {
|
||||||
obj.requestBody = {
|
obj.requestBody = {
|
||||||
required: true,
|
required: true,
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: { $ref: `#/components/schemas/${route.body}` },
|
schema: {
|
||||||
|
$ref: `#/components/schemas/${route.requestBody}`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}.merge(obj.requestBody);
|
}.merge(obj.requestBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.test?.response) {
|
if (route.responses) {
|
||||||
const status = route.test.response.status || 200;
|
for (const [k, v] of Object.entries(route.responses)) {
|
||||||
let schema = {
|
let schema = {
|
||||||
allOf: [
|
$ref: `#/components/schemas/${v.body}`,
|
||||||
{
|
};
|
||||||
$ref: `#/components/schemas/${route.test.response.body}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
example: route.test.body,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
if (!route.test.body) schema = schema.allOf[0];
|
|
||||||
|
|
||||||
obj.responses = {
|
obj.responses = {
|
||||||
[status]: {
|
[k]: {
|
||||||
...(route.test.response.body
|
...(v.body
|
||||||
? {
|
? {
|
||||||
description:
|
description:
|
||||||
obj?.responses?.[status]?.description || "",
|
obj?.responses?.[k]?.description || "",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: schema,
|
schema: schema,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
: {
|
||||||
: {}),
|
description: "No description available",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}.merge(obj.responses);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj.responses = {
|
||||||
|
default: {
|
||||||
|
description: "No description available",
|
||||||
},
|
},
|
||||||
}.merge(obj.responses);
|
};
|
||||||
delete obj.responses.default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handles path parameters
|
||||||
if (p.includes(":")) {
|
if (p.includes(":")) {
|
||||||
obj.parameters = p.match(/:\w+/g)?.map((x) => ({
|
obj.parameters = p.match(/:\w+/g)?.map((x) => ({
|
||||||
name: x.replace(":", ""),
|
name: x.replace(":", ""),
|
||||||
@ -187,16 +202,33 @@ function apiRoutes() {
|
|||||||
description: x.replace(":", ""),
|
description: x.replace(":", ""),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (route.query) {
|
||||||
|
// map to array
|
||||||
|
const query = Object.entries(route.query).map(([k, v]) => ({
|
||||||
|
name: k,
|
||||||
|
in: "query",
|
||||||
|
required: v.required,
|
||||||
|
schema: { type: v.type },
|
||||||
|
description: v.description,
|
||||||
|
}));
|
||||||
|
|
||||||
|
obj.parameters = [...(obj.parameters || []), ...query];
|
||||||
|
}
|
||||||
|
|
||||||
obj.tags = [...(obj.tags || []), getTag(p)].unique();
|
obj.tags = [...(obj.tags || []), getTag(p)].unique();
|
||||||
|
|
||||||
specification.paths[path] = {
|
specification.paths[path] = Object.assign(
|
||||||
...specification.paths[path],
|
specification.paths[path] || {},
|
||||||
[method]: obj,
|
{
|
||||||
};
|
[method]: obj,
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
console.log("Generating OpenAPI Specification...");
|
||||||
combineSchemas(schemas);
|
combineSchemas(schemas);
|
||||||
apiRoutes();
|
apiRoutes();
|
||||||
|
|
||||||
|
@ -34,9 +34,7 @@ const settings = {
|
|||||||
noExtraProps: true,
|
noExtraProps: true,
|
||||||
defaultProps: false,
|
defaultProps: false,
|
||||||
};
|
};
|
||||||
const compilerOptions = {
|
|
||||||
strictNullChecks: true,
|
|
||||||
};
|
|
||||||
const Excluded = [
|
const Excluded = [
|
||||||
"DefaultSchema",
|
"DefaultSchema",
|
||||||
"Schema",
|
"Schema",
|
||||||
@ -57,16 +55,10 @@ const Excluded = [
|
|||||||
"PropertiesSchema",
|
"PropertiesSchema",
|
||||||
"AsyncSchema",
|
"AsyncSchema",
|
||||||
"AnySchema",
|
"AnySchema",
|
||||||
|
"SMTPConnection.CustomAuthenticationResponse",
|
||||||
|
"TransportMakeRequestResponse",
|
||||||
];
|
];
|
||||||
|
|
||||||
function modify(obj) {
|
|
||||||
for (var k in obj) {
|
|
||||||
if (typeof obj[k] === "object" && obj[k] !== null) {
|
|
||||||
modify(obj[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const program = TJS.programFromConfig(
|
const program = TJS.programFromConfig(
|
||||||
path.join(__dirname, "..", "tsconfig.json"),
|
path.join(__dirname, "..", "tsconfig.json"),
|
||||||
@ -75,14 +67,14 @@ function main() {
|
|||||||
const generator = TJS.buildGenerator(program, settings);
|
const generator = TJS.buildGenerator(program, settings);
|
||||||
if (!generator || !program) return;
|
if (!generator || !program) return;
|
||||||
|
|
||||||
let schemas = generator
|
let schemas = generator.getUserSymbols().filter((x) => {
|
||||||
.getUserSymbols()
|
return (
|
||||||
.filter(
|
(x.endsWith("Schema") ||
|
||||||
(x) =>
|
x.endsWith("Response") ||
|
||||||
(x.endsWith("Schema") || x.endsWith("Response")) &&
|
x.startsWith("API")) &&
|
||||||
!Excluded.includes(x),
|
!Excluded.includes(x)
|
||||||
);
|
);
|
||||||
console.log(schemas);
|
});
|
||||||
|
|
||||||
var definitions = {};
|
var definitions = {};
|
||||||
|
|
||||||
@ -109,32 +101,12 @@ function main() {
|
|||||||
delete part.properties[key];
|
delete part.properties[key];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (part.properties[key].anyOf) {
|
|
||||||
// const nullIndex = part.properties[key].anyOf.findIndex(
|
|
||||||
// (x) => x.type == "null",
|
|
||||||
// );
|
|
||||||
// if (nullIndex != -1) {
|
|
||||||
// part.properties[key].nullable = true;
|
|
||||||
// part.properties[key].anyOf.splice(nullIndex, 1);
|
|
||||||
|
|
||||||
// if (part.properties[key].anyOf.length == 1) {
|
|
||||||
// Object.assign(
|
|
||||||
// part.properties[key],
|
|
||||||
// part.properties[key].anyOf[0],
|
|
||||||
// );
|
|
||||||
// delete part.properties[key].anyOf;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
definitions = { ...definitions, [name]: { ...part } };
|
definitions = { ...definitions, [name]: { ...part } };
|
||||||
}
|
}
|
||||||
|
|
||||||
modify(definitions);
|
|
||||||
|
|
||||||
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
|
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,80 +1,64 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const path = require("path");
|
||||||
|
const { traverseDirectory } = require("lambert-server");
|
||||||
|
const RouteUtility = require("../../dist/api/util/handlers/route.js");
|
||||||
|
|
||||||
|
const methods = ["get", "post", "put", "delete", "patch"];
|
||||||
|
const routes = new Map();
|
||||||
|
let currentFile = "";
|
||||||
|
let currentPath = "";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
For some reason, if a route exports multiple functions, it won't be registered here!
|
||||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
If someone could fix that I'd really appreciate it, but for now just, don't do that :p
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published
|
|
||||||
by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { traverseDirectory } = require("lambert-server");
|
const proxy = (file, method, prefix, path, ...args) => {
|
||||||
const path = require("path");
|
const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true);
|
||||||
const express = require("express");
|
if (!opts)
|
||||||
const RouteUtility = require("../../dist/api/util/handlers/route.js");
|
return console.error(
|
||||||
const Router = express.Router;
|
`${file} has route without route() description middleware`,
|
||||||
|
|
||||||
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);
|
|
||||||
opts.file = sourceFile;
|
|
||||||
// console.log(method, urlPath, opts);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function routeOptions(opts) {
|
console.log(prefix + path + " - " + method);
|
||||||
return opts;
|
opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts");
|
||||||
}
|
routes.set(prefix + path + "|" + method, opts());
|
||||||
|
};
|
||||||
|
|
||||||
RouteUtility.route = routeOptions;
|
express.Router = () => {
|
||||||
|
return Object.fromEntries(
|
||||||
|
methods.map((method) => [
|
||||||
|
method,
|
||||||
|
proxy.bind(null, currentFile, method, currentPath),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
express.Router = (opts) => {
|
RouteUtility.route = (opts) => {
|
||||||
const path = currentPath;
|
const func = function () {
|
||||||
const file = currentFile;
|
return opts;
|
||||||
const router = Router(opts);
|
};
|
||||||
|
func.prototype.OPTS_MARKER = true;
|
||||||
for (const method of methods) {
|
return func;
|
||||||
router[method] = registerPath.bind(null, file, method, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return router;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function getRouteDescriptions() {
|
module.exports = function getRouteDescriptions() {
|
||||||
const root = path.join(__dirname, "..", "..", "dist", "api", "routes", "/");
|
const root = path.join(__dirname, "..", "..", "dist", "api", "routes", "/");
|
||||||
traverseDirectory({ dirname: root, recursive: true }, (file) => {
|
traverseDirectory({ dirname: root, recursive: true }, (file) => {
|
||||||
currentFile = 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
|
currentPath = file.replace(root.slice(0, -1), "");
|
||||||
path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
|
currentPath = currentPath.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path
|
||||||
if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path
|
currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
|
||||||
currentPath = path;
|
if (currentPath.endsWith("/index"))
|
||||||
|
currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path
|
||||||
|
|
||||||
try {
|
try {
|
||||||
require(file);
|
require(file);
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.error("error loading file " + file, error);
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
};
|
};
|
||||||
|
@ -16,78 +16,99 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Application,
|
Application,
|
||||||
generateToken,
|
|
||||||
User,
|
|
||||||
BotModifySchema,
|
BotModifySchema,
|
||||||
handleFile,
|
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
|
User,
|
||||||
|
createAppBotUser,
|
||||||
|
generateToken,
|
||||||
|
handleFile,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { verifyToken } from "node-2fa";
|
import { verifyToken } from "node-2fa";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.post("/", route({}), async (req: Request, res: Response) => {
|
router.post(
|
||||||
const app = await Application.findOneOrFail({
|
"/",
|
||||||
where: { id: req.params.id },
|
route({
|
||||||
relations: ["owner"],
|
responses: {
|
||||||
});
|
204: {
|
||||||
|
body: "TokenOnlyResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const app = await Application.findOneOrFail({
|
||||||
|
where: { id: req.params.id },
|
||||||
|
relations: ["owner"],
|
||||||
|
});
|
||||||
|
|
||||||
if (app.owner.id != req.user_id)
|
if (app.owner.id != req.user_id)
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||||
|
|
||||||
const user = await User.register({
|
const user = await createAppBotUser(app, req);
|
||||||
username: app.name,
|
|
||||||
password: undefined,
|
|
||||||
id: app.id,
|
|
||||||
req,
|
|
||||||
});
|
|
||||||
|
|
||||||
user.id = app.id;
|
res.send({
|
||||||
user.premium_since = new Date();
|
token: await generateToken(user.id),
|
||||||
user.bot = true;
|
}).status(204);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await user.save();
|
router.post(
|
||||||
|
"/reset",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "TokenResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const bot = await User.findOneOrFail({ where: { id: req.params.id } });
|
||||||
|
const owner = await User.findOneOrFail({ where: { id: req.user_id } });
|
||||||
|
|
||||||
// flags is NaN here?
|
if (owner.id != req.user_id)
|
||||||
app.assign({ bot: user, flags: app.flags || 0 });
|
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||||
|
|
||||||
await app.save();
|
if (
|
||||||
|
owner.totp_secret &&
|
||||||
|
(!req.body.code || verifyToken(owner.totp_secret, req.body.code))
|
||||||
|
)
|
||||||
|
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||||
|
|
||||||
res.send({
|
bot.data = { hash: undefined, valid_tokens_since: new Date() };
|
||||||
token: await generateToken(user.id),
|
|
||||||
}).status(204);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/reset", route({}), async (req: Request, res: Response) => {
|
await bot.save();
|
||||||
const bot = await User.findOneOrFail({ where: { id: req.params.id } });
|
|
||||||
const owner = await User.findOneOrFail({ where: { id: req.user_id } });
|
|
||||||
|
|
||||||
if (owner.id != req.user_id)
|
const token = await generateToken(bot.id);
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
|
||||||
|
|
||||||
if (
|
res.json({ token }).status(200);
|
||||||
owner.totp_secret &&
|
},
|
||||||
(!req.body.code || verifyToken(owner.totp_secret, req.body.code))
|
);
|
||||||
)
|
|
||||||
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
|
||||||
|
|
||||||
bot.data = { hash: undefined, valid_tokens_since: new Date() };
|
|
||||||
|
|
||||||
await bot.save();
|
|
||||||
|
|
||||||
const token = await generateToken(bot.id);
|
|
||||||
|
|
||||||
res.json({ token }).status(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "BotModifySchema" }),
|
route({
|
||||||
|
requestBody: "BotModifySchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Application",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as BotModifySchema;
|
const body = req.body as BotModifySchema;
|
||||||
if (!body.avatar?.trim()) delete body.avatar;
|
if (!body.avatar?.trim()) delete body.avatar;
|
||||||
|
@ -16,15 +16,25 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), (req: Request, res: Response) => {
|
router.get(
|
||||||
// TODO:
|
"/",
|
||||||
//const { exclude_consumed } = req.query;
|
route({
|
||||||
res.status(200).send([]);
|
responses: {
|
||||||
});
|
200: {
|
||||||
|
body: "ApplicationEntitlementsResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
(req: Request, res: Response) => {
|
||||||
|
// TODO:
|
||||||
|
//const { exclude_consumed } = req.query;
|
||||||
|
res.status(200).send([]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,32 +16,55 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Application,
|
Application,
|
||||||
DiscordApiErrors,
|
|
||||||
ApplicationModifySchema,
|
ApplicationModifySchema,
|
||||||
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { verifyToken } from "node-2fa";
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
import { verifyToken } from "node-2fa";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const app = await Application.findOneOrFail({
|
"/",
|
||||||
where: { id: req.params.id },
|
route({
|
||||||
relations: ["owner", "bot"],
|
responses: {
|
||||||
});
|
200: {
|
||||||
if (app.owner.id != req.user_id)
|
body: "Application",
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const app = await Application.findOneOrFail({
|
||||||
|
where: { id: req.params.id },
|
||||||
|
relations: ["owner", "bot"],
|
||||||
|
});
|
||||||
|
if (app.owner.id != req.user_id)
|
||||||
|
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||||
|
|
||||||
return res.json(app);
|
return res.json(app);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ApplicationModifySchema" }),
|
route({
|
||||||
|
requestBody: "ApplicationModifySchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Application",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as ApplicationModifySchema;
|
const body = req.body as ApplicationModifySchema;
|
||||||
|
|
||||||
@ -73,23 +96,35 @@ router.patch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post("/delete", route({}), async (req: Request, res: Response) => {
|
router.post(
|
||||||
const app = await Application.findOneOrFail({
|
"/delete",
|
||||||
where: { id: req.params.id },
|
route({
|
||||||
relations: ["bot", "owner"],
|
responses: {
|
||||||
});
|
200: {},
|
||||||
if (app.owner.id != req.user_id)
|
400: {
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const app = await Application.findOneOrFail({
|
||||||
|
where: { id: req.params.id },
|
||||||
|
relations: ["bot", "owner"],
|
||||||
|
});
|
||||||
|
if (app.owner.id != req.user_id)
|
||||||
|
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
app.owner.totp_secret &&
|
app.owner.totp_secret &&
|
||||||
(!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))
|
(!req.body.code ||
|
||||||
)
|
verifyToken(app.owner.totp_secret, req.body.code))
|
||||||
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
)
|
||||||
|
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||||
|
|
||||||
await Application.delete({ id: app.id });
|
await Application.delete({ id: app.id });
|
||||||
|
|
||||||
res.send().status(200);
|
res.send().status(200);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,13 +16,23 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
res.json([]).status(200);
|
"/",
|
||||||
});
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "ApplicationSkusResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
res.json([]).status(200);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,14 +16,24 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
//TODO
|
"/",
|
||||||
res.send([]).status(200);
|
route({
|
||||||
});
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "ApplicationDetectableResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
//TODO
|
||||||
|
res.send([]).status(200);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,28 +16,47 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Application,
|
Application,
|
||||||
ApplicationCreateSchema,
|
ApplicationCreateSchema,
|
||||||
trimSpecial,
|
Config,
|
||||||
User,
|
User,
|
||||||
|
createAppBotUser,
|
||||||
|
trimSpecial,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const results = await Application.find({
|
"/",
|
||||||
where: { owner: { id: req.user_id } },
|
route({
|
||||||
relations: ["owner", "bot"],
|
responses: {
|
||||||
});
|
200: {
|
||||||
res.json(results).status(200);
|
body: "APIApplicationArray",
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const results = await Application.find({
|
||||||
|
where: { owner: { id: req.user_id } },
|
||||||
|
relations: ["owner", "bot"],
|
||||||
|
});
|
||||||
|
res.json(results).status(200);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ApplicationCreateSchema" }),
|
route({
|
||||||
|
requestBody: "ApplicationCreateSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Application",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as ApplicationCreateSchema;
|
const body = req.body as ApplicationCreateSchema;
|
||||||
const user = await User.findOneOrFail({ where: { id: req.user_id } });
|
const user = await User.findOneOrFail({ where: { id: req.user_id } });
|
||||||
@ -51,7 +70,11 @@ router.post(
|
|||||||
flags: 0,
|
flags: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await app.save();
|
// april 14, 2023: discord made bot users be automatically added to all new apps
|
||||||
|
const { autoCreateBotUsers } = Config.get().general;
|
||||||
|
if (autoCreateBotUsers) {
|
||||||
|
await createAppBotUser(app, req);
|
||||||
|
} else await app.save();
|
||||||
|
|
||||||
res.json(app);
|
res.json(app);
|
||||||
},
|
},
|
||||||
|
@ -30,7 +30,18 @@ const router = Router();
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ForgotPasswordSchema" }),
|
route({
|
||||||
|
requestBody: "ForgotPasswordSchema",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorOrCaptchaResponse",
|
||||||
|
},
|
||||||
|
500: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { login, captcha_key } = req.body as ForgotPasswordSchema;
|
const { login, captcha_key } = req.body as ForgotPasswordSchema;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { route, random } from "@spacebar/api";
|
import { random, route } from "@spacebar/api";
|
||||||
import { Config, ValidRegistrationToken } from "@spacebar/util";
|
import { Config, ValidRegistrationToken } from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
@ -25,7 +25,22 @@ export default router;
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ right: "OPERATOR" }),
|
route({
|
||||||
|
query: {
|
||||||
|
count: {
|
||||||
|
type: "number",
|
||||||
|
description:
|
||||||
|
"The number of registration tokens to generate. Defaults to 1.",
|
||||||
|
},
|
||||||
|
length: {
|
||||||
|
type: "number",
|
||||||
|
description:
|
||||||
|
"The length of each registration token. Defaults to 255.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
right: "OPERATOR",
|
||||||
|
responses: { 200: { body: "GenerateRegistrationTokensResponse" } },
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const count = req.query.count ? parseInt(req.query.count as string) : 1;
|
const count = req.query.count ? parseInt(req.query.count as string) : 1;
|
||||||
const length = req.query.length
|
const length = req.query.length
|
||||||
|
@ -16,20 +16,29 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
import { IPAnalysis, getIpAdress, route } from "@spacebar/api";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
import { getIpAdress, IPAnalysis } from "@spacebar/api";
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
//TODO
|
"/",
|
||||||
//Note: It's most likely related to legal. At the moment Discord hasn't finished this too
|
route({
|
||||||
const country_code = (await IPAnalysis(getIpAdress(req))).country_code;
|
responses: {
|
||||||
res.json({
|
200: {
|
||||||
consent_required: false,
|
body: "LocationMetadataResponse",
|
||||||
country_code: country_code,
|
},
|
||||||
promotional_email_opt_in: { required: true, pre_checked: false },
|
},
|
||||||
});
|
}),
|
||||||
});
|
async (req: Request, res: Response) => {
|
||||||
|
//TODO
|
||||||
|
//Note: It's most likely related to legal. At the moment Discord hasn't finished this too
|
||||||
|
const country_code = (await IPAnalysis(getIpAdress(req))).country_code;
|
||||||
|
res.json({
|
||||||
|
consent_required: false,
|
||||||
|
country_code: country_code,
|
||||||
|
promotional_email_opt_in: { required: true, pre_checked: false },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -36,7 +36,17 @@ export default router;
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "LoginSchema" }),
|
route({
|
||||||
|
requestBody: "LoginSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "LoginResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorOrCaptchaResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { login, password, captcha_key, undelete } =
|
const { login, password, captcha_key, undelete } =
|
||||||
req.body as LoginSchema;
|
req.body as LoginSchema;
|
||||||
|
@ -22,14 +22,25 @@ import { Request, Response, Router } from "express";
|
|||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
router.post("/", route({}), async (req: Request, res: Response) => {
|
router.post(
|
||||||
if (req.body.provider != null || req.body.voip_provider != null) {
|
"/",
|
||||||
console.log(`[LOGOUT]: provider or voip provider not null!`, req.body);
|
route({
|
||||||
} else {
|
responses: {
|
||||||
delete req.body.provider;
|
204: {},
|
||||||
delete req.body.voip_provider;
|
},
|
||||||
if (Object.keys(req.body).length != 0)
|
}),
|
||||||
console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
|
async (req: Request, res: Response) => {
|
||||||
}
|
if (req.body.provider != null || req.body.voip_provider != null) {
|
||||||
res.status(204).send();
|
console.log(
|
||||||
});
|
`[LOGOUT]: provider or voip provider not null!`,
|
||||||
|
req.body,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
delete req.body.provider;
|
||||||
|
delete req.body.voip_provider;
|
||||||
|
if (Object.keys(req.body).length != 0)
|
||||||
|
console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
|
||||||
|
}
|
||||||
|
res.status(204).send();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -16,16 +16,26 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { BackupCode, generateToken, User, TotpSchema } from "@spacebar/util";
|
import { BackupCode, TotpSchema, User, generateToken } from "@spacebar/util";
|
||||||
import { verifyToken } from "node-2fa";
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
import { verifyToken } from "node-2fa";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "TotpSchema" }),
|
route({
|
||||||
|
requestBody: "TotpSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "TokenResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
// const { code, ticket, gift_code_sku_id, login_source } =
|
// const { code, ticket, gift_code_sku_id, login_source } =
|
||||||
const { code, ticket } = req.body as TotpSchema;
|
const { code, ticket } = req.body as TotpSchema;
|
||||||
|
@ -41,7 +41,13 @@ function toArrayBuffer(buf: Buffer) {
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "WebAuthnTotpSchema" }),
|
route({
|
||||||
|
requestBody: "WebAuthnTotpSchema",
|
||||||
|
responses: {
|
||||||
|
200: { body: "TokenResponse" },
|
||||||
|
400: { body: "APIErrorResponse" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
if (!WebAuthn.fido2) {
|
if (!WebAuthn.fido2) {
|
||||||
// TODO: I did this for typescript and I can't use !
|
// TODO: I did this for typescript and I can't use !
|
||||||
|
@ -16,25 +16,25 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import {
|
import {
|
||||||
Config,
|
|
||||||
generateToken,
|
|
||||||
Invite,
|
|
||||||
FieldErrors,
|
|
||||||
User,
|
|
||||||
adjustEmail,
|
|
||||||
RegisterSchema,
|
|
||||||
ValidRegistrationToken,
|
|
||||||
} from "@spacebar/util";
|
|
||||||
import {
|
|
||||||
route,
|
|
||||||
getIpAdress,
|
|
||||||
IPAnalysis,
|
IPAnalysis,
|
||||||
|
getIpAdress,
|
||||||
isProxy,
|
isProxy,
|
||||||
|
route,
|
||||||
verifyCaptcha,
|
verifyCaptcha,
|
||||||
} from "@spacebar/api";
|
} from "@spacebar/api";
|
||||||
|
import {
|
||||||
|
Config,
|
||||||
|
FieldErrors,
|
||||||
|
Invite,
|
||||||
|
RegisterSchema,
|
||||||
|
User,
|
||||||
|
ValidRegistrationToken,
|
||||||
|
adjustEmail,
|
||||||
|
generateToken,
|
||||||
|
} from "@spacebar/util";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { MoreThan } from "typeorm";
|
import { MoreThan } from "typeorm";
|
||||||
|
|
||||||
@ -42,7 +42,13 @@ const router: Router = Router();
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "RegisterSchema" }),
|
route({
|
||||||
|
requestBody: "RegisterSchema",
|
||||||
|
responses: {
|
||||||
|
200: { body: "TokenOnlyResponse" },
|
||||||
|
400: { body: "APIErrorOrCaptchaResponse" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as RegisterSchema;
|
const body = req.body as RegisterSchema;
|
||||||
const { register, security, limits } = Config.get();
|
const { register, security, limits } = Config.get();
|
||||||
@ -219,6 +225,20 @@ router.post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (body.password) {
|
if (body.password) {
|
||||||
|
const min = register.password.minLength
|
||||||
|
? register.password.minLength
|
||||||
|
: 8;
|
||||||
|
if (body.password.length < min) {
|
||||||
|
throw FieldErrors({
|
||||||
|
password: {
|
||||||
|
code: "PASSWORD_REQUIREMENTS_MIN_LENGTH",
|
||||||
|
message: req.t(
|
||||||
|
"auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH",
|
||||||
|
{ min: min },
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
// the salt is saved in the password refer to bcrypt docs
|
// the salt is saved in the password refer to bcrypt docs
|
||||||
body.password = await bcrypt.hash(body.password, 12);
|
body.password = await bcrypt.hash(body.password, 12);
|
||||||
} else if (register.password.required) {
|
} else if (register.password.required) {
|
||||||
|
@ -31,9 +31,20 @@ import { Request, Response, Router } from "express";
|
|||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
// TODO: the response interface also returns settings, but this route doesn't actually return that.
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "PasswordResetSchema" }),
|
route({
|
||||||
|
requestBody: "PasswordResetSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "TokenOnlyResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorOrCaptchaResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { password, token } = req.body as PasswordResetSchema;
|
const { password, token } = req.body as PasswordResetSchema;
|
||||||
|
|
||||||
|
@ -37,9 +37,20 @@ async function getToken(user: User) {
|
|||||||
return { token };
|
return { token };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: the response interface also returns settings, but this route doesn't actually return that.
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "VerifyEmailSchema" }),
|
route({
|
||||||
|
requestBody: "VerifyEmailSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "TokenResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorOrCaptchaResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { captcha_key, token } = req.body;
|
const { captcha_key, token } = req.body;
|
||||||
|
|
||||||
|
@ -24,7 +24,18 @@ const router = Router();
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ right: "RESEND_VERIFICATION_EMAIL" }),
|
route({
|
||||||
|
right: "RESEND_VERIFICATION_EMAIL",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
500: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const user = await User.findOneOrFail({
|
const user = await User.findOneOrFail({
|
||||||
where: { id: req.user_id },
|
where: { id: req.user_id },
|
||||||
|
@ -16,15 +16,21 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { FieldErrors, User, BackupCodesChallengeSchema } from "@spacebar/util";
|
import { BackupCodesChallengeSchema, FieldErrors, User } from "@spacebar/util";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "BackupCodesChallengeSchema" }),
|
route({
|
||||||
|
requestBody: "BackupCodesChallengeSchema",
|
||||||
|
responses: {
|
||||||
|
200: { body: "BackupCodesChallengeResponse" },
|
||||||
|
400: { body: "APIErrorResponse" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { password } = req.body as BackupCodesChallengeSchema;
|
const { password } = req.body as BackupCodesChallengeSchema;
|
||||||
|
|
||||||
|
@ -16,18 +16,18 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
ChannelDeleteEvent,
|
ChannelDeleteEvent,
|
||||||
|
ChannelModifySchema,
|
||||||
ChannelType,
|
ChannelType,
|
||||||
ChannelUpdateEvent,
|
ChannelUpdateEvent,
|
||||||
emitEvent,
|
|
||||||
Recipient,
|
Recipient,
|
||||||
|
emitEvent,
|
||||||
handleFile,
|
handleFile,
|
||||||
ChannelModifySchema,
|
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
// TODO: delete channel
|
// TODO: delete channel
|
||||||
@ -35,7 +35,15 @@ const router: Router = Router();
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "VIEW_CHANNEL" }),
|
route({
|
||||||
|
permission: "VIEW_CHANNEL",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Channel",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
|
|
||||||
@ -49,7 +57,15 @@ router.get(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_CHANNELS" }),
|
route({
|
||||||
|
permission: "MANAGE_CHANNELS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Channel",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
|
|
||||||
@ -90,7 +106,19 @@ router.delete(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
|
route({
|
||||||
|
requestBody: "ChannelModifySchema",
|
||||||
|
permission: "MANAGE_CHANNELS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Channel",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const payload = req.body as ChannelModifySchema;
|
const payload = req.body as ChannelModifySchema;
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
|
@ -16,29 +16,37 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
import { random, route } from "@spacebar/api";
|
||||||
import { HTTPError } from "lambert-server";
|
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import { random } from "@spacebar/api";
|
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
|
Guild,
|
||||||
Invite,
|
Invite,
|
||||||
InviteCreateEvent,
|
InviteCreateEvent,
|
||||||
emitEvent,
|
|
||||||
User,
|
|
||||||
Guild,
|
|
||||||
PublicInviteRelation,
|
PublicInviteRelation,
|
||||||
|
User,
|
||||||
|
emitEvent,
|
||||||
|
isTextChannel,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { isTextChannel } from "./messages";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({
|
route({
|
||||||
body: "InviteCreateSchema",
|
requestBody: "InviteCreateSchema",
|
||||||
permission: "CREATE_INSTANT_INVITE",
|
permission: "CREATE_INSTANT_INVITE",
|
||||||
right: "CREATE_INVITES",
|
right: "CREATE_INVITES",
|
||||||
|
responses: {
|
||||||
|
201: {
|
||||||
|
body: "Invite",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { user_id } = req;
|
const { user_id } = req;
|
||||||
@ -84,7 +92,15 @@ router.post(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_CHANNELS" }),
|
route({
|
||||||
|
permission: "MANAGE_CHANNELS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIInviteArray",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
const channel = await Channel.findOneOrFail({
|
const channel = await Channel.findOneOrFail({
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
emitEvent,
|
emitEvent,
|
||||||
getPermission,
|
getPermission,
|
||||||
@ -23,7 +24,6 @@ import {
|
|||||||
ReadState,
|
ReadState,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -33,7 +33,13 @@ const router = Router();
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "MessageAcknowledgeSchema" }),
|
route({
|
||||||
|
requestBody: "MessageAcknowledgeSchema",
|
||||||
|
responses: {
|
||||||
|
200: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id, message_id } = req.params;
|
const { channel_id, message_id } = req.params;
|
||||||
|
|
||||||
|
@ -16,14 +16,21 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_MESSAGES" }),
|
route({
|
||||||
|
permission: "MANAGE_MESSAGES",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Message",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
(req: Request, res: Response) => {
|
(req: Request, res: Response) => {
|
||||||
// TODO:
|
// TODO:
|
||||||
res.json({
|
res.json({
|
||||||
|
@ -19,24 +19,23 @@
|
|||||||
import {
|
import {
|
||||||
Attachment,
|
Attachment,
|
||||||
Channel,
|
Channel,
|
||||||
emitEvent,
|
|
||||||
SpacebarApiErrors,
|
|
||||||
getPermission,
|
|
||||||
getRights,
|
|
||||||
Message,
|
Message,
|
||||||
MessageCreateEvent,
|
MessageCreateEvent,
|
||||||
|
MessageCreateSchema,
|
||||||
MessageDeleteEvent,
|
MessageDeleteEvent,
|
||||||
|
MessageEditSchema,
|
||||||
MessageUpdateEvent,
|
MessageUpdateEvent,
|
||||||
Snowflake,
|
Snowflake,
|
||||||
|
SpacebarApiErrors,
|
||||||
|
emitEvent,
|
||||||
|
getPermission,
|
||||||
|
getRights,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
MessageCreateSchema,
|
|
||||||
MessageEditSchema,
|
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Router, Response, Request } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import multer from "multer";
|
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import { handleMessage, postHandleMessage } from "@spacebar/api";
|
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
import multer from "multer";
|
||||||
|
import { handleMessage, postHandleMessage, route } from "../../../../../util";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
// TODO: message content/embed string length limit
|
// TODO: message content/embed string length limit
|
||||||
@ -53,9 +52,19 @@ const messageUpload = multer({
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({
|
route({
|
||||||
body: "MessageEditSchema",
|
requestBody: "MessageEditSchema",
|
||||||
permission: "SEND_MESSAGES",
|
permission: "SEND_MESSAGES",
|
||||||
right: "SEND_MESSAGES",
|
right: "SEND_MESSAGES",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Message",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { message_id, channel_id } = req.params;
|
const { message_id, channel_id } = req.params;
|
||||||
@ -143,9 +152,19 @@ router.put(
|
|||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
route({
|
route({
|
||||||
body: "MessageCreateSchema",
|
requestBody: "MessageCreateSchema",
|
||||||
permission: "SEND_MESSAGES",
|
permission: "SEND_MESSAGES",
|
||||||
right: "SEND_BACKDATED_EVENTS",
|
right: "SEND_BACKDATED_EVENTS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Message",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id, message_id } = req.params;
|
const { channel_id, message_id } = req.params;
|
||||||
@ -230,7 +249,19 @@ router.put(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "VIEW_CHANNEL" }),
|
route({
|
||||||
|
permission: "VIEW_CHANNEL",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Message",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { message_id, channel_id } = req.params;
|
const { message_id, channel_id } = req.params;
|
||||||
|
|
||||||
@ -252,38 +283,54 @@ router.get(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete("/", route({}), async (req: Request, res: Response) => {
|
router.delete(
|
||||||
const { message_id, channel_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
|
responses: {
|
||||||
const message = await Message.findOneOrFail({ where: { id: message_id } });
|
204: {},
|
||||||
|
400: {
|
||||||
const rights = await getRights(req.user_id);
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
if (message.author_id !== req.user_id) {
|
404: {},
|
||||||
if (!rights.has("MANAGE_MESSAGES")) {
|
|
||||||
const permission = await getPermission(
|
|
||||||
req.user_id,
|
|
||||||
channel.guild_id,
|
|
||||||
channel_id,
|
|
||||||
);
|
|
||||||
permission.hasThrow("MANAGE_MESSAGES");
|
|
||||||
}
|
|
||||||
} else rights.hasThrow("SELF_DELETE_MESSAGES");
|
|
||||||
|
|
||||||
await Message.delete({ id: message_id });
|
|
||||||
|
|
||||||
await emitEvent({
|
|
||||||
event: "MESSAGE_DELETE",
|
|
||||||
channel_id,
|
|
||||||
data: {
|
|
||||||
id: message_id,
|
|
||||||
channel_id,
|
|
||||||
guild_id: channel.guild_id,
|
|
||||||
},
|
},
|
||||||
} as MessageDeleteEvent);
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { message_id, channel_id } = req.params;
|
||||||
|
|
||||||
res.sendStatus(204);
|
const channel = await Channel.findOneOrFail({
|
||||||
});
|
where: { id: channel_id },
|
||||||
|
});
|
||||||
|
const message = await Message.findOneOrFail({
|
||||||
|
where: { id: message_id },
|
||||||
|
});
|
||||||
|
|
||||||
|
const rights = await getRights(req.user_id);
|
||||||
|
|
||||||
|
if (message.author_id !== req.user_id) {
|
||||||
|
if (!rights.has("MANAGE_MESSAGES")) {
|
||||||
|
const permission = await getPermission(
|
||||||
|
req.user_id,
|
||||||
|
channel.guild_id,
|
||||||
|
channel_id,
|
||||||
|
);
|
||||||
|
permission.hasThrow("MANAGE_MESSAGES");
|
||||||
|
}
|
||||||
|
} else rights.hasThrow("SELF_DELETE_MESSAGES");
|
||||||
|
|
||||||
|
await Message.delete({ id: message_id });
|
||||||
|
|
||||||
|
await emitEvent({
|
||||||
|
event: "MESSAGE_DELETE",
|
||||||
|
channel_id,
|
||||||
|
data: {
|
||||||
|
id: message_id,
|
||||||
|
channel_id,
|
||||||
|
guild_id: channel.guild_id,
|
||||||
|
},
|
||||||
|
} as MessageDeleteEvent);
|
||||||
|
|
||||||
|
res.sendStatus(204);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
@ -32,8 +33,7 @@ import {
|
|||||||
PublicUserProjection,
|
PublicUserProjection,
|
||||||
User,
|
User,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { In } from "typeorm";
|
import { In } from "typeorm";
|
||||||
|
|
||||||
@ -57,7 +57,17 @@ function getEmoji(emoji: string): PartialEmoji {
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_MESSAGES" }),
|
route({
|
||||||
|
permission: "MANAGE_MESSAGES",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { message_id, channel_id } = req.params;
|
const { message_id, channel_id } = req.params;
|
||||||
|
|
||||||
@ -83,7 +93,17 @@ router.delete(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:emoji",
|
"/:emoji",
|
||||||
route({ permission: "MANAGE_MESSAGES" }),
|
route({
|
||||||
|
permission: "MANAGE_MESSAGES",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { message_id, channel_id } = req.params;
|
const { message_id, channel_id } = req.params;
|
||||||
const emoji = getEmoji(req.params.emoji);
|
const emoji = getEmoji(req.params.emoji);
|
||||||
@ -120,7 +140,19 @@ router.delete(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:emoji",
|
"/:emoji",
|
||||||
route({ permission: "VIEW_CHANNEL" }),
|
route({
|
||||||
|
permission: "VIEW_CHANNEL",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "PublicUser",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { message_id, channel_id } = req.params;
|
const { message_id, channel_id } = req.params;
|
||||||
const emoji = getEmoji(req.params.emoji);
|
const emoji = getEmoji(req.params.emoji);
|
||||||
@ -148,7 +180,18 @@ router.get(
|
|||||||
|
|
||||||
router.put(
|
router.put(
|
||||||
"/:emoji/:user_id",
|
"/:emoji/:user_id",
|
||||||
route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }),
|
route({
|
||||||
|
permission: "READ_MESSAGE_HISTORY",
|
||||||
|
right: "SELF_ADD_REACTIONS",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { message_id, channel_id, user_id } = req.params;
|
const { message_id, channel_id, user_id } = req.params;
|
||||||
if (user_id !== "@me") throw new HTTPError("Invalid user");
|
if (user_id !== "@me") throw new HTTPError("Invalid user");
|
||||||
@ -219,7 +262,16 @@ router.put(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:emoji/:user_id",
|
"/:emoji/:user_id",
|
||||||
route({}),
|
route({
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
let { user_id } = req.params;
|
let { user_id } = req.params;
|
||||||
const { message_id, channel_id } = req.params;
|
const { message_id, channel_id } = req.params;
|
||||||
|
@ -16,18 +16,18 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
Config,
|
Config,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
getPermission,
|
getPermission,
|
||||||
getRights,
|
getRights,
|
||||||
MessageDeleteBulkEvent,
|
|
||||||
Message,
|
Message,
|
||||||
|
MessageDeleteBulkEvent,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
@ -38,7 +38,17 @@ export default router;
|
|||||||
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages
|
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "BulkDeleteSchema" }),
|
route({
|
||||||
|
requestBody: "BulkDeleteSchema",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
const channel = await Channel.findOneOrFail({
|
const channel = await Channel.findOneOrFail({
|
||||||
|
@ -16,165 +16,172 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
import { handleMessage, postHandleMessage, route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Attachment,
|
Attachment,
|
||||||
Channel,
|
Channel,
|
||||||
ChannelType,
|
|
||||||
Config,
|
Config,
|
||||||
DmChannelDTO,
|
DmChannelDTO,
|
||||||
emitEvent,
|
|
||||||
FieldErrors,
|
FieldErrors,
|
||||||
getPermission,
|
Member,
|
||||||
Message,
|
Message,
|
||||||
MessageCreateEvent,
|
MessageCreateEvent,
|
||||||
Snowflake,
|
|
||||||
uploadFile,
|
|
||||||
Member,
|
|
||||||
MessageCreateSchema,
|
MessageCreateSchema,
|
||||||
|
Reaction,
|
||||||
ReadState,
|
ReadState,
|
||||||
Rights,
|
Rights,
|
||||||
Reaction,
|
Snowflake,
|
||||||
User,
|
User,
|
||||||
|
emitEvent,
|
||||||
|
getPermission,
|
||||||
|
isTextChannel,
|
||||||
|
uploadFile,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { handleMessage, postHandleMessage, route } from "@spacebar/api";
|
|
||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm";
|
import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
export default router;
|
|
||||||
|
|
||||||
export function isTextChannel(type: ChannelType): boolean {
|
|
||||||
switch (type) {
|
|
||||||
case ChannelType.GUILD_STORE:
|
|
||||||
case ChannelType.GUILD_VOICE:
|
|
||||||
case ChannelType.GUILD_STAGE_VOICE:
|
|
||||||
case ChannelType.GUILD_CATEGORY:
|
|
||||||
case ChannelType.GUILD_FORUM:
|
|
||||||
case ChannelType.DIRECTORY:
|
|
||||||
throw new HTTPError("not a text channel", 400);
|
|
||||||
case ChannelType.DM:
|
|
||||||
case ChannelType.GROUP_DM:
|
|
||||||
case ChannelType.GUILD_NEWS:
|
|
||||||
case ChannelType.GUILD_NEWS_THREAD:
|
|
||||||
case ChannelType.GUILD_PUBLIC_THREAD:
|
|
||||||
case ChannelType.GUILD_PRIVATE_THREAD:
|
|
||||||
case ChannelType.GUILD_TEXT:
|
|
||||||
case ChannelType.ENCRYPTED:
|
|
||||||
case ChannelType.ENCRYPTED_THREAD:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
throw new HTTPError("unimplemented", 400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://discord.com/developers/docs/resources/channel#create-message
|
// https://discord.com/developers/docs/resources/channel#create-message
|
||||||
// get messages
|
// get messages
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const channel_id = req.params.channel_id;
|
"/",
|
||||||
const channel = await Channel.findOneOrFail({
|
route({
|
||||||
where: { id: channel_id },
|
query: {
|
||||||
});
|
around: {
|
||||||
if (!channel) throw new HTTPError("Channel not found", 404);
|
type: "string",
|
||||||
|
},
|
||||||
|
before: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: "number",
|
||||||
|
description:
|
||||||
|
"max number of messages to return (1-100). defaults to 50",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIMessageArray",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const channel_id = req.params.channel_id;
|
||||||
|
const channel = await Channel.findOneOrFail({
|
||||||
|
where: { id: channel_id },
|
||||||
|
});
|
||||||
|
if (!channel) throw new HTTPError("Channel not found", 404);
|
||||||
|
|
||||||
isTextChannel(channel.type);
|
isTextChannel(channel.type);
|
||||||
const around = req.query.around ? `${req.query.around}` : undefined;
|
const around = req.query.around ? `${req.query.around}` : undefined;
|
||||||
const before = req.query.before ? `${req.query.before}` : undefined;
|
const before = req.query.before ? `${req.query.before}` : undefined;
|
||||||
const after = req.query.after ? `${req.query.after}` : undefined;
|
const after = req.query.after ? `${req.query.after}` : undefined;
|
||||||
const limit = Number(req.query.limit) || 50;
|
const limit = Number(req.query.limit) || 50;
|
||||||
if (limit < 1 || limit > 100)
|
if (limit < 1 || limit > 100)
|
||||||
throw new HTTPError("limit must be between 1 and 100", 422);
|
throw new HTTPError("limit must be between 1 and 100", 422);
|
||||||
|
|
||||||
const halfLimit = Math.floor(limit / 2);
|
const permissions = await getPermission(
|
||||||
|
req.user_id,
|
||||||
|
channel.guild_id,
|
||||||
|
channel_id,
|
||||||
|
);
|
||||||
|
permissions.hasThrow("VIEW_CHANNEL");
|
||||||
|
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
|
||||||
|
|
||||||
const permissions = await getPermission(
|
const query: FindManyOptions<Message> & {
|
||||||
req.user_id,
|
where: { id?: FindOperator<string> | FindOperator<string>[] };
|
||||||
channel.guild_id,
|
} = {
|
||||||
channel_id,
|
order: { timestamp: "DESC" },
|
||||||
);
|
take: limit,
|
||||||
permissions.hasThrow("VIEW_CHANNEL");
|
where: { channel_id },
|
||||||
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
|
relations: [
|
||||||
|
"author",
|
||||||
|
"webhook",
|
||||||
|
"application",
|
||||||
|
"mentions",
|
||||||
|
"mention_roles",
|
||||||
|
"mention_channels",
|
||||||
|
"sticker_items",
|
||||||
|
"attachments",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const query: FindManyOptions<Message> & {
|
let messages: Message[];
|
||||||
where: { id?: FindOperator<string> | FindOperator<string>[] };
|
if (after) {
|
||||||
} = {
|
if (BigInt(after) > BigInt(Snowflake.generate()))
|
||||||
order: { timestamp: "DESC" },
|
return res.status(422);
|
||||||
take: limit,
|
query.where.id = MoreThan(after);
|
||||||
where: { channel_id },
|
messages = await Message.find(query);
|
||||||
relations: [
|
} else if (before) {
|
||||||
"author",
|
if (BigInt(before) < BigInt(req.params.channel_id))
|
||||||
"webhook",
|
return res.status(422);
|
||||||
"application",
|
query.where.id = LessThan(before);
|
||||||
"mentions",
|
messages = await Message.find(query);
|
||||||
"mention_roles",
|
} else if (around) {
|
||||||
"mention_channels",
|
query.take = Math.floor(limit / 2);
|
||||||
"sticker_items",
|
query.where.id = LessThan(around);
|
||||||
"attachments",
|
const messages_before = await Message.find(query);
|
||||||
],
|
query.where.id = MoreThan(around);
|
||||||
};
|
const messages_after = await Message.find(query);
|
||||||
|
messages = messages_before.concat(messages_after);
|
||||||
|
} else {
|
||||||
|
throw new HTTPError("after, around or before must be present", 422);
|
||||||
|
}
|
||||||
|
|
||||||
if (after) {
|
const endpoint = Config.get().cdn.endpointPublic;
|
||||||
if (BigInt(after) > BigInt(Snowflake.generate()))
|
|
||||||
return res.status(422);
|
|
||||||
query.where.id = MoreThan(after);
|
|
||||||
} else if (before) {
|
|
||||||
if (BigInt(before) < BigInt(req.params.channel_id))
|
|
||||||
return res.status(422);
|
|
||||||
query.where.id = LessThan(before);
|
|
||||||
} else if (around) {
|
|
||||||
query.where.id = [
|
|
||||||
MoreThan((BigInt(around) - BigInt(halfLimit)).toString()),
|
|
||||||
LessThan((BigInt(around) + BigInt(halfLimit)).toString()),
|
|
||||||
];
|
|
||||||
|
|
||||||
return res.json([]); // TODO: fix around
|
return res.json(
|
||||||
}
|
messages.map((x: Partial<Message>) => {
|
||||||
|
(x.reactions || []).forEach((y: Partial<Reaction>) => {
|
||||||
const messages = await Message.find(query);
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
const endpoint = Config.get().cdn.endpointPublic;
|
//@ts-ignore
|
||||||
|
if ((y.user_ids || []).includes(req.user_id)) y.me = true;
|
||||||
return res.json(
|
delete y.user_ids;
|
||||||
messages.map((x: Partial<Message>) => {
|
});
|
||||||
(x.reactions || []).forEach((y: Partial<Reaction>) => {
|
if (!x.author)
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
x.author = User.create({
|
||||||
//@ts-ignore
|
id: "4",
|
||||||
if ((y.user_ids || []).includes(req.user_id)) y.me = true;
|
discriminator: "0000",
|
||||||
delete y.user_ids;
|
username: "Spacebar Ghost",
|
||||||
});
|
public_flags: 0,
|
||||||
if (!x.author)
|
});
|
||||||
x.author = User.create({
|
x.attachments?.forEach((y: Attachment) => {
|
||||||
id: "4",
|
// dynamically set attachment proxy_url in case the endpoint changed
|
||||||
discriminator: "0000",
|
const uri = y.proxy_url.startsWith("http")
|
||||||
username: "Spacebar Ghost",
|
? y.proxy_url
|
||||||
public_flags: 0,
|
: `https://example.org${y.proxy_url}`;
|
||||||
|
y.proxy_url = `${endpoint == null ? "" : endpoint}${
|
||||||
|
new URL(uri).pathname
|
||||||
|
}`;
|
||||||
});
|
});
|
||||||
x.attachments?.forEach((y: Attachment) => {
|
|
||||||
// dynamically set attachment proxy_url in case the endpoint changed
|
|
||||||
const uri = y.proxy_url.startsWith("http")
|
|
||||||
? y.proxy_url
|
|
||||||
: `https://example.org${y.proxy_url}`;
|
|
||||||
y.proxy_url = `${endpoint == null ? "" : endpoint}${
|
|
||||||
new URL(uri).pathname
|
|
||||||
}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Some clients ( discord.js ) only check if a property exists within the response,
|
Some clients ( discord.js ) only check if a property exists within the response,
|
||||||
which causes errors when, say, the `application` property is `null`.
|
which causes errors when, say, the `application` property is `null`.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
// for (var curr in x) {
|
// for (var curr in x) {
|
||||||
// if (x[curr] === null)
|
// if (x[curr] === null)
|
||||||
// delete x[curr];
|
// delete x[curr];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: config max upload size
|
// TODO: config max upload size
|
||||||
const messageUpload = multer({
|
const messageUpload = multer({
|
||||||
@ -205,9 +212,19 @@ router.post(
|
|||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
route({
|
route({
|
||||||
body: "MessageCreateSchema",
|
requestBody: "MessageCreateSchema",
|
||||||
permission: "SEND_MESSAGES",
|
permission: "SEND_MESSAGES",
|
||||||
right: "SEND_MESSAGES",
|
right: "SEND_MESSAGES",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Message",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
@ -366,3 +383,5 @@ router.post(
|
|||||||
return res.json(message);
|
return res.json(message);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
@ -19,13 +19,13 @@
|
|||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
ChannelPermissionOverwrite,
|
ChannelPermissionOverwrite,
|
||||||
|
ChannelPermissionOverwriteSchema,
|
||||||
ChannelUpdateEvent,
|
ChannelUpdateEvent,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
Member,
|
Member,
|
||||||
Role,
|
Role,
|
||||||
ChannelPermissionOverwriteSchema,
|
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Router, Response, Request } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
@ -36,8 +36,14 @@ const router: Router = Router();
|
|||||||
router.put(
|
router.put(
|
||||||
"/:overwrite_id",
|
"/:overwrite_id",
|
||||||
route({
|
route({
|
||||||
body: "ChannelPermissionOverwriteSchema",
|
requestBody: "ChannelPermissionOverwriteSchema",
|
||||||
permission: "MANAGE_ROLES",
|
permission: "MANAGE_ROLES",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
404: {},
|
||||||
|
501: {},
|
||||||
|
400: { body: "APIErrorResponse" },
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id, overwrite_id } = req.params;
|
const { channel_id, overwrite_id } = req.params;
|
||||||
@ -92,7 +98,7 @@ router.put(
|
|||||||
// TODO: check permission hierarchy
|
// TODO: check permission hierarchy
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:overwrite_id",
|
"/:overwrite_id",
|
||||||
route({ permission: "MANAGE_ROLES" }),
|
route({ permission: "MANAGE_ROLES", responses: { 204: {}, 404: {} } }),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id, overwrite_id } = req.params;
|
const { channel_id, overwrite_id } = req.params;
|
||||||
|
|
||||||
|
@ -16,23 +16,33 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
ChannelPinsUpdateEvent,
|
ChannelPinsUpdateEvent,
|
||||||
Config,
|
Config,
|
||||||
|
DiscordApiErrors,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
Message,
|
Message,
|
||||||
MessageUpdateEvent,
|
MessageUpdateEvent,
|
||||||
DiscordApiErrors,
|
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Router, Request, Response } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.put(
|
router.put(
|
||||||
"/:message_id",
|
"/:message_id",
|
||||||
route({ permission: "VIEW_CHANNEL" }),
|
route({
|
||||||
|
permission: "VIEW_CHANNEL",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id, message_id } = req.params;
|
const { channel_id, message_id } = req.params;
|
||||||
|
|
||||||
@ -74,7 +84,17 @@ router.put(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:message_id",
|
"/:message_id",
|
||||||
route({ permission: "VIEW_CHANNEL" }),
|
route({
|
||||||
|
permission: "VIEW_CHANNEL",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id, message_id } = req.params;
|
const { channel_id, message_id } = req.params;
|
||||||
|
|
||||||
@ -114,7 +134,17 @@ router.delete(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: ["READ_MESSAGE_HISTORY"] }),
|
route({
|
||||||
|
permission: ["READ_MESSAGE_HISTORY"],
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIMessageArray",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
|
|
||||||
|
@ -16,20 +16,20 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HTTPError } from "lambert-server";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { isTextChannel } from "./messages";
|
|
||||||
import { FindManyOptions, Between, Not, FindOperator } from "typeorm";
|
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
emitEvent,
|
|
||||||
getPermission,
|
|
||||||
getRights,
|
|
||||||
Message,
|
Message,
|
||||||
MessageDeleteBulkEvent,
|
MessageDeleteBulkEvent,
|
||||||
PurgeSchema,
|
PurgeSchema,
|
||||||
|
emitEvent,
|
||||||
|
getPermission,
|
||||||
|
getRights,
|
||||||
|
isTextChannel,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Router, Response, Request } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
import { Between, FindManyOptions, FindOperator, Not } from "typeorm";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
@ -42,6 +42,14 @@ router.post(
|
|||||||
"/",
|
"/",
|
||||||
route({
|
route({
|
||||||
/*body: "PurgeSchema",*/
|
/*body: "PurgeSchema",*/
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
ChannelRecipientAddEvent,
|
ChannelRecipientAddEvent,
|
||||||
@ -28,80 +28,98 @@ import {
|
|||||||
Recipient,
|
Recipient,
|
||||||
User,
|
User,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.put("/:user_id", route({}), async (req: Request, res: Response) => {
|
router.put(
|
||||||
const { channel_id, user_id } = req.params;
|
"/:user_id",
|
||||||
const channel = await Channel.findOneOrFail({
|
route({
|
||||||
where: { id: channel_id },
|
responses: {
|
||||||
relations: ["recipients"],
|
201: {},
|
||||||
});
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { channel_id, user_id } = req.params;
|
||||||
|
const channel = await Channel.findOneOrFail({
|
||||||
|
where: { id: channel_id },
|
||||||
|
relations: ["recipients"],
|
||||||
|
});
|
||||||
|
|
||||||
if (channel.type !== ChannelType.GROUP_DM) {
|
if (channel.type !== ChannelType.GROUP_DM) {
|
||||||
const recipients = [
|
const recipients = [
|
||||||
...(channel.recipients?.map((r) => r.user_id) || []),
|
...(channel.recipients?.map((r) => r.user_id) || []),
|
||||||
user_id,
|
user_id,
|
||||||
].unique();
|
].unique();
|
||||||
|
|
||||||
const new_channel = await Channel.createDMChannel(
|
const new_channel = await Channel.createDMChannel(
|
||||||
recipients,
|
recipients,
|
||||||
req.user_id,
|
req.user_id,
|
||||||
);
|
);
|
||||||
return res.status(201).json(new_channel);
|
return res.status(201).json(new_channel);
|
||||||
} else {
|
} else {
|
||||||
if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
|
if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
|
||||||
|
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.recipients?.push(
|
||||||
|
Recipient.create({ channel_id: channel_id, user_id: user_id }),
|
||||||
|
);
|
||||||
|
await channel.save();
|
||||||
|
|
||||||
|
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",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { channel_id, user_id } = req.params;
|
||||||
|
const channel = await Channel.findOneOrFail({
|
||||||
|
where: { id: channel_id },
|
||||||
|
relations: ["recipients"],
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
channel.type === ChannelType.GROUP_DM &&
|
||||||
|
(channel.owner_id === req.user_id || user_id === req.user_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw DiscordApiErrors.MISSING_PERMISSIONS;
|
||||||
|
|
||||||
|
if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
|
||||||
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
|
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.recipients?.push(
|
await Channel.removeRecipientFromChannel(channel, user_id);
|
||||||
Recipient.create({ channel_id: channel_id, user_id: user_id }),
|
|
||||||
);
|
|
||||||
await channel.save();
|
|
||||||
|
|
||||||
await emitEvent({
|
|
||||||
event: "CHANNEL_CREATE",
|
|
||||||
data: await DmChannelDTO.from(channel, [user_id]),
|
|
||||||
user_id: user_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await emitEvent({
|
|
||||||
event: "CHANNEL_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);
|
return res.sendStatus(204);
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
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 Channel.removeRecipientFromChannel(channel, user_id);
|
|
||||||
|
|
||||||
return res.sendStatus(204);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,15 +16,22 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Channel, emitEvent, Member, TypingStartEvent } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { Router, Request, Response } from "express";
|
import { Channel, emitEvent, Member, TypingStartEvent } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "SEND_MESSAGES" }),
|
route({
|
||||||
|
permission: "SEND_MESSAGES",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
404: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id } = req.params;
|
const { channel_id } = req.params;
|
||||||
const user_id = req.user_id;
|
const user_id = req.user_id;
|
||||||
|
@ -16,34 +16,56 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
Config,
|
Config,
|
||||||
handleFile,
|
DiscordApiErrors,
|
||||||
trimSpecial,
|
|
||||||
User,
|
User,
|
||||||
Webhook,
|
Webhook,
|
||||||
WebhookCreateSchema,
|
WebhookCreateSchema,
|
||||||
WebhookType,
|
WebhookType,
|
||||||
|
handleFile,
|
||||||
|
trimSpecial,
|
||||||
|
isTextChannel,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { HTTPError } from "lambert-server";
|
|
||||||
import { isTextChannel } from "./messages/index";
|
|
||||||
import { DiscordApiErrors } from "@spacebar/util";
|
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
//TODO: implement webhooks
|
//TODO: implement webhooks
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
res.json([]);
|
"/",
|
||||||
});
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIWebhookArray",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
res.json([]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: use Image Data Type for avatar instead of String
|
// TODO: use Image Data Type for avatar instead of String
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }),
|
route({
|
||||||
|
requestBody: "WebhookCreateSchema",
|
||||||
|
permission: "MANAGE_WEBHOOKS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "WebhookCreateResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const channel_id = req.params.channel_id;
|
const channel_id = req.params.channel_id;
|
||||||
const channel = await Channel.findOneOrFail({
|
const channel = await Channel.findOneOrFail({
|
||||||
|
@ -29,7 +29,7 @@ const router = Router();
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ConnectionCallbackSchema" }),
|
route({ requestBody: "ConnectionCallbackSchema" }),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { connection_name } = req.params;
|
const { connection_name } = req.params;
|
||||||
const connection = ConnectionStore.connections.get(connection_name);
|
const connection = ConnectionStore.connections.get(connection_name);
|
||||||
|
@ -16,49 +16,61 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Guild, Config } from "@spacebar/util";
|
import { Config, Guild } from "@spacebar/util";
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { Like } from "typeorm";
|
import { Like } from "typeorm";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { offset, limit, categories } = req.query;
|
"/",
|
||||||
const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
|
route({
|
||||||
const configLimit = Config.get().guild.discovery.limit;
|
responses: {
|
||||||
let guilds;
|
200: {
|
||||||
if (categories == undefined) {
|
body: "DiscoverableGuildsResponse",
|
||||||
guilds = showAllGuilds
|
},
|
||||||
? await Guild.find({ take: Math.abs(Number(limit || configLimit)) })
|
},
|
||||||
: await Guild.find({
|
}),
|
||||||
where: { features: Like(`%DISCOVERABLE%`) },
|
async (req: Request, res: Response) => {
|
||||||
take: Math.abs(Number(limit || configLimit)),
|
const { offset, limit, categories } = req.query;
|
||||||
});
|
const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
|
||||||
} else {
|
const configLimit = Config.get().guild.discovery.limit;
|
||||||
guilds = showAllGuilds
|
let guilds;
|
||||||
? await Guild.find({
|
if (categories == undefined) {
|
||||||
where: { primary_category_id: categories.toString() },
|
guilds = showAllGuilds
|
||||||
take: Math.abs(Number(limit || configLimit)),
|
? await Guild.find({
|
||||||
})
|
take: Math.abs(Number(limit || configLimit)),
|
||||||
: await Guild.find({
|
})
|
||||||
where: {
|
: await Guild.find({
|
||||||
primary_category_id: categories.toString(),
|
where: { features: Like(`%DISCOVERABLE%`) },
|
||||||
features: Like("%DISCOVERABLE%"),
|
take: Math.abs(Number(limit || configLimit)),
|
||||||
},
|
});
|
||||||
take: Math.abs(Number(limit || configLimit)),
|
} else {
|
||||||
});
|
guilds = showAllGuilds
|
||||||
}
|
? await Guild.find({
|
||||||
|
where: { primary_category_id: categories.toString() },
|
||||||
|
take: Math.abs(Number(limit || configLimit)),
|
||||||
|
})
|
||||||
|
: await Guild.find({
|
||||||
|
where: {
|
||||||
|
primary_category_id: categories.toString(),
|
||||||
|
features: Like("%DISCOVERABLE%"),
|
||||||
|
},
|
||||||
|
take: Math.abs(Number(limit || configLimit)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const total = guilds ? guilds.length : undefined;
|
const total = guilds ? guilds.length : undefined;
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
total: total,
|
total: total,
|
||||||
guilds: guilds,
|
guilds: guilds,
|
||||||
offset: Number(offset || Config.get().guild.discovery.offset),
|
offset: Number(offset || Config.get().guild.discovery.offset),
|
||||||
limit: Number(limit || configLimit),
|
limit: Number(limit || configLimit),
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,24 +16,34 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Categories } from "@spacebar/util";
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Categories } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/categories", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
// TODO:
|
"/categories",
|
||||||
// Get locale instead
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIDiscoveryCategoryArray",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
// TODO:
|
||||||
|
// Get locale instead
|
||||||
|
|
||||||
// const { locale, primary_only } = req.query;
|
// const { locale, primary_only } = req.query;
|
||||||
const { primary_only } = req.query;
|
const { primary_only } = req.query;
|
||||||
|
|
||||||
const out = primary_only
|
const out = primary_only
|
||||||
? await Categories.find()
|
? await Categories.find()
|
||||||
: await Categories.find({ where: { is_primary: true } });
|
: await Categories.find({ where: { is_primary: true } });
|
||||||
|
|
||||||
res.send(out);
|
res.send(out);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,32 +16,43 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { FieldErrors, Release } from "@spacebar/util";
|
import { FieldErrors, Release } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { platform } = req.query;
|
"/",
|
||||||
|
route({
|
||||||
if (!platform)
|
responses: {
|
||||||
throw FieldErrors({
|
302: {},
|
||||||
platform: {
|
404: {
|
||||||
code: "BASE_TYPE_REQUIRED",
|
body: "APIErrorResponse",
|
||||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { platform } = req.query;
|
||||||
|
|
||||||
|
if (!platform)
|
||||||
|
throw FieldErrors({
|
||||||
|
platform: {
|
||||||
|
code: "BASE_TYPE_REQUIRED",
|
||||||
|
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const release = await Release.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
enabled: true,
|
||||||
|
platform: platform as string,
|
||||||
|
},
|
||||||
|
order: { pub_date: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const release = await Release.findOneOrFail({
|
res.redirect(release.url);
|
||||||
where: {
|
},
|
||||||
enabled: true,
|
);
|
||||||
platform: platform as string,
|
|
||||||
},
|
|
||||||
order: { pub_date: "DESC" },
|
|
||||||
});
|
|
||||||
|
|
||||||
res.redirect(release.url);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,32 +16,34 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import { Config } from "@spacebar/util";
|
import { Config } from "@spacebar/util";
|
||||||
import { Router, Response, Request } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route, RouteOptions } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
const options: RouteOptions = {
|
router.get(
|
||||||
test: {
|
"/",
|
||||||
response: {
|
route({
|
||||||
body: "GatewayBotResponse",
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GatewayBotResponse",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
(req: Request, res: Response) => {
|
||||||
|
const { endpointPublic } = Config.get().gateway;
|
||||||
|
res.json({
|
||||||
|
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
|
||||||
|
shards: 1,
|
||||||
|
session_start_limit: {
|
||||||
|
total: 1000,
|
||||||
|
remaining: 999,
|
||||||
|
reset_after: 14400000,
|
||||||
|
max_concurrency: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
);
|
||||||
|
|
||||||
router.get("/", route(options), (req: Request, res: Response) => {
|
|
||||||
const { endpointPublic } = Config.get().gateway;
|
|
||||||
res.json({
|
|
||||||
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
|
|
||||||
shards: 1,
|
|
||||||
session_start_limit: {
|
|
||||||
total: 1000,
|
|
||||||
remaining: 999,
|
|
||||||
reset_after: 14400000,
|
|
||||||
max_concurrency: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,25 +16,27 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import { Config } from "@spacebar/util";
|
import { Config } from "@spacebar/util";
|
||||||
import { Router, Response, Request } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route, RouteOptions } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
const options: RouteOptions = {
|
router.get(
|
||||||
test: {
|
"/",
|
||||||
response: {
|
route({
|
||||||
body: "GatewayResponse",
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GatewayResponse",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
(req: Request, res: Response) => {
|
||||||
|
const { endpointPublic } = Config.get().gateway;
|
||||||
|
res.json({
|
||||||
|
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
);
|
||||||
|
|
||||||
router.get("/", route(options), (req: Request, res: Response) => {
|
|
||||||
const { endpointPublic } = Config.get().gateway;
|
|
||||||
res.json({
|
|
||||||
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,34 +16,62 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
import { route } from "@spacebar/api";
|
||||||
|
import { TenorMediaTypes, getGifApiKey, parseGifResult } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import ProxyAgent from "proxy-agent";
|
import ProxyAgent from "proxy-agent";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import { getGifApiKey, parseGifResult } from "./trending";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
// TODO: Custom providers
|
"/",
|
||||||
const { q, media_format, locale } = req.query;
|
route({
|
||||||
|
query: {
|
||||||
const apiKey = getGifApiKey();
|
q: {
|
||||||
|
type: "string",
|
||||||
const agent = new ProxyAgent();
|
required: true,
|
||||||
|
description: "Search query",
|
||||||
const response = await fetch(
|
},
|
||||||
`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`,
|
media_format: {
|
||||||
{
|
type: "string",
|
||||||
agent,
|
description: "Media format",
|
||||||
method: "get",
|
values: Object.keys(TenorMediaTypes).filter((key) =>
|
||||||
headers: { "Content-Type": "application/json" },
|
isNaN(Number(key)),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
locale: {
|
||||||
|
type: "string",
|
||||||
|
description: "Locale",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "TenorGifsResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
// TODO: Custom providers
|
||||||
|
const { q, media_format, locale } = req.query;
|
||||||
|
|
||||||
const { results } = await response.json();
|
const apiKey = getGifApiKey();
|
||||||
|
|
||||||
res.json(results.map(parseGifResult)).status(200);
|
const agent = new ProxyAgent();
|
||||||
});
|
|
||||||
|
const response = await fetch(
|
||||||
|
`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`,
|
||||||
|
{
|
||||||
|
agent,
|
||||||
|
method: "get",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { results } = await response.json();
|
||||||
|
|
||||||
|
res.json(results.map(parseGifResult)).status(200);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,34 +16,57 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
import { route } from "@spacebar/api";
|
||||||
|
import { TenorMediaTypes, getGifApiKey, parseGifResult } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import ProxyAgent from "proxy-agent";
|
import ProxyAgent from "proxy-agent";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import { getGifApiKey, parseGifResult } from "./trending";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
// TODO: Custom providers
|
"/",
|
||||||
const { media_format, locale } = req.query;
|
route({
|
||||||
|
query: {
|
||||||
const apiKey = getGifApiKey();
|
media_format: {
|
||||||
|
type: "string",
|
||||||
const agent = new ProxyAgent();
|
description: "Media format",
|
||||||
|
values: Object.keys(TenorMediaTypes).filter((key) =>
|
||||||
const response = await fetch(
|
isNaN(Number(key)),
|
||||||
`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`,
|
),
|
||||||
{
|
},
|
||||||
agent,
|
locale: {
|
||||||
method: "get",
|
type: "string",
|
||||||
headers: { "Content-Type": "application/json" },
|
description: "Locale",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "TenorGifsResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
// TODO: Custom providers
|
||||||
|
const { media_format, locale } = req.query;
|
||||||
|
|
||||||
const { results } = await response.json();
|
const apiKey = getGifApiKey();
|
||||||
|
|
||||||
res.json(results.map(parseGifResult)).status(200);
|
const agent = new ProxyAgent();
|
||||||
});
|
|
||||||
|
const response = await fetch(
|
||||||
|
`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`,
|
||||||
|
{
|
||||||
|
agent,
|
||||||
|
method: "get",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { results } = await response.json();
|
||||||
|
|
||||||
|
res.json(results.map(parseGifResult)).status(200);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,126 +16,76 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
import { route } from "@spacebar/api";
|
||||||
|
import {
|
||||||
|
TenorCategoriesResults,
|
||||||
|
TenorTrendingResults,
|
||||||
|
getGifApiKey,
|
||||||
|
parseGifResult,
|
||||||
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import ProxyAgent from "proxy-agent";
|
import ProxyAgent from "proxy-agent";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import { Config } from "@spacebar/util";
|
|
||||||
import { HTTPError } from "lambert-server";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// TODO: Move somewhere else
|
router.get(
|
||||||
enum TENOR_GIF_TYPES {
|
"/",
|
||||||
gif,
|
route({
|
||||||
mediumgif,
|
query: {
|
||||||
tinygif,
|
locale: {
|
||||||
nanogif,
|
type: "string",
|
||||||
mp4,
|
description: "Locale",
|
||||||
loopedmp4,
|
|
||||||
tinymp4,
|
|
||||||
nanomp4,
|
|
||||||
webm,
|
|
||||||
tinywebm,
|
|
||||||
nanowebm,
|
|
||||||
}
|
|
||||||
|
|
||||||
type TENOR_MEDIA = {
|
|
||||||
preview: string;
|
|
||||||
url: string;
|
|
||||||
dims: number[];
|
|
||||||
size: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TENOR_GIF = {
|
|
||||||
created: number;
|
|
||||||
hasaudio: boolean;
|
|
||||||
id: string;
|
|
||||||
media: { [type in keyof typeof TENOR_GIF_TYPES]: TENOR_MEDIA }[];
|
|
||||||
tags: string[];
|
|
||||||
title: string;
|
|
||||||
itemurl: string;
|
|
||||||
hascaption: boolean;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TENOR_CATEGORY = {
|
|
||||||
searchterm: string;
|
|
||||||
path: string;
|
|
||||||
image: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TENOR_CATEGORIES_RESULTS = {
|
|
||||||
tags: TENOR_CATEGORY[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type TENOR_TRENDING_RESULTS = {
|
|
||||||
next: string;
|
|
||||||
results: TENOR_GIF[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function parseGifResult(result: TENOR_GIF) {
|
|
||||||
return {
|
|
||||||
id: result.id,
|
|
||||||
title: result.title,
|
|
||||||
url: result.itemurl,
|
|
||||||
src: result.media[0].mp4.url,
|
|
||||||
gif_src: result.media[0].gif.url,
|
|
||||||
width: result.media[0].mp4.dims[0],
|
|
||||||
height: result.media[0].mp4.dims[1],
|
|
||||||
preview: result.media[0].mp4.preview,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getGifApiKey() {
|
|
||||||
const { enabled, provider, apiKey } = Config.get().gif;
|
|
||||||
if (!enabled) throw new HTTPError(`Gifs are disabled`);
|
|
||||||
if (provider !== "tenor" || !apiKey)
|
|
||||||
throw new HTTPError(`${provider} gif provider not supported`);
|
|
||||||
|
|
||||||
return apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
|
||||||
// TODO: Custom providers
|
|
||||||
// TODO: return gifs as mp4
|
|
||||||
// const { media_format, locale } = req.query;
|
|
||||||
const { locale } = req.query;
|
|
||||||
|
|
||||||
const apiKey = getGifApiKey();
|
|
||||||
|
|
||||||
const agent = new ProxyAgent();
|
|
||||||
|
|
||||||
const [responseSource, trendGifSource] = await Promise.all([
|
|
||||||
fetch(
|
|
||||||
`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`,
|
|
||||||
{
|
|
||||||
agent,
|
|
||||||
method: "get",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
fetch(
|
responses: {
|
||||||
`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`,
|
200: {
|
||||||
{
|
body: "TenorTrendingResponse",
|
||||||
agent,
|
|
||||||
method: "get",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
]);
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
// TODO: Custom providers
|
||||||
|
// TODO: return gifs as mp4
|
||||||
|
// const { media_format, locale } = req.query;
|
||||||
|
const { locale } = req.query;
|
||||||
|
|
||||||
const { tags } = (await responseSource.json()) as TENOR_CATEGORIES_RESULTS;
|
const apiKey = getGifApiKey();
|
||||||
const { results } = (await trendGifSource.json()) as TENOR_TRENDING_RESULTS;
|
|
||||||
|
|
||||||
res.json({
|
const agent = new ProxyAgent();
|
||||||
categories: tags.map((x) => ({
|
|
||||||
name: x.searchterm,
|
const [responseSource, trendGifSource] = await Promise.all([
|
||||||
src: x.image,
|
fetch(
|
||||||
})),
|
`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`,
|
||||||
gifs: [parseGifResult(results[0])],
|
{
|
||||||
}).status(200);
|
agent,
|
||||||
});
|
method: "get",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
},
|
||||||
|
),
|
||||||
|
fetch(
|
||||||
|
`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`,
|
||||||
|
{
|
||||||
|
agent,
|
||||||
|
method: "get",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { tags } =
|
||||||
|
(await responseSource.json()) as TenorCategoriesResults;
|
||||||
|
const { results } =
|
||||||
|
(await trendGifSource.json()) as TenorTrendingResults;
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
categories: tags.map((x) => ({
|
||||||
|
name: x.searchterm,
|
||||||
|
src: x.image,
|
||||||
|
})),
|
||||||
|
gifs: [parseGifResult(results[0])],
|
||||||
|
}).status(200);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,34 +16,44 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Guild, Config } from "@spacebar/util";
|
import { Config, Guild } from "@spacebar/util";
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { Like } from "typeorm";
|
import { Like } from "typeorm";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
// const { limit, personalization_disabled } = req.query;
|
"/",
|
||||||
const { limit } = req.query;
|
route({
|
||||||
const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GuildRecommendationsResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
// const { limit, personalization_disabled } = req.query;
|
||||||
|
const { limit } = req.query;
|
||||||
|
const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
|
||||||
|
|
||||||
const genLoadId = (size: number) =>
|
const genLoadId = (size: number) =>
|
||||||
[...Array(size)]
|
[...Array(size)]
|
||||||
.map(() => Math.floor(Math.random() * 16).toString(16))
|
.map(() => Math.floor(Math.random() * 16).toString(16))
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
const guilds = showAllGuilds
|
const guilds = showAllGuilds
|
||||||
? await Guild.find({ take: Math.abs(Number(limit || 24)) })
|
? await Guild.find({ take: Math.abs(Number(limit || 24)) })
|
||||||
: await Guild.find({
|
: await Guild.find({
|
||||||
where: { features: Like("%DISCOVERABLE%") },
|
where: { features: Like("%DISCOVERABLE%") },
|
||||||
take: Math.abs(Number(limit || 24)),
|
take: Math.abs(Number(limit || 24)),
|
||||||
});
|
});
|
||||||
res.send({
|
res.send({
|
||||||
recommended_guilds: guilds,
|
recommended_guilds: guilds,
|
||||||
load_id: `server_recs/${genLoadId(32)}`,
|
load_id: `server_recs/${genLoadId(32)}`,
|
||||||
}).status(200);
|
}).status(200);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,20 +16,20 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
import { getIpAdress, route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
|
Ban,
|
||||||
|
BanModeratorSchema,
|
||||||
|
BanRegistrySchema,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
emitEvent,
|
|
||||||
GuildBanAddEvent,
|
GuildBanAddEvent,
|
||||||
GuildBanRemoveEvent,
|
GuildBanRemoveEvent,
|
||||||
Ban,
|
|
||||||
User,
|
|
||||||
Member,
|
Member,
|
||||||
BanRegistrySchema,
|
User,
|
||||||
BanModeratorSchema,
|
emitEvent,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { getIpAdress, route } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
@ -37,7 +37,17 @@ const router: Router = Router();
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "BAN_MEMBERS" }),
|
route({
|
||||||
|
permission: "BAN_MEMBERS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GuildBansResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
@ -73,7 +83,20 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:user",
|
"/:user",
|
||||||
route({ permission: "BAN_MEMBERS" }),
|
route({
|
||||||
|
permission: "BAN_MEMBERS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "BanModeratorSchema",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const user_id = req.params.ban;
|
const user_id = req.params.ban;
|
||||||
@ -97,7 +120,21 @@ router.get(
|
|||||||
|
|
||||||
router.put(
|
router.put(
|
||||||
"/:user_id",
|
"/:user_id",
|
||||||
route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }),
|
route({
|
||||||
|
requestBody: "BanCreateSchema",
|
||||||
|
permission: "BAN_MEMBERS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Ban",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const banned_user_id = req.params.user_id;
|
const banned_user_id = req.params.user_id;
|
||||||
@ -143,7 +180,20 @@ router.put(
|
|||||||
|
|
||||||
router.put(
|
router.put(
|
||||||
"/@me",
|
"/@me",
|
||||||
route({ body: "BanCreateSchema" }),
|
route({
|
||||||
|
requestBody: "BanCreateSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Ban",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
@ -182,7 +232,18 @@ router.put(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:user_id",
|
"/:user_id",
|
||||||
route({ permission: "BAN_MEMBERS" }),
|
route({
|
||||||
|
permission: "BAN_MEMBERS",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id, user_id } = req.params;
|
const { guild_id, user_id } = req.params;
|
||||||
|
|
||||||
|
@ -16,28 +16,52 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
ChannelUpdateEvent,
|
|
||||||
emitEvent,
|
|
||||||
ChannelModifySchema,
|
ChannelModifySchema,
|
||||||
ChannelReorderSchema,
|
ChannelReorderSchema,
|
||||||
|
ChannelUpdateEvent,
|
||||||
|
emitEvent,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
const channels = await Channel.find({ where: { guild_id } });
|
route({
|
||||||
|
responses: {
|
||||||
|
201: {
|
||||||
|
body: "APIChannelArray",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
const channels = await Channel.find({ where: { guild_id } });
|
||||||
|
|
||||||
res.json(channels);
|
res.json(channels);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
|
route({
|
||||||
|
requestBody: "ChannelModifySchema",
|
||||||
|
permission: "MANAGE_CHANNELS",
|
||||||
|
responses: {
|
||||||
|
201: {
|
||||||
|
body: "Channel",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
// creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
|
// creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
@ -54,7 +78,19 @@ router.post(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }),
|
route({
|
||||||
|
requestBody: "ChannelReorderSchema",
|
||||||
|
permission: "MANAGE_CHANNELS",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
// changes guild channel position
|
// changes guild channel position
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
|
@ -16,37 +16,51 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { emitEvent, GuildDeleteEvent, Guild } from "@spacebar/util";
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { HTTPError } from "lambert-server";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Guild, GuildDeleteEvent, emitEvent } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// discord prefixes this route with /delete instead of using the delete method
|
// discord prefixes this route with /delete instead of using the delete method
|
||||||
// docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild
|
// docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild
|
||||||
router.post("/", route({}), async (req: Request, res: Response) => {
|
router.post(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
const guild = await Guild.findOneOrFail({
|
responses: {
|
||||||
where: { id: guild_id },
|
204: {},
|
||||||
select: ["owner_id"],
|
401: {
|
||||||
});
|
body: "APIErrorResponse",
|
||||||
if (guild.owner_id !== req.user_id)
|
|
||||||
throw new HTTPError("You are not the owner of this guild", 401);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
Guild.delete({ id: guild_id }), // this will also delete all guild related data
|
|
||||||
emitEvent({
|
|
||||||
event: "GUILD_DELETE",
|
|
||||||
data: {
|
|
||||||
id: guild_id,
|
|
||||||
},
|
},
|
||||||
guild_id: guild_id,
|
404: {
|
||||||
} as GuildDeleteEvent),
|
body: "APIErrorResponse",
|
||||||
]);
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
return res.sendStatus(204);
|
const guild = await Guild.findOneOrFail({
|
||||||
});
|
where: { id: guild_id },
|
||||||
|
select: ["owner_id"],
|
||||||
|
});
|
||||||
|
if (guild.owner_id !== req.user_id)
|
||||||
|
throw new HTTPError("You are not the owner of this guild", 401);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
Guild.delete({ id: guild_id }), // this will also delete all guild related data
|
||||||
|
emitEvent({
|
||||||
|
event: "GUILD_DELETE",
|
||||||
|
data: {
|
||||||
|
id: guild_id,
|
||||||
|
},
|
||||||
|
guild_id: guild_id,
|
||||||
|
} as GuildDeleteEvent),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return res.sendStatus(204);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,40 +16,50 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
// TODO:
|
route({
|
||||||
// Load from database
|
responses: {
|
||||||
// Admin control, but for now it allows anyone to be discoverable
|
200: {
|
||||||
|
body: "GuildDiscoveryRequirementsResponse",
|
||||||
res.send({
|
},
|
||||||
guild_id: guild_id,
|
|
||||||
safe_environment: true,
|
|
||||||
healthy: true,
|
|
||||||
health_score_pending: false,
|
|
||||||
size: true,
|
|
||||||
nsfw_properties: {},
|
|
||||||
protected: true,
|
|
||||||
sufficient: true,
|
|
||||||
sufficient_without_grace_period: true,
|
|
||||||
valid_rules_channel: true,
|
|
||||||
retention_healthy: true,
|
|
||||||
engagement_healthy: true,
|
|
||||||
age: true,
|
|
||||||
minimum_age: 0,
|
|
||||||
health_score: {
|
|
||||||
avg_nonnew_participators: 0,
|
|
||||||
avg_nonnew_communicators: 0,
|
|
||||||
num_intentful_joiners: 0,
|
|
||||||
perc_ret_w1_intentful: 0,
|
|
||||||
},
|
},
|
||||||
minimum_size: 0,
|
}),
|
||||||
});
|
async (req: Request, res: Response) => {
|
||||||
});
|
const { guild_id } = req.params;
|
||||||
|
// TODO:
|
||||||
|
// Load from database
|
||||||
|
// Admin control, but for now it allows anyone to be discoverable
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
guild_id: guild_id,
|
||||||
|
safe_environment: true,
|
||||||
|
healthy: true,
|
||||||
|
health_score_pending: false,
|
||||||
|
size: true,
|
||||||
|
nsfw_properties: {},
|
||||||
|
protected: true,
|
||||||
|
sufficient: true,
|
||||||
|
sufficient_without_grace_period: true,
|
||||||
|
valid_rules_channel: true,
|
||||||
|
retention_healthy: true,
|
||||||
|
engagement_healthy: true,
|
||||||
|
age: true,
|
||||||
|
minimum_age: 0,
|
||||||
|
health_score: {
|
||||||
|
avg_nonnew_participators: 0,
|
||||||
|
avg_nonnew_communicators: 0,
|
||||||
|
num_intentful_joiners: 0,
|
||||||
|
perc_ret_w1_intentful: 0,
|
||||||
|
},
|
||||||
|
minimum_size: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,55 +16,95 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Config,
|
Config,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
emitEvent,
|
|
||||||
Emoji,
|
Emoji,
|
||||||
|
EmojiCreateSchema,
|
||||||
|
EmojiModifySchema,
|
||||||
GuildEmojisUpdateEvent,
|
GuildEmojisUpdateEvent,
|
||||||
handleFile,
|
|
||||||
Member,
|
Member,
|
||||||
Snowflake,
|
Snowflake,
|
||||||
User,
|
User,
|
||||||
EmojiCreateSchema,
|
emitEvent,
|
||||||
EmojiModifySchema,
|
handleFile,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIEmojiArray",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
|
||||||
const emojis = await Emoji.find({
|
const emojis = await Emoji.find({
|
||||||
where: { guild_id: guild_id },
|
where: { guild_id: guild_id },
|
||||||
relations: ["user"],
|
relations: ["user"],
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json(emojis);
|
return res.json(emojis);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id, emoji_id } = req.params;
|
"/:emoji_id",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Emoji",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id, emoji_id } = req.params;
|
||||||
|
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
|
||||||
const emoji = await Emoji.findOneOrFail({
|
const emoji = await Emoji.findOneOrFail({
|
||||||
where: { guild_id: guild_id, id: emoji_id },
|
where: { guild_id: guild_id, id: emoji_id },
|
||||||
relations: ["user"],
|
relations: ["user"],
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json(emoji);
|
return res.json(emoji);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({
|
route({
|
||||||
body: "EmojiCreateSchema",
|
requestBody: "EmojiCreateSchema",
|
||||||
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
||||||
|
responses: {
|
||||||
|
201: {
|
||||||
|
body: "Emoji",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
@ -113,8 +153,16 @@ router.post(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/:emoji_id",
|
"/:emoji_id",
|
||||||
route({
|
route({
|
||||||
body: "EmojiModifySchema",
|
requestBody: "EmojiModifySchema",
|
||||||
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Emoji",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { emoji_id, guild_id } = req.params;
|
const { emoji_id, guild_id } = req.params;
|
||||||
@ -141,7 +189,15 @@ router.patch(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:emoji_id",
|
"/:emoji_id",
|
||||||
route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
|
route({
|
||||||
|
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { emoji_id, guild_id } = req.params;
|
const { emoji_id, guild_id } = req.params;
|
||||||
|
|
||||||
|
@ -16,46 +16,79 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
|
Guild,
|
||||||
|
GuildUpdateEvent,
|
||||||
|
GuildUpdateSchema,
|
||||||
|
Member,
|
||||||
|
SpacebarApiErrors,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
getPermission,
|
getPermission,
|
||||||
getRights,
|
getRights,
|
||||||
Guild,
|
|
||||||
GuildUpdateEvent,
|
|
||||||
handleFile,
|
handleFile,
|
||||||
Member,
|
|
||||||
GuildUpdateSchema,
|
|
||||||
SpacebarApiErrors,
|
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
body: "APIGuildWithJoinedAt",
|
||||||
|
},
|
||||||
|
401: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
const [guild, member] = await Promise.all([
|
const [guild, member] = await Promise.all([
|
||||||
Guild.findOneOrFail({ where: { id: guild_id } }),
|
Guild.findOneOrFail({ where: { id: guild_id } }),
|
||||||
Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
|
Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
|
||||||
]);
|
]);
|
||||||
if (!member)
|
if (!member)
|
||||||
throw new HTTPError(
|
throw new HTTPError(
|
||||||
"You are not a member of the guild you are trying to access",
|
"You are not a member of the guild you are trying to access",
|
||||||
401,
|
401,
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.send({
|
return res.send({
|
||||||
...guild,
|
...guild,
|
||||||
joined_at: member?.joined_at,
|
joined_at: member?.joined_at,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "GuildUpdateSchema", permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
requestBody: "GuildUpdateSchema",
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
body: "GuildUpdateSchema",
|
||||||
|
},
|
||||||
|
401: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as GuildUpdateSchema;
|
const body = req.body as GuildUpdateSchema;
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
|
@ -16,15 +16,22 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Invite, PublicInviteRelation } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Invite, PublicInviteRelation } from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIInviteArray",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
|
@ -16,17 +16,27 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
// TODO: member verification
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
// TODO: member verification
|
||||||
|
|
||||||
res.status(404).json({
|
res.status(404).json({
|
||||||
message: "Unknown Guild Member Verification Form",
|
message: "Unknown Guild Member Verification Form",
|
||||||
code: 10068,
|
code: 10068,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,38 +16,91 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Member,
|
emitEvent,
|
||||||
|
Emoji,
|
||||||
getPermission,
|
getPermission,
|
||||||
getRights,
|
getRights,
|
||||||
Role,
|
|
||||||
GuildMemberUpdateEvent,
|
|
||||||
emitEvent,
|
|
||||||
Sticker,
|
|
||||||
Emoji,
|
|
||||||
Guild,
|
Guild,
|
||||||
|
GuildMemberUpdateEvent,
|
||||||
handleFile,
|
handleFile,
|
||||||
|
Member,
|
||||||
MemberChangeSchema,
|
MemberChangeSchema,
|
||||||
|
PublicMemberProjection,
|
||||||
|
PublicUserProjection,
|
||||||
|
Role,
|
||||||
|
Sticker,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id, member_id } = req.params;
|
"/",
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIPublicMember",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id, member_id } = req.params;
|
||||||
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
|
||||||
const member = await Member.findOneOrFail({
|
const member = await Member.findOneOrFail({
|
||||||
where: { id: member_id, guild_id },
|
where: { id: member_id, guild_id },
|
||||||
});
|
relations: ["roles", "user"],
|
||||||
|
select: {
|
||||||
|
index: true,
|
||||||
|
// only grab public member props
|
||||||
|
...Object.fromEntries(
|
||||||
|
PublicMemberProjection.map((x) => [x, true]),
|
||||||
|
),
|
||||||
|
// and public user props
|
||||||
|
user: Object.fromEntries(
|
||||||
|
PublicUserProjection.map((x) => [x, true]),
|
||||||
|
),
|
||||||
|
roles: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return res.json(member);
|
return res.json({
|
||||||
});
|
...member.toPublicMember(),
|
||||||
|
user: member.user.toPublicUser(),
|
||||||
|
roles: member.roles.map((x) => x.id),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "MemberChangeSchema" }),
|
route({
|
||||||
|
requestBody: "MemberChangeSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Member",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const member_id =
|
const member_id =
|
||||||
@ -119,54 +172,81 @@ router.patch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
router.put("/", route({}), async (req: Request, res: Response) => {
|
router.put(
|
||||||
// TODO: Lurker mode
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "MemberJoinGuildResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
// TODO: Lurker mode
|
||||||
|
|
||||||
const rights = await getRights(req.user_id);
|
const rights = await getRights(req.user_id);
|
||||||
|
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
let { member_id } = req.params;
|
let { member_id } = req.params;
|
||||||
if (member_id === "@me") {
|
if (member_id === "@me") {
|
||||||
member_id = req.user_id;
|
member_id = req.user_id;
|
||||||
rights.hasThrow("JOIN_GUILDS");
|
rights.hasThrow("JOIN_GUILDS");
|
||||||
} else {
|
} else {
|
||||||
// TODO: join others by controller
|
// TODO: join others by controller
|
||||||
}
|
}
|
||||||
|
|
||||||
const guild = await Guild.findOneOrFail({
|
const guild = await Guild.findOneOrFail({
|
||||||
where: { id: guild_id },
|
where: { id: guild_id },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emoji = await Emoji.find({
|
const emoji = await Emoji.find({
|
||||||
where: { guild_id: guild_id },
|
where: { guild_id: guild_id },
|
||||||
});
|
});
|
||||||
|
|
||||||
const roles = await Role.find({
|
const roles = await Role.find({
|
||||||
where: { guild_id: guild_id },
|
where: { guild_id: guild_id },
|
||||||
});
|
});
|
||||||
|
|
||||||
const stickers = await Sticker.find({
|
const stickers = await Sticker.find({
|
||||||
where: { guild_id: guild_id },
|
where: { guild_id: guild_id },
|
||||||
});
|
});
|
||||||
|
|
||||||
await Member.addToGuild(member_id, guild_id);
|
await Member.addToGuild(member_id, guild_id);
|
||||||
res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
|
res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.delete("/", route({}), async (req: Request, res: Response) => {
|
router.delete(
|
||||||
const { guild_id, member_id } = req.params;
|
"/",
|
||||||
const permission = await getPermission(req.user_id, guild_id);
|
route({
|
||||||
const rights = await getRights(req.user_id);
|
responses: {
|
||||||
if (member_id === "@me" || member_id === req.user_id) {
|
204: {},
|
||||||
// TODO: unless force-joined
|
403: {
|
||||||
rights.hasThrow("SELF_LEAVE_GROUPS");
|
body: "APIErrorResponse",
|
||||||
} else {
|
},
|
||||||
rights.hasThrow("KICK_BAN_MEMBERS");
|
},
|
||||||
permission.hasThrow("KICK_MEMBERS");
|
}),
|
||||||
}
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id, member_id } = req.params;
|
||||||
|
const permission = await getPermission(req.user_id, guild_id);
|
||||||
|
const rights = await getRights(req.user_id);
|
||||||
|
if (member_id === "@me" || member_id === req.user_id) {
|
||||||
|
// TODO: unless force-joined
|
||||||
|
rights.hasThrow("SELF_LEAVE_GROUPS");
|
||||||
|
} else {
|
||||||
|
rights.hasThrow("KICK_BAN_MEMBERS");
|
||||||
|
permission.hasThrow("KICK_MEMBERS");
|
||||||
|
}
|
||||||
|
|
||||||
await Member.removeFromGuild(member_id, guild_id);
|
await Member.removeFromGuild(member_id, guild_id);
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,15 +16,26 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getPermission, Member, PermissionResolvable } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { getPermission, Member, PermissionResolvable } from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "MemberNickChangeSchema" }),
|
route({
|
||||||
|
requestBody: "MemberNickChangeSchema",
|
||||||
|
responses: {
|
||||||
|
200: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
|
let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
|
||||||
|
@ -16,15 +16,23 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Member } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Member } from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_ROLES" }),
|
route({
|
||||||
|
permission: "MANAGE_ROLES",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id, role_id, member_id } = req.params;
|
const { guild_id, role_id, member_id } = req.params;
|
||||||
|
|
||||||
@ -35,7 +43,13 @@ router.delete(
|
|||||||
|
|
||||||
router.put(
|
router.put(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_ROLES" }),
|
route({
|
||||||
|
permission: "MANAGE_ROLES",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
403: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id, role_id, member_id } = req.params;
|
const { guild_id, role_id, member_id } = req.params;
|
||||||
|
|
||||||
|
@ -16,35 +16,58 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { Member, PublicMemberProjection } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { MoreThan } from "typeorm";
|
import { Member, PublicMemberProjection } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
import { MoreThan } from "typeorm";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// TODO: send over websocket
|
// TODO: send over websocket
|
||||||
// TODO: check for GUILD_MEMBERS intent
|
// TODO: check for GUILD_MEMBERS intent
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
const limit = Number(req.query.limit) || 1;
|
route({
|
||||||
if (limit > 1000 || limit < 1)
|
query: {
|
||||||
throw new HTTPError("Limit must be between 1 and 1000");
|
limit: {
|
||||||
const after = `${req.query.after}`;
|
type: "number",
|
||||||
const query = after ? { id: MoreThan(after) } : {};
|
description:
|
||||||
|
"max number of members to return (1-1000). default 1",
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIMemberArray",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
const limit = Number(req.query.limit) || 1;
|
||||||
|
if (limit > 1000 || limit < 1)
|
||||||
|
throw new HTTPError("Limit must be between 1 and 1000");
|
||||||
|
const after = `${req.query.after}`;
|
||||||
|
const query = after ? { id: MoreThan(after) } : {};
|
||||||
|
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
|
||||||
const members = await Member.find({
|
const members = await Member.find({
|
||||||
where: { guild_id, ...query },
|
where: { guild_id, ...query },
|
||||||
select: PublicMemberProjection,
|
select: PublicMemberProjection,
|
||||||
take: limit,
|
take: limit,
|
||||||
order: { id: "ASC" },
|
order: { id: "ASC" },
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json(members);
|
return res.json(members);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -18,140 +18,159 @@
|
|||||||
|
|
||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { getPermission, FieldErrors, Message, Channel } from "@spacebar/util";
|
import { Channel, FieldErrors, Message, getPermission } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { FindManyOptions, In, Like } from "typeorm";
|
import { FindManyOptions, In, Like } from "typeorm";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const {
|
"/",
|
||||||
channel_id,
|
route({
|
||||||
content,
|
responses: {
|
||||||
// include_nsfw, // TODO
|
200: {
|
||||||
offset,
|
body: "GuildMessagesSearchResponse",
|
||||||
sort_order,
|
},
|
||||||
// sort_by, // TODO: Handle 'relevance'
|
403: {
|
||||||
limit,
|
body: "APIErrorResponse",
|
||||||
author_id,
|
},
|
||||||
} = req.query;
|
422: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
const parsedLimit = Number(limit) || 50;
|
|
||||||
if (parsedLimit < 1 || parsedLimit > 100)
|
|
||||||
throw new HTTPError("limit must be between 1 and 100", 422);
|
|
||||||
|
|
||||||
if (sort_order) {
|
|
||||||
if (
|
|
||||||
typeof sort_order != "string" ||
|
|
||||||
["desc", "asc"].indexOf(sort_order) == -1
|
|
||||||
)
|
|
||||||
throw FieldErrors({
|
|
||||||
sort_order: {
|
|
||||||
message: "Value must be one of ('desc', 'asc').",
|
|
||||||
code: "BASE_TYPE_CHOICES",
|
|
||||||
},
|
|
||||||
}); // todo this is wrong
|
|
||||||
}
|
|
||||||
|
|
||||||
const permissions = await getPermission(
|
|
||||||
req.user_id,
|
|
||||||
req.params.guild_id,
|
|
||||||
channel_id as string | undefined,
|
|
||||||
);
|
|
||||||
permissions.hasThrow("VIEW_CHANNEL");
|
|
||||||
if (!permissions.has("READ_MESSAGE_HISTORY"))
|
|
||||||
return res.json({ messages: [], total_results: 0 });
|
|
||||||
|
|
||||||
const query: FindManyOptions<Message> = {
|
|
||||||
order: {
|
|
||||||
timestamp: sort_order
|
|
||||||
? (sort_order.toUpperCase() as "ASC" | "DESC")
|
|
||||||
: "DESC",
|
|
||||||
},
|
|
||||||
take: parsedLimit || 0,
|
|
||||||
where: {
|
|
||||||
guild: {
|
|
||||||
id: req.params.guild_id,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relations: [
|
}),
|
||||||
"author",
|
async (req: Request, res: Response) => {
|
||||||
"webhook",
|
const {
|
||||||
"application",
|
channel_id,
|
||||||
"mentions",
|
content,
|
||||||
"mention_roles",
|
// include_nsfw, // TODO
|
||||||
"mention_channels",
|
offset,
|
||||||
"sticker_items",
|
sort_order,
|
||||||
"attachments",
|
// sort_by, // TODO: Handle 'relevance'
|
||||||
],
|
limit,
|
||||||
skip: offset ? Number(offset) : 0,
|
author_id,
|
||||||
};
|
} = req.query;
|
||||||
//@ts-ignore
|
|
||||||
if (channel_id) query.where.channel = { id: channel_id };
|
|
||||||
else {
|
|
||||||
// get all channel IDs that this user can access
|
|
||||||
const channels = await Channel.find({
|
|
||||||
where: { guild_id: req.params.guild_id },
|
|
||||||
select: ["id"],
|
|
||||||
});
|
|
||||||
const ids = [];
|
|
||||||
|
|
||||||
for (const channel of channels) {
|
const parsedLimit = Number(limit) || 50;
|
||||||
const perm = await getPermission(
|
if (parsedLimit < 1 || parsedLimit > 100)
|
||||||
req.user_id,
|
throw new HTTPError("limit must be between 1 and 100", 422);
|
||||||
req.params.guild_id,
|
|
||||||
channel.id,
|
if (sort_order) {
|
||||||
);
|
if (
|
||||||
if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY"))
|
typeof sort_order != "string" ||
|
||||||
continue;
|
["desc", "asc"].indexOf(sort_order) == -1
|
||||||
ids.push(channel.id);
|
)
|
||||||
|
throw FieldErrors({
|
||||||
|
sort_order: {
|
||||||
|
message: "Value must be one of ('desc', 'asc').",
|
||||||
|
code: "BASE_TYPE_CHOICES",
|
||||||
|
},
|
||||||
|
}); // todo this is wrong
|
||||||
}
|
}
|
||||||
|
|
||||||
//@ts-ignore
|
const permissions = await getPermission(
|
||||||
query.where.channel = { id: In(ids) };
|
req.user_id,
|
||||||
}
|
req.params.guild_id,
|
||||||
//@ts-ignore
|
channel_id as string | undefined,
|
||||||
if (author_id) query.where.author = { id: author_id };
|
);
|
||||||
//@ts-ignore
|
permissions.hasThrow("VIEW_CHANNEL");
|
||||||
if (content) query.where.content = Like(`%${content}%`);
|
if (!permissions.has("READ_MESSAGE_HISTORY"))
|
||||||
|
return res.json({ messages: [], total_results: 0 });
|
||||||
|
|
||||||
const messages: Message[] = await Message.find(query);
|
const query: FindManyOptions<Message> = {
|
||||||
|
order: {
|
||||||
const messagesDto = messages.map((x) => [
|
timestamp: sort_order
|
||||||
{
|
? (sort_order.toUpperCase() as "ASC" | "DESC")
|
||||||
id: x.id,
|
: "DESC",
|
||||||
type: x.type,
|
|
||||||
content: x.content,
|
|
||||||
channel_id: x.channel_id,
|
|
||||||
author: {
|
|
||||||
id: x.author?.id,
|
|
||||||
username: x.author?.username,
|
|
||||||
avatar: x.author?.avatar,
|
|
||||||
avatar_decoration: null,
|
|
||||||
discriminator: x.author?.discriminator,
|
|
||||||
public_flags: x.author?.public_flags,
|
|
||||||
},
|
},
|
||||||
attachments: x.attachments,
|
take: parsedLimit || 0,
|
||||||
embeds: x.embeds,
|
where: {
|
||||||
mentions: x.mentions,
|
guild: {
|
||||||
mention_roles: x.mention_roles,
|
id: req.params.guild_id,
|
||||||
pinned: x.pinned,
|
},
|
||||||
mention_everyone: x.mention_everyone,
|
},
|
||||||
tts: x.tts,
|
relations: [
|
||||||
timestamp: x.timestamp,
|
"author",
|
||||||
edited_timestamp: x.edited_timestamp,
|
"webhook",
|
||||||
flags: x.flags,
|
"application",
|
||||||
components: x.components,
|
"mentions",
|
||||||
hit: true,
|
"mention_roles",
|
||||||
},
|
"mention_channels",
|
||||||
]);
|
"sticker_items",
|
||||||
|
"attachments",
|
||||||
|
],
|
||||||
|
skip: offset ? Number(offset) : 0,
|
||||||
|
};
|
||||||
|
//@ts-ignore
|
||||||
|
if (channel_id) query.where.channel = { id: channel_id };
|
||||||
|
else {
|
||||||
|
// get all channel IDs that this user can access
|
||||||
|
const channels = await Channel.find({
|
||||||
|
where: { guild_id: req.params.guild_id },
|
||||||
|
select: ["id"],
|
||||||
|
});
|
||||||
|
const ids = [];
|
||||||
|
|
||||||
return res.json({
|
for (const channel of channels) {
|
||||||
messages: messagesDto,
|
const perm = await getPermission(
|
||||||
total_results: messages.length,
|
req.user_id,
|
||||||
});
|
req.params.guild_id,
|
||||||
});
|
channel.id,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!perm.has("VIEW_CHANNEL") ||
|
||||||
|
!perm.has("READ_MESSAGE_HISTORY")
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
ids.push(channel.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
query.where.channel = { id: In(ids) };
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
if (author_id) query.where.author = { id: author_id };
|
||||||
|
//@ts-ignore
|
||||||
|
if (content) query.where.content = Like(`%${content}%`);
|
||||||
|
|
||||||
|
const messages: Message[] = await Message.find(query);
|
||||||
|
|
||||||
|
const messagesDto = messages.map((x) => [
|
||||||
|
{
|
||||||
|
id: x.id,
|
||||||
|
type: x.type,
|
||||||
|
content: x.content,
|
||||||
|
channel_id: x.channel_id,
|
||||||
|
author: {
|
||||||
|
id: x.author?.id,
|
||||||
|
username: x.author?.username,
|
||||||
|
avatar: x.author?.avatar,
|
||||||
|
avatar_decoration: null,
|
||||||
|
discriminator: x.author?.discriminator,
|
||||||
|
public_flags: x.author?.public_flags,
|
||||||
|
},
|
||||||
|
attachments: x.attachments,
|
||||||
|
embeds: x.embeds,
|
||||||
|
mentions: x.mentions,
|
||||||
|
mention_roles: x.mention_roles,
|
||||||
|
pinned: x.pinned,
|
||||||
|
mention_everyone: x.mention_everyone,
|
||||||
|
tts: x.tts,
|
||||||
|
timestamp: x.timestamp,
|
||||||
|
edited_timestamp: x.edited_timestamp,
|
||||||
|
flags: x.flags,
|
||||||
|
components: x.components,
|
||||||
|
hit: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
messages: messagesDto,
|
||||||
|
total_results: messages.length,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -31,7 +31,20 @@ const router = Router();
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:member_id",
|
"/:member_id",
|
||||||
route({ body: "MemberChangeProfileSchema" }),
|
route({
|
||||||
|
requestBody: "MemberChangeProfileSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Member",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
// const member_id =
|
// const member_id =
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { Guild, Member, Snowflake } from "@spacebar/util";
|
|
||||||
import { LessThan, IsNull } from "typeorm";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Guild, Member, Snowflake } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
import { IsNull, LessThan } from "typeorm";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
//Returns all inactive members, respecting role hierarchy
|
//Returns all inactive members, respecting role hierarchy
|
||||||
export const inactiveMembers = async (
|
const inactiveMembers = async (
|
||||||
guild_id: string,
|
guild_id: string,
|
||||||
user_id: string,
|
user_id: string,
|
||||||
days: number,
|
days: number,
|
||||||
@ -80,25 +80,46 @@ export const inactiveMembers = async (
|
|||||||
return members;
|
return members;
|
||||||
};
|
};
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const days = parseInt(req.query.days as string);
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
body: "GuildPruneResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const days = parseInt(req.query.days as string);
|
||||||
|
|
||||||
let roles = req.query.include_roles;
|
let roles = req.query.include_roles;
|
||||||
if (typeof roles === "string") roles = [roles]; //express will return array otherwise
|
if (typeof roles === "string") roles = [roles]; //express will return array otherwise
|
||||||
|
|
||||||
const members = await inactiveMembers(
|
const members = await inactiveMembers(
|
||||||
req.params.guild_id,
|
req.params.guild_id,
|
||||||
req.user_id,
|
req.user_id,
|
||||||
days,
|
days,
|
||||||
roles as string[],
|
roles as string[],
|
||||||
);
|
);
|
||||||
|
|
||||||
res.send({ pruned: members.length });
|
res.send({ pruned: members.length });
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }),
|
route({
|
||||||
|
permission: "KICK_MEMBERS",
|
||||||
|
right: "KICK_BAN_MEMBERS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GuildPurgeResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const days = parseInt(req.body.days);
|
const days = parseInt(req.body.days);
|
||||||
|
|
||||||
|
@ -16,22 +16,35 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { getIpAdress, getVoiceRegions, route } from "@spacebar/api";
|
||||||
import { Guild } from "@spacebar/util";
|
import { Guild } from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { getVoiceRegions, route, getIpAdress } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
route({
|
||||||
//TODO we should use an enum for guild's features and not hardcoded strings
|
responses: {
|
||||||
return res.json(
|
200: {
|
||||||
await getVoiceRegions(
|
body: "APIGuildVoiceRegion",
|
||||||
getIpAdress(req),
|
},
|
||||||
guild.features.includes("VIP_REGIONS"),
|
404: {
|
||||||
),
|
body: "APIErrorResponse",
|
||||||
);
|
},
|
||||||
});
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||||
|
//TODO we should use an enum for guild's features and not hardcoded strings
|
||||||
|
return res.json(
|
||||||
|
await getVoiceRegions(
|
||||||
|
getIpAdress(req),
|
||||||
|
guild.features.includes("VIP_REGIONS"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,31 +16,63 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Role,
|
|
||||||
Member,
|
|
||||||
GuildRoleUpdateEvent,
|
|
||||||
GuildRoleDeleteEvent,
|
|
||||||
emitEvent,
|
emitEvent,
|
||||||
|
GuildRoleDeleteEvent,
|
||||||
|
GuildRoleUpdateEvent,
|
||||||
handleFile,
|
handleFile,
|
||||||
|
Member,
|
||||||
|
Role,
|
||||||
RoleModifySchema,
|
RoleModifySchema,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id, role_id } = req.params;
|
"/",
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
route({
|
||||||
const role = await Role.findOneOrFail({ where: { guild_id, id: role_id } });
|
responses: {
|
||||||
return res.json(role);
|
200: {
|
||||||
});
|
body: "Role",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id, role_id } = req.params;
|
||||||
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
const role = await Role.findOneOrFail({
|
||||||
|
where: { guild_id, id: role_id },
|
||||||
|
});
|
||||||
|
return res.json(role);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_ROLES" }),
|
route({
|
||||||
|
permission: "MANAGE_ROLES",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id, role_id } = req.params;
|
const { guild_id, role_id } = req.params;
|
||||||
if (role_id === guild_id)
|
if (role_id === guild_id)
|
||||||
@ -69,7 +101,24 @@ router.delete(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }),
|
route({
|
||||||
|
requestBody: "RoleModifySchema",
|
||||||
|
permission: "MANAGE_ROLES",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Role",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { role_id, guild_id } = req.params;
|
const { role_id, guild_id } = req.params;
|
||||||
const body = req.body as RoleModifySchema;
|
const body = req.body as RoleModifySchema;
|
||||||
|
@ -16,21 +16,20 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Role,
|
|
||||||
getPermission,
|
|
||||||
Member,
|
|
||||||
GuildRoleCreateEvent,
|
|
||||||
GuildRoleUpdateEvent,
|
|
||||||
emitEvent,
|
|
||||||
Config,
|
Config,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
|
emitEvent,
|
||||||
|
GuildRoleCreateEvent,
|
||||||
|
GuildRoleUpdateEvent,
|
||||||
|
Member,
|
||||||
|
Role,
|
||||||
RoleModifySchema,
|
RoleModifySchema,
|
||||||
RolePositionUpdateSchema,
|
RolePositionUpdateSchema,
|
||||||
Snowflake,
|
Snowflake,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
import { Not } from "typeorm";
|
import { Not } from "typeorm";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
@ -47,7 +46,21 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }),
|
route({
|
||||||
|
requestBody: "RoleModifySchema",
|
||||||
|
permission: "MANAGE_ROLES",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Role",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const guild_id = req.params.guild_id;
|
const guild_id = req.params.guild_id;
|
||||||
const body = req.body as RoleModifySchema;
|
const body = req.body as RoleModifySchema;
|
||||||
@ -104,14 +117,25 @@ router.post(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "RolePositionUpdateSchema" }),
|
route({
|
||||||
|
requestBody: "RolePositionUpdateSchema",
|
||||||
|
permission: "MANAGE_ROLES",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIRoleArray",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const body = req.body as RolePositionUpdateSchema;
|
const body = req.body as RolePositionUpdateSchema;
|
||||||
|
|
||||||
const perms = await getPermission(req.user_id, guild_id);
|
|
||||||
perms.hasThrow("MANAGE_ROLES");
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
body.map(async (x) =>
|
body.map(async (x) =>
|
||||||
Role.update({ guild_id, id: x.id }, { position: x.position }),
|
Role.update({ guild_id, id: x.id }, { position: x.position }),
|
||||||
|
@ -16,29 +16,42 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
emitEvent,
|
|
||||||
GuildStickersUpdateEvent,
|
GuildStickersUpdateEvent,
|
||||||
Member,
|
Member,
|
||||||
|
ModifyGuildStickerSchema,
|
||||||
Snowflake,
|
Snowflake,
|
||||||
Sticker,
|
Sticker,
|
||||||
StickerFormatType,
|
StickerFormatType,
|
||||||
StickerType,
|
StickerType,
|
||||||
|
emitEvent,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
ModifyGuildStickerSchema,
|
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Router, Request, Response } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import multer from "multer";
|
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
import multer from "multer";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIStickerArray",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
|
||||||
res.json(await Sticker.find({ where: { guild_id } }));
|
res.json(await Sticker.find({ where: { guild_id } }));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const bodyParser = multer({
|
const bodyParser = multer({
|
||||||
limits: {
|
limits: {
|
||||||
@ -54,7 +67,18 @@ router.post(
|
|||||||
bodyParser,
|
bodyParser,
|
||||||
route({
|
route({
|
||||||
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
||||||
body: "ModifyGuildStickerSchema",
|
requestBody: "ModifyGuildStickerSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Sticker",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
if (!req.file) throw new HTTPError("missing file");
|
if (!req.file) throw new HTTPError("missing file");
|
||||||
@ -81,7 +105,7 @@ router.post(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export function getStickerFormat(mime_type: string) {
|
function getStickerFormat(mime_type: string) {
|
||||||
switch (mime_type) {
|
switch (mime_type) {
|
||||||
case "image/apng":
|
case "image/apng":
|
||||||
return StickerFormatType.APNG;
|
return StickerFormatType.APNG;
|
||||||
@ -98,20 +122,46 @@ export function getStickerFormat(mime_type: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get("/:sticker_id", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id, sticker_id } = req.params;
|
"/:sticker_id",
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Sticker",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id, sticker_id } = req.params;
|
||||||
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
|
||||||
res.json(
|
res.json(
|
||||||
await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }),
|
await Sticker.findOneOrFail({
|
||||||
);
|
where: { guild_id, id: sticker_id },
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:sticker_id",
|
"/:sticker_id",
|
||||||
route({
|
route({
|
||||||
body: "ModifyGuildStickerSchema",
|
requestBody: "ModifyGuildStickerSchema",
|
||||||
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Sticker",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id, sticker_id } = req.params;
|
const { guild_id, sticker_id } = req.params;
|
||||||
@ -141,7 +191,15 @@ async function sendStickerUpdateEvent(guild_id: string) {
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:sticker_id",
|
"/:sticker_id",
|
||||||
route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
|
route({
|
||||||
|
permission: "MANAGE_EMOJIS_AND_STICKERS",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id, sticker_id } = req.params;
|
const { guild_id, sticker_id } = req.params;
|
||||||
|
|
||||||
|
@ -16,11 +16,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
import { generateCode, route } from "@spacebar/api";
|
||||||
import { Guild, Template } from "@spacebar/util";
|
import { Guild, Template } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import { generateCode } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
@ -41,19 +40,46 @@ const TemplateGuildProjection: (keyof Guild)[] = [
|
|||||||
"icon",
|
"icon",
|
||||||
];
|
];
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APITemplateArray",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
const templates = await Template.find({
|
const templates = await Template.find({
|
||||||
where: { source_guild_id: guild_id },
|
where: { source_guild_id: guild_id },
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json(templates);
|
return res.json(templates);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
requestBody: "TemplateCreateSchema",
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Template",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const guild = await Guild.findOneOrFail({
|
const guild = await Guild.findOneOrFail({
|
||||||
@ -81,7 +107,13 @@ router.post(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:code",
|
"/:code",
|
||||||
route({ permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
200: { body: "Template" },
|
||||||
|
403: { body: "APIErrorResponse" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { code, guild_id } = req.params;
|
const { code, guild_id } = req.params;
|
||||||
|
|
||||||
@ -96,7 +128,13 @@ router.delete(
|
|||||||
|
|
||||||
router.put(
|
router.put(
|
||||||
"/:code",
|
"/:code",
|
||||||
route({ permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
200: { body: "Template" },
|
||||||
|
403: { body: "APIErrorResponse" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { code, guild_id } = req.params;
|
const { code, guild_id } = req.params;
|
||||||
const guild = await Guild.findOneOrFail({
|
const guild = await Guild.findOneOrFail({
|
||||||
@ -115,7 +153,14 @@ router.put(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:code",
|
"/:code",
|
||||||
route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
requestBody: "TemplateModifySchema",
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
200: { body: "Template" },
|
||||||
|
403: { body: "APIErrorResponse" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { code, guild_id } = req.params;
|
const { code, guild_id } = req.params;
|
||||||
const { name, description } = req.body;
|
const { name, description } = req.body;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
ChannelType,
|
ChannelType,
|
||||||
@ -23,8 +24,7 @@ import {
|
|||||||
Invite,
|
Invite,
|
||||||
VanityUrlSchema,
|
VanityUrlSchema,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Router, Request, Response } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@ -33,7 +33,20 @@ const InviteRegex = /\W/g;
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GuildVanityUrlResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||||
@ -60,7 +73,21 @@ router.get(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
requestBody: "VanityUrlSchema",
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GuildVanityUrlCreateResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const body = req.body as VanityUrlSchema;
|
const body = req.body as VanityUrlSchema;
|
||||||
@ -80,6 +107,17 @@ router.patch(
|
|||||||
where: { guild_id, type: ChannelType.GUILD_TEXT },
|
where: { guild_id, type: ChannelType.GUILD_TEXT },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!guild.features.includes("ALIASABLE_NAMES")) {
|
||||||
|
await Invite.update(
|
||||||
|
{ guild_id },
|
||||||
|
{
|
||||||
|
code: code,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.json({ code });
|
||||||
|
}
|
||||||
|
|
||||||
await Invite.create({
|
await Invite.create({
|
||||||
vanity_url: true,
|
vanity_url: true,
|
||||||
code: code,
|
code: code,
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
ChannelType,
|
ChannelType,
|
||||||
@ -26,7 +27,6 @@ import {
|
|||||||
VoiceStateUpdateEvent,
|
VoiceStateUpdateEvent,
|
||||||
VoiceStateUpdateSchema,
|
VoiceStateUpdateSchema,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@ -34,7 +34,21 @@ const router = Router();
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "VoiceStateUpdateSchema" }),
|
route({
|
||||||
|
requestBody: "VoiceStateUpdateSchema",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as VoiceStateUpdateSchema;
|
const body = req.body as VoiceStateUpdateSchema;
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
|
@ -16,27 +16,49 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { Guild, Member, GuildUpdateWelcomeScreenSchema } from "@spacebar/util";
|
|
||||||
import { HTTPError } from "lambert-server";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Guild, GuildUpdateWelcomeScreenSchema, Member } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const guild_id = req.params.guild_id;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GuildWelcomeScreen",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const guild_id = req.params.guild_id;
|
||||||
|
|
||||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
|
||||||
res.json(guild.welcome_screen);
|
res.json(guild.welcome_screen);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({
|
route({
|
||||||
body: "GuildUpdateWelcomeScreenSchema",
|
requestBody: "GuildUpdateWelcomeScreenSchema",
|
||||||
permission: "MANAGE_GUILD",
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const guild_id = req.params.guild_id;
|
const guild_id = req.params.guild_id;
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { Permissions, Guild, Invite, Channel, Member } from "@spacebar/util";
|
|
||||||
import { HTTPError } from "lambert-server";
|
|
||||||
import { random, route } from "@spacebar/api";
|
import { random, route } from "@spacebar/api";
|
||||||
|
import { Channel, Guild, Invite, Member, Permissions } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
@ -32,77 +32,90 @@ const router: Router = Router();
|
|||||||
|
|
||||||
// https://discord.com/developers/docs/resources/guild#get-guild-widget
|
// https://discord.com/developers/docs/resources/guild#get-guild-widget
|
||||||
// TODO: Cache the response for a guild for 5 minutes regardless of response
|
// TODO: Cache the response for a guild for 5 minutes regardless of response
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GuildWidgetJsonResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||||
if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
|
if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
|
||||||
|
|
||||||
// Fetch existing widget invite for widget channel
|
// Fetch existing widget invite for widget channel
|
||||||
let invite = await Invite.findOne({
|
let invite = await Invite.findOne({
|
||||||
where: { channel_id: guild.widget_channel_id },
|
where: { channel_id: guild.widget_channel_id },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (guild.widget_channel_id && !invite) {
|
if (guild.widget_channel_id && !invite) {
|
||||||
// Create invite for channel if none exists
|
// Create invite for channel if none exists
|
||||||
// TODO: Refactor invite create code to a shared function
|
// TODO: Refactor invite create code to a shared function
|
||||||
const max_age = 86400; // 24 hours
|
const max_age = 86400; // 24 hours
|
||||||
const expires_at = new Date(max_age * 1000 + Date.now());
|
const expires_at = new Date(max_age * 1000 + Date.now());
|
||||||
|
|
||||||
invite = await Invite.create({
|
invite = await Invite.create({
|
||||||
code: random(),
|
code: random(),
|
||||||
temporary: false,
|
temporary: false,
|
||||||
uses: 0,
|
uses: 0,
|
||||||
max_uses: 0,
|
max_uses: 0,
|
||||||
max_age: max_age,
|
max_age: max_age,
|
||||||
expires_at,
|
expires_at,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
guild_id,
|
guild_id,
|
||||||
channel_id: guild.widget_channel_id,
|
channel_id: guild.widget_channel_id,
|
||||||
}).save();
|
}).save();
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch voice channels, and the @everyone permissions object
|
|
||||||
const channels: { id: string; name: string; position: number }[] = [];
|
|
||||||
|
|
||||||
(
|
|
||||||
await Channel.find({
|
|
||||||
where: { guild_id: guild_id, type: 2 },
|
|
||||||
order: { position: "ASC" },
|
|
||||||
})
|
|
||||||
).filter((doc) => {
|
|
||||||
// Only return channels where @everyone has the CONNECT permission
|
|
||||||
if (
|
|
||||||
doc.permission_overwrites === undefined ||
|
|
||||||
Permissions.channelPermission(
|
|
||||||
doc.permission_overwrites,
|
|
||||||
Permissions.FLAGS.CONNECT,
|
|
||||||
) === Permissions.FLAGS.CONNECT
|
|
||||||
) {
|
|
||||||
channels.push({
|
|
||||||
id: doc.id,
|
|
||||||
name: doc.name ?? "Unknown channel",
|
|
||||||
position: doc.position ?? 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch members
|
// Fetch voice channels, and the @everyone permissions object
|
||||||
// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
|
const channels: { id: string; name: string; position: number }[] = [];
|
||||||
const members = await Member.find({ where: { guild_id: guild_id } });
|
|
||||||
|
|
||||||
// Construct object to respond with
|
(
|
||||||
const data = {
|
await Channel.find({
|
||||||
id: guild_id,
|
where: { guild_id: guild_id, type: 2 },
|
||||||
name: guild.name,
|
order: { position: "ASC" },
|
||||||
instant_invite: invite?.code,
|
})
|
||||||
channels: channels,
|
).filter((doc) => {
|
||||||
members: members,
|
// Only return channels where @everyone has the CONNECT permission
|
||||||
presence_count: guild.presence_count,
|
if (
|
||||||
};
|
doc.permission_overwrites === undefined ||
|
||||||
|
Permissions.channelPermission(
|
||||||
|
doc.permission_overwrites,
|
||||||
|
Permissions.FLAGS.CONNECT,
|
||||||
|
) === Permissions.FLAGS.CONNECT
|
||||||
|
) {
|
||||||
|
channels.push({
|
||||||
|
id: doc.id,
|
||||||
|
name: doc.name ?? "Unknown channel",
|
||||||
|
position: doc.position ?? 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
res.set("Cache-Control", "public, max-age=300");
|
// Fetch members
|
||||||
return res.json(data);
|
// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
|
||||||
});
|
const members = await Member.find({ where: { guild_id: guild_id } });
|
||||||
|
|
||||||
|
// Construct object to respond with
|
||||||
|
const data = {
|
||||||
|
id: guild_id,
|
||||||
|
name: guild.name,
|
||||||
|
instant_invite: invite?.code,
|
||||||
|
channels: channels,
|
||||||
|
members: members,
|
||||||
|
presence_count: guild.presence_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
res.set("Cache-Control", "public, max-age=300");
|
||||||
|
return res.json(data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -18,11 +18,11 @@
|
|||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { Guild } from "@spacebar/util";
|
|
||||||
import { HTTPError } from "lambert-server";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Guild } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
@ -31,130 +31,178 @@ const router: Router = Router();
|
|||||||
|
|
||||||
// https://discord.com/developers/docs/resources/guild#get-guild-widget-image
|
// https://discord.com/developers/docs/resources/guild#get-guild-widget-image
|
||||||
// TODO: Cache the response
|
// TODO: Cache the response
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||||
if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
|
if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
|
||||||
|
|
||||||
// Fetch guild information
|
// Fetch guild information
|
||||||
const icon = guild.icon;
|
const icon = guild.icon;
|
||||||
const name = guild.name;
|
const name = guild.name;
|
||||||
const presence = guild.presence_count + " ONLINE";
|
const presence = guild.presence_count + " ONLINE";
|
||||||
|
|
||||||
// Fetch parameter
|
// Fetch parameter
|
||||||
const style = req.query.style?.toString() || "shield";
|
const style = req.query.style?.toString() || "shield";
|
||||||
if (
|
if (
|
||||||
!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)
|
!["shield", "banner1", "banner2", "banner3", "banner4"].includes(
|
||||||
) {
|
style,
|
||||||
throw new HTTPError(
|
)
|
||||||
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
|
) {
|
||||||
400,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup canvas
|
|
||||||
const { createCanvas } = require("canvas");
|
|
||||||
const { loadImage } = require("canvas");
|
|
||||||
const sizeOf = require("image-size");
|
|
||||||
|
|
||||||
// TODO: Widget style templates need Spacebar branding
|
|
||||||
const source = path.join(
|
|
||||||
__dirname,
|
|
||||||
"..",
|
|
||||||
"..",
|
|
||||||
"..",
|
|
||||||
"..",
|
|
||||||
"..",
|
|
||||||
"assets",
|
|
||||||
"widget",
|
|
||||||
`${style}.png`,
|
|
||||||
);
|
|
||||||
if (!fs.existsSync(source)) {
|
|
||||||
throw new HTTPError("Widget template does not exist.", 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create base template image for parameter
|
|
||||||
const { width, height } = await sizeOf(source);
|
|
||||||
const canvas = createCanvas(width, height);
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
const template = await loadImage(source);
|
|
||||||
ctx.drawImage(template, 0, 0);
|
|
||||||
|
|
||||||
// Add the guild specific information to the template asset image
|
|
||||||
switch (style) {
|
|
||||||
case "shield":
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
await drawText(
|
|
||||||
ctx,
|
|
||||||
73,
|
|
||||||
13,
|
|
||||||
"#FFFFFF",
|
|
||||||
"thin 10px Verdana",
|
|
||||||
presence,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "banner1":
|
|
||||||
if (icon) await drawIcon(ctx, 20, 27, 50, icon);
|
|
||||||
await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
|
|
||||||
await drawText(
|
|
||||||
ctx,
|
|
||||||
83,
|
|
||||||
66,
|
|
||||||
"#C9D2F0FF",
|
|
||||||
"thin 11px Verdana",
|
|
||||||
presence,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "banner2":
|
|
||||||
if (icon) await drawIcon(ctx, 13, 19, 36, icon);
|
|
||||||
await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
|
|
||||||
await drawText(
|
|
||||||
ctx,
|
|
||||||
62,
|
|
||||||
49,
|
|
||||||
"#C9D2F0FF",
|
|
||||||
"thin 11px Verdana",
|
|
||||||
presence,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "banner3":
|
|
||||||
if (icon) await drawIcon(ctx, 20, 20, 50, icon);
|
|
||||||
await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
|
|
||||||
await drawText(
|
|
||||||
ctx,
|
|
||||||
83,
|
|
||||||
58,
|
|
||||||
"#C9D2F0FF",
|
|
||||||
"thin 11px Verdana",
|
|
||||||
presence,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "banner4":
|
|
||||||
if (icon) await drawIcon(ctx, 21, 136, 50, icon);
|
|
||||||
await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
|
|
||||||
await drawText(
|
|
||||||
ctx,
|
|
||||||
84,
|
|
||||||
171,
|
|
||||||
"#C9D2F0FF",
|
|
||||||
"thin 12px Verdana",
|
|
||||||
presence,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new HTTPError(
|
throw new HTTPError(
|
||||||
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
|
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
|
||||||
400,
|
400,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return final image
|
// Setup canvas
|
||||||
const buffer = canvas.toBuffer("image/png");
|
const { createCanvas } = require("canvas");
|
||||||
res.set("Content-Type", "image/png");
|
const { loadImage } = require("canvas");
|
||||||
res.set("Cache-Control", "public, max-age=3600");
|
const sizeOf = require("image-size");
|
||||||
return res.send(buffer);
|
|
||||||
});
|
// TODO: Widget style templates need Spacebar branding
|
||||||
|
const source = path.join(
|
||||||
|
__dirname,
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"assets",
|
||||||
|
"widget",
|
||||||
|
`${style}.png`,
|
||||||
|
);
|
||||||
|
if (!fs.existsSync(source)) {
|
||||||
|
throw new HTTPError("Widget template does not exist.", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create base template image for parameter
|
||||||
|
const { width, height } = await sizeOf(source);
|
||||||
|
const canvas = createCanvas(width, height);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const template = await loadImage(source);
|
||||||
|
ctx.drawImage(template, 0, 0);
|
||||||
|
|
||||||
|
// Add the guild specific information to the template asset image
|
||||||
|
switch (style) {
|
||||||
|
case "shield":
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
73,
|
||||||
|
13,
|
||||||
|
"#FFFFFF",
|
||||||
|
"thin 10px Verdana",
|
||||||
|
presence,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "banner1":
|
||||||
|
if (icon) await drawIcon(ctx, 20, 27, 50, icon);
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
83,
|
||||||
|
51,
|
||||||
|
"#FFFFFF",
|
||||||
|
"12px Verdana",
|
||||||
|
name,
|
||||||
|
22,
|
||||||
|
);
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
83,
|
||||||
|
66,
|
||||||
|
"#C9D2F0FF",
|
||||||
|
"thin 11px Verdana",
|
||||||
|
presence,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "banner2":
|
||||||
|
if (icon) await drawIcon(ctx, 13, 19, 36, icon);
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
62,
|
||||||
|
34,
|
||||||
|
"#FFFFFF",
|
||||||
|
"12px Verdana",
|
||||||
|
name,
|
||||||
|
15,
|
||||||
|
);
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
62,
|
||||||
|
49,
|
||||||
|
"#C9D2F0FF",
|
||||||
|
"thin 11px Verdana",
|
||||||
|
presence,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "banner3":
|
||||||
|
if (icon) await drawIcon(ctx, 20, 20, 50, icon);
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
83,
|
||||||
|
44,
|
||||||
|
"#FFFFFF",
|
||||||
|
"12px Verdana",
|
||||||
|
name,
|
||||||
|
27,
|
||||||
|
);
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
83,
|
||||||
|
58,
|
||||||
|
"#C9D2F0FF",
|
||||||
|
"thin 11px Verdana",
|
||||||
|
presence,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "banner4":
|
||||||
|
if (icon) await drawIcon(ctx, 21, 136, 50, icon);
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
84,
|
||||||
|
156,
|
||||||
|
"#FFFFFF",
|
||||||
|
"13px Verdana",
|
||||||
|
name,
|
||||||
|
27,
|
||||||
|
);
|
||||||
|
await drawText(
|
||||||
|
ctx,
|
||||||
|
84,
|
||||||
|
171,
|
||||||
|
"#C9D2F0FF",
|
||||||
|
"thin 12px Verdana",
|
||||||
|
presence,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new HTTPError(
|
||||||
|
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return final image
|
||||||
|
const buffer = canvas.toBuffer("image/png");
|
||||||
|
res.set("Content-Type", "image/png");
|
||||||
|
res.set("Cache-Control", "public, max-age=3600");
|
||||||
|
return res.send(buffer);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
async function drawIcon(
|
async function drawIcon(
|
||||||
canvas: any,
|
canvas: any,
|
||||||
|
@ -16,28 +16,55 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { Guild, WidgetModifySchema } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Guild, WidgetModifySchema } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
// https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
|
// https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { guild_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "GuildWidgetSettingsResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
|
||||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
enabled: guild.widget_enabled || false,
|
enabled: guild.widget_enabled || false,
|
||||||
channel_id: guild.widget_channel_id || null,
|
channel_id: guild.widget_channel_id || null,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// https://discord.com/developers/docs/resources/guild#modify-guild-widget
|
// https://discord.com/developers/docs/resources/guild#modify-guild-widget
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }),
|
route({
|
||||||
|
requestBody: "WidgetModifySchema",
|
||||||
|
permission: "MANAGE_GUILD",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "WidgetModifySchema",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as WidgetModifySchema;
|
const body = req.body as WidgetModifySchema;
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
|
@ -16,16 +16,16 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import {
|
|
||||||
Guild,
|
|
||||||
Config,
|
|
||||||
getRights,
|
|
||||||
Member,
|
|
||||||
DiscordApiErrors,
|
|
||||||
GuildCreateSchema,
|
|
||||||
} from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import {
|
||||||
|
Config,
|
||||||
|
DiscordApiErrors,
|
||||||
|
Guild,
|
||||||
|
GuildCreateSchema,
|
||||||
|
Member,
|
||||||
|
getRights,
|
||||||
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
@ -33,7 +33,21 @@ const router: Router = Router();
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }),
|
route({
|
||||||
|
requestBody: "GuildCreateSchema",
|
||||||
|
right: "CREATE_GUILDS",
|
||||||
|
responses: {
|
||||||
|
201: {
|
||||||
|
body: "GuildCreateResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as GuildCreateSchema;
|
const body = req.body as GuildCreateSchema;
|
||||||
|
|
||||||
@ -58,7 +72,7 @@ router.post(
|
|||||||
|
|
||||||
await Member.addToGuild(req.user_id, guild.id);
|
await Member.addToGuild(req.user_id, guild.id);
|
||||||
|
|
||||||
res.status(201).json({ id: guild.id });
|
res.status(201).json(guild);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -16,72 +16,91 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Template,
|
Config,
|
||||||
|
DiscordApiErrors,
|
||||||
Guild,
|
Guild,
|
||||||
|
GuildTemplateCreateSchema,
|
||||||
|
Member,
|
||||||
Role,
|
Role,
|
||||||
Snowflake,
|
Snowflake,
|
||||||
Config,
|
Template,
|
||||||
Member,
|
|
||||||
GuildTemplateCreateSchema,
|
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
import { DiscordApiErrors } from "@spacebar/util";
|
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/:code", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { allowDiscordTemplates, allowRaws, enabled } =
|
"/:code",
|
||||||
Config.get().templates;
|
route({
|
||||||
if (!enabled)
|
responses: {
|
||||||
res.json({
|
200: {
|
||||||
code: 403,
|
body: "Template",
|
||||||
message: "Template creation & usage is disabled on this instance.",
|
|
||||||
}).sendStatus(403);
|
|
||||||
|
|
||||||
const { code } = req.params;
|
|
||||||
|
|
||||||
if (code.startsWith("discord:")) {
|
|
||||||
if (!allowDiscordTemplates)
|
|
||||||
return res
|
|
||||||
.json({
|
|
||||||
code: 403,
|
|
||||||
message:
|
|
||||||
"Discord templates cannot be used on this instance.",
|
|
||||||
})
|
|
||||||
.sendStatus(403);
|
|
||||||
const discordTemplateID = code.split("discord:", 2)[1];
|
|
||||||
|
|
||||||
const discordTemplateData = await fetch(
|
|
||||||
`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
|
|
||||||
{
|
|
||||||
method: "get",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
},
|
},
|
||||||
);
|
403: {
|
||||||
return res.json(await discordTemplateData.json());
|
body: "APIErrorResponse",
|
||||||
}
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { allowDiscordTemplates, allowRaws, enabled } =
|
||||||
|
Config.get().templates;
|
||||||
|
if (!enabled)
|
||||||
|
res.json({
|
||||||
|
code: 403,
|
||||||
|
message:
|
||||||
|
"Template creation & usage is disabled on this instance.",
|
||||||
|
}).sendStatus(403);
|
||||||
|
|
||||||
if (code.startsWith("external:")) {
|
const { code } = req.params;
|
||||||
if (!allowRaws)
|
|
||||||
return res
|
|
||||||
.json({
|
|
||||||
code: 403,
|
|
||||||
message: "Importing raws is disabled on this instance.",
|
|
||||||
})
|
|
||||||
.sendStatus(403);
|
|
||||||
|
|
||||||
return res.json(code.split("external:", 2)[1]);
|
if (code.startsWith("discord:")) {
|
||||||
}
|
if (!allowDiscordTemplates)
|
||||||
|
return res
|
||||||
|
.json({
|
||||||
|
code: 403,
|
||||||
|
message:
|
||||||
|
"Discord templates cannot be used on this instance.",
|
||||||
|
})
|
||||||
|
.sendStatus(403);
|
||||||
|
const discordTemplateID = code.split("discord:", 2)[1];
|
||||||
|
|
||||||
const template = await Template.findOneOrFail({ where: { code: code } });
|
const discordTemplateData = await fetch(
|
||||||
res.json(template);
|
`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
|
||||||
});
|
{
|
||||||
|
method: "get",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return res.json(await discordTemplateData.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.startsWith("external:")) {
|
||||||
|
if (!allowRaws)
|
||||||
|
return res
|
||||||
|
.json({
|
||||||
|
code: 403,
|
||||||
|
message: "Importing raws is disabled on this instance.",
|
||||||
|
})
|
||||||
|
.sendStatus(403);
|
||||||
|
|
||||||
|
return res.json(code.split("external:", 2)[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = await Template.findOneOrFail({
|
||||||
|
where: { code: code },
|
||||||
|
});
|
||||||
|
res.json(template);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:code",
|
"/:code",
|
||||||
route({ body: "GuildTemplateCreateSchema" }),
|
route({ requestBody: "GuildTemplateCreateSchema" }),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
enabled,
|
enabled,
|
||||||
|
@ -16,36 +16,68 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
|
DiscordApiErrors,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
getPermission,
|
getPermission,
|
||||||
Guild,
|
Guild,
|
||||||
Invite,
|
Invite,
|
||||||
InviteDeleteEvent,
|
InviteDeleteEvent,
|
||||||
User,
|
|
||||||
PublicInviteRelation,
|
PublicInviteRelation,
|
||||||
|
User,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/:code", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { code } = req.params;
|
"/:code",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
body: "Invite",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { code } = req.params;
|
||||||
|
|
||||||
const invite = await Invite.findOneOrFail({
|
const invite = await Invite.findOneOrFail({
|
||||||
where: { code },
|
where: { code },
|
||||||
relations: PublicInviteRelation,
|
relations: PublicInviteRelation,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).send(invite);
|
res.status(200).send(invite);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:code",
|
"/:code",
|
||||||
route({ right: "USE_MASS_INVITES" }),
|
route({
|
||||||
|
right: "USE_MASS_INVITES",
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
body: "Invite",
|
||||||
|
},
|
||||||
|
401: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
|
if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
|
||||||
|
|
||||||
const { code } = req.params;
|
const { code } = req.params;
|
||||||
const { guild_id } = await Invite.findOneOrFail({
|
const { guild_id } = await Invite.findOneOrFail({
|
||||||
where: { code: code },
|
where: { code: code },
|
||||||
@ -75,33 +107,56 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// * cant use permission of route() function because path doesn't have guild_id/channel_id
|
// * cant use permission of route() function because path doesn't have guild_id/channel_id
|
||||||
router.delete("/:code", route({}), async (req: Request, res: Response) => {
|
router.delete(
|
||||||
const { code } = req.params;
|
"/:code",
|
||||||
const invite = await Invite.findOneOrFail({ where: { code } });
|
route({
|
||||||
const { guild_id, channel_id } = invite;
|
responses: {
|
||||||
|
"200": {
|
||||||
|
body: "Invite",
|
||||||
|
},
|
||||||
|
401: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { code } = req.params;
|
||||||
|
const invite = await Invite.findOneOrFail({ where: { code } });
|
||||||
|
const { guild_id, channel_id } = invite;
|
||||||
|
|
||||||
const permission = await getPermission(req.user_id, guild_id, channel_id);
|
const permission = await getPermission(
|
||||||
|
req.user_id,
|
||||||
if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS"))
|
guild_id,
|
||||||
throw new HTTPError(
|
channel_id,
|
||||||
"You missing the MANAGE_GUILD or MANAGE_CHANNELS permission",
|
|
||||||
401,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all([
|
if (
|
||||||
Invite.delete({ code }),
|
!permission.has("MANAGE_GUILD") &&
|
||||||
emitEvent({
|
!permission.has("MANAGE_CHANNELS")
|
||||||
event: "INVITE_DELETE",
|
)
|
||||||
guild_id: guild_id,
|
throw new HTTPError(
|
||||||
data: {
|
"You missing the MANAGE_GUILD or MANAGE_CHANNELS permission",
|
||||||
channel_id: channel_id,
|
401,
|
||||||
guild_id: guild_id,
|
);
|
||||||
code: code,
|
|
||||||
},
|
|
||||||
} as InviteDeleteEvent),
|
|
||||||
]);
|
|
||||||
|
|
||||||
res.json({ invite: invite });
|
await Promise.all([
|
||||||
});
|
Invite.delete({ code }),
|
||||||
|
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 });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,126 +16,168 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
ApiError,
|
ApiError,
|
||||||
Application,
|
Application,
|
||||||
ApplicationAuthorizeSchema,
|
ApplicationAuthorizeSchema,
|
||||||
getPermission,
|
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
Member,
|
Member,
|
||||||
Permissions,
|
Permissions,
|
||||||
User,
|
User,
|
||||||
|
getPermission,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// TODO: scopes, other oauth types
|
// TODO: scopes, other oauth types
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
// const { client_id, scope, response_type, redirect_url } = req.query;
|
"/",
|
||||||
const { client_id } = req.query;
|
route({
|
||||||
|
responses: {
|
||||||
const app = await Application.findOne({
|
// TODO: I really didn't feel like typing all of it out
|
||||||
where: {
|
200: {},
|
||||||
id: client_id as string,
|
400: {
|
||||||
},
|
body: "APIErrorResponse",
|
||||||
relations: ["bot"],
|
},
|
||||||
});
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
// TODO: use DiscordApiErrors
|
|
||||||
// findOneOrFail throws code 404
|
|
||||||
if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
|
|
||||||
if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
|
|
||||||
|
|
||||||
const bot = app.bot;
|
|
||||||
delete app.bot;
|
|
||||||
|
|
||||||
const user = await User.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
id: req.user_id,
|
|
||||||
bot: false,
|
|
||||||
},
|
|
||||||
select: ["id", "username", "avatar", "discriminator", "public_flags"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const guilds = await Member.find({
|
|
||||||
where: {
|
|
||||||
user: {
|
|
||||||
id: req.user_id,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relations: ["guild", "roles"],
|
}),
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
async (req: Request, res: Response) => {
|
||||||
//@ts-ignore
|
// const { client_id, scope, response_type, redirect_url } = req.query;
|
||||||
// prettier-ignore
|
const { client_id } = req.query;
|
||||||
select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const guildsWithPermissions = guilds.map((x) => {
|
const app = await Application.findOne({
|
||||||
const perms =
|
where: {
|
||||||
x.guild.owner_id === user.id
|
id: client_id as string,
|
||||||
? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
|
},
|
||||||
: Permissions.finalPermission({
|
relations: ["bot"],
|
||||||
user: {
|
});
|
||||||
id: user.id,
|
|
||||||
roles: x.roles?.map((x) => x.id) || [],
|
|
||||||
},
|
|
||||||
guild: {
|
|
||||||
roles: x?.roles || [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
// TODO: use DiscordApiErrors
|
||||||
id: x.guild.id,
|
// findOneOrFail throws code 404
|
||||||
name: x.guild.name,
|
if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
|
||||||
icon: x.guild.icon,
|
if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
|
||||||
mfa_level: x.guild.mfa_level,
|
|
||||||
permissions: perms.bitfield.toString(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.json({
|
const bot = app.bot;
|
||||||
guilds: guildsWithPermissions,
|
delete app.bot;
|
||||||
user: {
|
|
||||||
id: user.id,
|
const user = await User.findOneOrFail({
|
||||||
username: user.username,
|
where: {
|
||||||
avatar: user.avatar,
|
id: req.user_id,
|
||||||
avatar_decoration: null, // TODO
|
bot: false,
|
||||||
discriminator: user.discriminator,
|
},
|
||||||
public_flags: user.public_flags,
|
select: [
|
||||||
},
|
"id",
|
||||||
application: {
|
"username",
|
||||||
id: app.id,
|
"avatar",
|
||||||
name: app.name,
|
"discriminator",
|
||||||
icon: app.icon,
|
"public_flags",
|
||||||
description: app.description,
|
],
|
||||||
summary: app.summary,
|
});
|
||||||
type: app.type,
|
|
||||||
hook: app.hook,
|
const guilds = await Member.find({
|
||||||
guild_id: null, // TODO support guilds
|
where: {
|
||||||
bot_public: app.bot_public,
|
user: {
|
||||||
bot_require_code_grant: app.bot_require_code_grant,
|
id: req.user_id,
|
||||||
verify_key: app.verify_key,
|
},
|
||||||
flags: app.flags,
|
},
|
||||||
},
|
relations: ["guild", "roles"],
|
||||||
bot: {
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
id: bot.id,
|
//@ts-ignore
|
||||||
username: bot.username,
|
// prettier-ignore
|
||||||
avatar: bot.avatar,
|
select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"],
|
||||||
avatar_decoration: null, // TODO
|
});
|
||||||
discriminator: bot.discriminator,
|
|
||||||
public_flags: bot.public_flags,
|
const guildsWithPermissions = guilds.map((x) => {
|
||||||
bot: true,
|
const perms =
|
||||||
approximated_guild_count: 0, // TODO
|
x.guild.owner_id === user.id
|
||||||
},
|
? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
|
||||||
authorized: false,
|
: Permissions.finalPermission({
|
||||||
});
|
user: {
|
||||||
});
|
id: user.id,
|
||||||
|
roles: x.roles?.map((x) => x.id) || [],
|
||||||
|
},
|
||||||
|
guild: {
|
||||||
|
roles: x?.roles || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: x.guild.id,
|
||||||
|
name: x.guild.name,
|
||||||
|
icon: x.guild.icon,
|
||||||
|
mfa_level: x.guild.mfa_level,
|
||||||
|
permissions: perms.bitfield.toString(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
guilds: guildsWithPermissions,
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
avatar: user.avatar,
|
||||||
|
avatar_decoration: null, // TODO
|
||||||
|
discriminator: user.discriminator,
|
||||||
|
public_flags: user.public_flags,
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
id: app.id,
|
||||||
|
name: app.name,
|
||||||
|
icon: app.icon,
|
||||||
|
description: app.description,
|
||||||
|
summary: app.summary,
|
||||||
|
type: app.type,
|
||||||
|
hook: app.hook,
|
||||||
|
guild_id: null, // TODO support guilds
|
||||||
|
bot_public: app.bot_public,
|
||||||
|
bot_require_code_grant: app.bot_require_code_grant,
|
||||||
|
verify_key: app.verify_key,
|
||||||
|
flags: app.flags,
|
||||||
|
},
|
||||||
|
bot: {
|
||||||
|
id: bot.id,
|
||||||
|
username: bot.username,
|
||||||
|
avatar: bot.avatar,
|
||||||
|
avatar_decoration: null, // TODO
|
||||||
|
discriminator: bot.discriminator,
|
||||||
|
public_flags: bot.public_flags,
|
||||||
|
bot: true,
|
||||||
|
approximated_guild_count: 0, // TODO
|
||||||
|
},
|
||||||
|
authorized: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ApplicationAuthorizeSchema" }),
|
route({
|
||||||
|
requestBody: "ApplicationAuthorizeSchema",
|
||||||
|
query: {
|
||||||
|
client_id: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "OAuthAuthorizeResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as ApplicationAuthorizeSchema;
|
const body = req.body as ApplicationAuthorizeSchema;
|
||||||
// const { client_id, scope, response_type, redirect_url } = req.query;
|
// const { client_id, scope, response_type, redirect_url } = req.query;
|
||||||
|
@ -16,29 +16,39 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { Config } from "@spacebar/util";
|
import { Config } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), (req: Request, res: Response) => {
|
router.get(
|
||||||
const { general } = Config.get();
|
"/",
|
||||||
res.send({
|
route({
|
||||||
ping: "pong!",
|
responses: {
|
||||||
instance: {
|
200: {
|
||||||
id: general.instanceId,
|
body: "InstancePingResponse",
|
||||||
name: general.instanceName,
|
},
|
||||||
description: general.instanceDescription,
|
|
||||||
image: general.image,
|
|
||||||
|
|
||||||
correspondenceEmail: general.correspondenceEmail,
|
|
||||||
correspondenceUserID: general.correspondenceUserID,
|
|
||||||
|
|
||||||
frontPage: general.frontPage,
|
|
||||||
tosPage: general.tosPage,
|
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
});
|
(req: Request, res: Response) => {
|
||||||
|
const { general } = Config.get();
|
||||||
|
res.send({
|
||||||
|
ping: "pong!",
|
||||||
|
instance: {
|
||||||
|
id: general.instanceId,
|
||||||
|
name: general.instanceName,
|
||||||
|
description: general.instanceDescription,
|
||||||
|
image: general.image,
|
||||||
|
|
||||||
|
correspondenceEmail: general.correspondenceEmail,
|
||||||
|
correspondenceUserID: general.correspondenceUserID,
|
||||||
|
|
||||||
|
frontPage: general.frontPage,
|
||||||
|
tosPage: general.tosPage,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,25 +16,38 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { Config } from "@spacebar/util";
|
import { Config } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { cdn, gateway, api } = Config.get();
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "InstanceDomainsResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { cdn, gateway, api } = Config.get();
|
||||||
|
|
||||||
const IdentityForm = {
|
const IdentityForm = {
|
||||||
cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001",
|
cdn:
|
||||||
gateway:
|
cdn.endpointPublic ||
|
||||||
gateway.endpointPublic ||
|
process.env.CDN ||
|
||||||
process.env.GATEWAY ||
|
"http://localhost:3001",
|
||||||
"ws://localhost:3001",
|
gateway:
|
||||||
defaultApiVersion: api.defaultVersion ?? 9,
|
gateway.endpointPublic ||
|
||||||
apiEndpoint: api.endpointPublic ?? "http://localhost:3001/api/",
|
process.env.GATEWAY ||
|
||||||
};
|
"ws://localhost:3001",
|
||||||
|
defaultApiVersion: api.defaultVersion ?? 9,
|
||||||
|
apiEndpoint: api.endpointPublic ?? "http://localhost:3001/api/",
|
||||||
|
};
|
||||||
|
|
||||||
res.json(IdentityForm);
|
res.json(IdentityForm);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,14 +16,24 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { Config } from "@spacebar/util";
|
import { Config } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { general } = Config.get();
|
"/",
|
||||||
res.json(general);
|
route({
|
||||||
});
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIGeneralConfiguration",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { general } = Config.get();
|
||||||
|
res.json(general);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,14 +16,24 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { Config } from "@spacebar/util";
|
import { Config } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { limits } = Config.get();
|
"/",
|
||||||
res.json(limits);
|
route({
|
||||||
});
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APILimitsConfiguration",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { limits } = Config.get();
|
||||||
|
res.json(limits);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -28,20 +28,33 @@ import {
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
if (!Config.get().security.statsWorldReadable) {
|
"/",
|
||||||
const rights = await getRights(req.user_id);
|
route({
|
||||||
rights.hasThrow("VIEW_SERVER_STATS");
|
responses: {
|
||||||
}
|
200: {
|
||||||
|
body: "InstanceStatsResponse",
|
||||||
res.json({
|
},
|
||||||
counts: {
|
403: {
|
||||||
user: await User.count(),
|
body: "APIErrorResponse",
|
||||||
guild: await Guild.count(),
|
},
|
||||||
message: await Message.count(),
|
|
||||||
members: await Member.count(),
|
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
});
|
async (req: Request, res: Response) => {
|
||||||
|
if (!Config.get().security.statsWorldReadable) {
|
||||||
|
const rights = await getRights(req.user_id);
|
||||||
|
rights.hasThrow("VIEW_SERVER_STATS");
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
counts: {
|
||||||
|
user: await User.count(),
|
||||||
|
guild: await Guild.count(),
|
||||||
|
message: await Message.count(),
|
||||||
|
members: await Member.count(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,14 +16,22 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { AckBulkSchema, ReadState } from "@spacebar/util";
|
import { AckBulkSchema, ReadState } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "AckBulkSchema" }),
|
route({
|
||||||
|
requestBody: "AckBulkSchema",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as AckBulkSchema;
|
const body = req.body as AckBulkSchema;
|
||||||
|
|
||||||
|
@ -16,14 +16,22 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/", route({}), (req: Request, res: Response) => {
|
router.post(
|
||||||
// TODO:
|
"/",
|
||||||
res.sendStatus(204);
|
route({
|
||||||
});
|
responses: {
|
||||||
|
204: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
(req: Request, res: Response) => {
|
||||||
|
// TODO:
|
||||||
|
res.sendStatus(204);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,16 +16,28 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { StickerPack } from "@spacebar/util";
|
import { StickerPack } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const sticker_packs = await StickerPack.find({ relations: ["stickers"] });
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIStickerPackArray",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const sticker_packs = await StickerPack.find({
|
||||||
|
relations: ["stickers"],
|
||||||
|
});
|
||||||
|
|
||||||
res.json({ sticker_packs });
|
res.json({ sticker_packs });
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,15 +16,25 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Sticker } from "@spacebar/util";
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Sticker } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { sticker_id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Sticker",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { sticker_id } = req.params;
|
||||||
|
|
||||||
res.json(await Sticker.find({ where: { id: sticker_id } }));
|
res.json(await Sticker.find({ where: { id: sticker_id } }));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,14 +16,22 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ right: "OPERATOR" }),
|
route({
|
||||||
|
right: "OPERATOR",
|
||||||
|
responses: {
|
||||||
|
200: {},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
|
console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
|
@ -16,37 +16,53 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import { FieldErrors, Release } from "@spacebar/util";
|
import { FieldErrors, Release } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const platform = req.query.platform;
|
"/",
|
||||||
|
route({
|
||||||
if (!platform)
|
responses: {
|
||||||
throw FieldErrors({
|
200: {
|
||||||
platform: {
|
body: "UpdatesResponse",
|
||||||
code: "BASE_TYPE_REQUIRED",
|
|
||||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
|
||||||
},
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const platform = req.query.platform;
|
||||||
|
|
||||||
|
if (!platform)
|
||||||
|
throw FieldErrors({
|
||||||
|
platform: {
|
||||||
|
code: "BASE_TYPE_REQUIRED",
|
||||||
|
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const release = await Release.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
enabled: true,
|
||||||
|
platform: platform as string,
|
||||||
|
},
|
||||||
|
order: { pub_date: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const release = await Release.findOneOrFail({
|
res.json({
|
||||||
where: {
|
name: release.name,
|
||||||
enabled: true,
|
pub_date: release.pub_date,
|
||||||
platform: platform as string,
|
url: release.url,
|
||||||
},
|
notes: release.notes,
|
||||||
order: { pub_date: "DESC" },
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
res.json({
|
|
||||||
name: release.name,
|
|
||||||
pub_date: release.pub_date,
|
|
||||||
url: release.url,
|
|
||||||
notes: release.notes,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -30,7 +30,18 @@ const router = Router();
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ right: "MANAGE_USERS" }),
|
route({
|
||||||
|
right: "MANAGE_USERS",
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
403: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
await User.findOneOrFail({
|
await User.findOneOrFail({
|
||||||
where: { id: req.params.id },
|
where: { id: req.params.id },
|
||||||
|
@ -16,16 +16,26 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { User } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { User } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const { id } = req.params;
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "APIPublicUser",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
res.json(await User.getPublicUser(id));
|
res.json(await User.getPublicUser(id));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,23 +16,23 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import {
|
|
||||||
User,
|
|
||||||
Member,
|
|
||||||
UserProfileModifySchema,
|
|
||||||
handleFile,
|
|
||||||
PrivateUserProjection,
|
|
||||||
emitEvent,
|
|
||||||
UserUpdateEvent,
|
|
||||||
} from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import {
|
||||||
|
Member,
|
||||||
|
PrivateUserProjection,
|
||||||
|
User,
|
||||||
|
UserProfileModifySchema,
|
||||||
|
UserUpdateEvent,
|
||||||
|
emitEvent,
|
||||||
|
handleFile,
|
||||||
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ test: { response: { body: "UserProfileResponse" } } }),
|
route({ responses: { 200: { body: "UserProfileResponse" } } }),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
if (req.params.id === "@me") req.params.id = req.user_id;
|
if (req.params.id === "@me") req.params.id = req.user_id;
|
||||||
|
|
||||||
@ -84,18 +84,6 @@ router.get(
|
|||||||
|
|
||||||
// TODO: make proper DTO's in util?
|
// TODO: make proper DTO's in util?
|
||||||
|
|
||||||
const userDto = {
|
|
||||||
username: user.username,
|
|
||||||
discriminator: user.discriminator,
|
|
||||||
id: user.id,
|
|
||||||
public_flags: user.public_flags,
|
|
||||||
avatar: user.avatar,
|
|
||||||
accent_color: user.accent_color,
|
|
||||||
banner: user.banner,
|
|
||||||
bio: req.user_bot ? null : user.bio,
|
|
||||||
bot: user.bot,
|
|
||||||
};
|
|
||||||
|
|
||||||
const userProfile = {
|
const userProfile = {
|
||||||
bio: req.user_bot ? null : user.bio,
|
bio: req.user_bot ? null : user.bio,
|
||||||
accent_color: user.accent_color,
|
accent_color: user.accent_color,
|
||||||
@ -104,28 +92,6 @@ router.get(
|
|||||||
theme_colors: user.theme_colors,
|
theme_colors: user.theme_colors,
|
||||||
};
|
};
|
||||||
|
|
||||||
const guildMemberDto = guild_member
|
|
||||||
? {
|
|
||||||
avatar: guild_member.avatar,
|
|
||||||
banner: guild_member.banner,
|
|
||||||
bio: req.user_bot ? null : guild_member.bio,
|
|
||||||
communication_disabled_until:
|
|
||||||
guild_member.communication_disabled_until,
|
|
||||||
deaf: guild_member.deaf,
|
|
||||||
flags: user.flags,
|
|
||||||
is_pending: guild_member.pending,
|
|
||||||
pending: guild_member.pending, // why is this here twice, discord?
|
|
||||||
joined_at: guild_member.joined_at,
|
|
||||||
mute: guild_member.mute,
|
|
||||||
nick: guild_member.nick,
|
|
||||||
premium_since: guild_member.premium_since,
|
|
||||||
roles: guild_member.roles
|
|
||||||
.map((x) => x.id)
|
|
||||||
.filter((id) => id != guild_id),
|
|
||||||
user: userDto,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const guildMemberProfile = {
|
const guildMemberProfile = {
|
||||||
accent_color: null,
|
accent_color: null,
|
||||||
banner: guild_member?.banner || null,
|
banner: guild_member?.banner || null,
|
||||||
@ -139,11 +105,11 @@ router.get(
|
|||||||
premium_guild_since: premium_guild_since, // TODO
|
premium_guild_since: premium_guild_since, // TODO
|
||||||
premium_since: user.premium_since, // TODO
|
premium_since: user.premium_since, // TODO
|
||||||
mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
|
mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
|
||||||
user: userDto,
|
user: user.toPublicUser(),
|
||||||
premium_type: user.premium_type,
|
premium_type: user.premium_type,
|
||||||
profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
|
profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
|
||||||
user_profile: userProfile,
|
user_profile: userProfile,
|
||||||
guild_member: guild_id && guildMemberDto,
|
guild_member: guild_member?.toPublicMember(),
|
||||||
guild_member_profile: guild_id && guildMemberProfile,
|
guild_member_profile: guild_id && guildMemberProfile,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -151,7 +117,7 @@ router.get(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "UserProfileModifySchema" }),
|
route({ requestBody: "UserProfileModifySchema" }),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as UserProfileModifySchema;
|
const body = req.body as UserProfileModifySchema;
|
||||||
|
|
||||||
|
@ -16,17 +16,25 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { User } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { User, UserRelationsResponse } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
route({ test: { response: { body: "UserRelationsResponse" } } }),
|
route({
|
||||||
|
responses: {
|
||||||
|
200: { body: "UserRelationsResponse" },
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const mutual_relations: object[] = [];
|
const mutual_relations: UserRelationsResponse = [];
|
||||||
|
|
||||||
const requested_relations = await User.findOneOrFail({
|
const requested_relations = await User.findOneOrFail({
|
||||||
where: { id: req.params.id },
|
where: { id: req.params.id },
|
||||||
relations: ["relationships"],
|
relations: ["relationships"],
|
||||||
|
@ -16,32 +16,51 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, Router } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
Recipient,
|
|
||||||
DmChannelDTO,
|
|
||||||
Channel,
|
Channel,
|
||||||
DmChannelCreateSchema,
|
DmChannelCreateSchema,
|
||||||
|
DmChannelDTO,
|
||||||
|
Recipient,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const recipients = await Recipient.find({
|
"/",
|
||||||
where: { user_id: req.user_id, closed: false },
|
route({
|
||||||
relations: ["channel", "channel.recipients"],
|
responses: {
|
||||||
});
|
200: {
|
||||||
res.json(
|
body: "APIDMChannelArray",
|
||||||
await Promise.all(
|
},
|
||||||
recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])),
|
},
|
||||||
),
|
}),
|
||||||
);
|
async (req: Request, res: Response) => {
|
||||||
});
|
const recipients = await Recipient.find({
|
||||||
|
where: { user_id: req.user_id, closed: false },
|
||||||
|
relations: ["channel", "channel.recipients"],
|
||||||
|
});
|
||||||
|
res.json(
|
||||||
|
await Promise.all(
|
||||||
|
recipients.map((r) =>
|
||||||
|
DmChannelDTO.from(r.channel, [req.user_id]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "DmChannelCreateSchema" }),
|
route({
|
||||||
|
requestBody: "DmChannelCreateSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "DmChannelDTO",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const body = req.body as DmChannelCreateSchema;
|
const body = req.body as DmChannelCreateSchema;
|
||||||
res.json(
|
res.json(
|
||||||
|
@ -29,7 +29,7 @@ const router = Router();
|
|||||||
// TODO: connection update schema
|
// TODO: connection update schema
|
||||||
router.patch(
|
router.patch(
|
||||||
"/",
|
"/",
|
||||||
route({ body: "ConnectionUpdateSchema" }),
|
route({ requestBody: "ConnectionUpdateSchema" }),
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { connection_name, connection_id } = req.params;
|
const { connection_name, connection_id } = req.params;
|
||||||
const body = req.body as ConnectionUpdateSchema;
|
const body = req.body as ConnectionUpdateSchema;
|
||||||
|
@ -16,41 +16,58 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
|
||||||
import { Member, User } from "@spacebar/util";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { Member, User } from "@spacebar/util";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/", route({}), async (req: Request, res: Response) => {
|
router.post(
|
||||||
const user = await User.findOneOrFail({
|
"/",
|
||||||
where: { id: req.user_id },
|
route({
|
||||||
select: ["data"],
|
responses: {
|
||||||
}); //User object
|
204: {},
|
||||||
let correctpass = true;
|
401: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const user = await User.findOneOrFail({
|
||||||
|
where: { id: req.user_id },
|
||||||
|
select: ["data"],
|
||||||
|
}); //User object
|
||||||
|
let correctpass = true;
|
||||||
|
|
||||||
if (user.data.hash) {
|
if (user.data.hash) {
|
||||||
// guest accounts can delete accounts without password
|
// guest accounts can delete accounts without password
|
||||||
correctpass = await bcrypt.compare(req.body.password, user.data.hash);
|
correctpass = await bcrypt.compare(
|
||||||
if (!correctpass) {
|
req.body.password,
|
||||||
throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
|
user.data.hash,
|
||||||
|
);
|
||||||
|
if (!correctpass) {
|
||||||
|
throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: decrement guild member count
|
// TODO: decrement guild member count
|
||||||
|
|
||||||
if (correctpass) {
|
if (correctpass) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
User.delete({ id: req.user_id }),
|
User.delete({ id: req.user_id }),
|
||||||
Member.delete({ id: req.user_id }),
|
Member.delete({ id: req.user_id }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(401);
|
res.sendStatus(401);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,35 +16,52 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { User } from "@spacebar/util";
|
|
||||||
import { Router, Response, Request } from "express";
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
import { User } from "@spacebar/util";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/", route({}), async (req: Request, res: Response) => {
|
router.post(
|
||||||
const user = await User.findOneOrFail({
|
"/",
|
||||||
where: { id: req.user_id },
|
route({
|
||||||
select: ["data"],
|
responses: {
|
||||||
}); //User object
|
204: {},
|
||||||
let correctpass = true;
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const user = await User.findOneOrFail({
|
||||||
|
where: { id: req.user_id },
|
||||||
|
select: ["data"],
|
||||||
|
}); //User object
|
||||||
|
let correctpass = true;
|
||||||
|
|
||||||
if (user.data.hash) {
|
if (user.data.hash) {
|
||||||
// guest accounts can delete accounts without password
|
// guest accounts can delete accounts without password
|
||||||
correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/
|
correctpass = await bcrypt.compare(
|
||||||
}
|
req.body.password,
|
||||||
|
user.data.hash,
|
||||||
|
); //Not sure if user typed right password :/
|
||||||
|
}
|
||||||
|
|
||||||
if (correctpass) {
|
if (correctpass) {
|
||||||
await User.update({ id: req.user_id }, { disabled: true });
|
await User.update({ id: req.user_id }, { disabled: true });
|
||||||
|
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
} else {
|
} else {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
message: "Password does not match",
|
message: "Password does not match",
|
||||||
code: 50018,
|
code: 50018,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -16,79 +16,106 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
|
Config,
|
||||||
Guild,
|
Guild,
|
||||||
Member,
|
|
||||||
User,
|
|
||||||
GuildDeleteEvent,
|
GuildDeleteEvent,
|
||||||
GuildMemberRemoveEvent,
|
GuildMemberRemoveEvent,
|
||||||
|
Member,
|
||||||
|
User,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
Config,
|
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { route } from "@spacebar/api";
|
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const members = await Member.find({
|
"/",
|
||||||
relations: ["guild"],
|
route({
|
||||||
where: { id: req.user_id },
|
responses: {
|
||||||
});
|
200: {
|
||||||
|
body: "APIGuildArray",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const members = await Member.find({
|
||||||
|
relations: ["guild"],
|
||||||
|
where: { id: req.user_id },
|
||||||
|
});
|
||||||
|
|
||||||
let guild = members.map((x) => x.guild);
|
let guild = members.map((x) => x.guild);
|
||||||
|
|
||||||
if ("with_counts" in req.query && req.query.with_counts == "true") {
|
if ("with_counts" in req.query && req.query.with_counts == "true") {
|
||||||
guild = []; // TODO: Load guilds with user role permissions number
|
guild = []; // TODO: Load guilds with user role permissions number
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(guild);
|
res.json(guild);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// user send to leave a certain guild
|
// user send to leave a certain guild
|
||||||
router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
|
router.delete(
|
||||||
const { autoJoin } = Config.get().guild;
|
"/:guild_id",
|
||||||
const { guild_id } = req.params;
|
route({
|
||||||
const guild = await Guild.findOneOrFail({
|
responses: {
|
||||||
where: { id: guild_id },
|
204: {},
|
||||||
select: ["owner_id"],
|
400: {
|
||||||
});
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
if (!guild) throw new HTTPError("Guild doesn't exist", 404);
|
404: {
|
||||||
if (guild.owner_id === req.user_id)
|
body: "APIErrorResponse",
|
||||||
throw new HTTPError("You can't leave your own guild", 400);
|
|
||||||
if (
|
|
||||||
autoJoin.enabled &&
|
|
||||||
autoJoin.guilds.includes(guild_id) &&
|
|
||||||
!autoJoin.canLeave
|
|
||||||
) {
|
|
||||||
throw new HTTPError("You can't leave instance auto join guilds", 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
Member.delete({ id: req.user_id, guild_id: guild_id }),
|
|
||||||
emitEvent({
|
|
||||||
event: "GUILD_DELETE",
|
|
||||||
data: {
|
|
||||||
id: guild_id,
|
|
||||||
},
|
},
|
||||||
user_id: req.user_id,
|
|
||||||
} as GuildDeleteEvent),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const user = await User.getPublicUser(req.user_id);
|
|
||||||
|
|
||||||
await emitEvent({
|
|
||||||
event: "GUILD_MEMBER_REMOVE",
|
|
||||||
data: {
|
|
||||||
guild_id: guild_id,
|
|
||||||
user: user,
|
|
||||||
},
|
},
|
||||||
guild_id: guild_id,
|
}),
|
||||||
} as GuildMemberRemoveEvent);
|
async (req: Request, res: Response) => {
|
||||||
|
const { autoJoin } = Config.get().guild;
|
||||||
|
const { guild_id } = req.params;
|
||||||
|
const guild = await Guild.findOneOrFail({
|
||||||
|
where: { id: guild_id },
|
||||||
|
select: ["owner_id"],
|
||||||
|
});
|
||||||
|
|
||||||
return res.sendStatus(204);
|
if (!guild) throw new HTTPError("Guild doesn't exist", 404);
|
||||||
});
|
if (guild.owner_id === req.user_id)
|
||||||
|
throw new HTTPError("You can't leave your own guild", 400);
|
||||||
|
if (
|
||||||
|
autoJoin.enabled &&
|
||||||
|
autoJoin.guilds.includes(guild_id) &&
|
||||||
|
!autoJoin.canLeave
|
||||||
|
) {
|
||||||
|
throw new HTTPError(
|
||||||
|
"You can't leave instance auto join guilds",
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
Member.delete({ id: req.user_id, guild_id: guild_id }),
|
||||||
|
emitEvent({
|
||||||
|
event: "GUILD_DELETE",
|
||||||
|
data: {
|
||||||
|
id: guild_id,
|
||||||
|
},
|
||||||
|
user_id: req.user_id,
|
||||||
|
} as GuildDeleteEvent),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const user = await User.getPublicUser(req.user_id);
|
||||||
|
|
||||||
|
await emitEvent({
|
||||||
|
event: "GUILD_MEMBER_REMOVE",
|
||||||
|
data: {
|
||||||
|
guild_id: guild_id,
|
||||||
|
user: user,
|
||||||
|
},
|
||||||
|
guild_id: guild_id,
|
||||||
|
} as GuildMemberRemoveEvent);
|
||||||
|
|
||||||
|
return res.sendStatus(204);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user