mirror of
https://github.com/spacebarchat/server.git
synced 2026-06-07 02:21:45 +00:00
Prettier u9
This commit is contained in:
+5
-28
@@ -17,14 +17,7 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
ApiError,
|
||||
ConnectedAccount,
|
||||
ConnectionStore,
|
||||
DiscordApiErrors,
|
||||
FieldErrors,
|
||||
RefreshableConnection,
|
||||
} from "@spacebar/util";
|
||||
import { ApiError, ConnectedAccount, ConnectionStore, DiscordApiErrors, FieldErrors, RefreshableConnection } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -62,33 +55,17 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
external_id: connection_id,
|
||||
user_id: req.user_id,
|
||||
},
|
||||
select: [
|
||||
"external_id",
|
||||
"type",
|
||||
"name",
|
||||
"verified",
|
||||
"visibility",
|
||||
"show_activity",
|
||||
"revoked",
|
||||
"token_data",
|
||||
"friend_sync",
|
||||
"integrations",
|
||||
],
|
||||
select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
|
||||
});
|
||||
if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION;
|
||||
if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED;
|
||||
if (!connectedAccount.token_data)
|
||||
throw new ApiError("No token data", 0, 400);
|
||||
if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400);
|
||||
|
||||
let access_token = connectedAccount.token_data.access_token;
|
||||
const { expires_at, expires_in, fetched_at } = connectedAccount.token_data;
|
||||
|
||||
if (
|
||||
(expires_at && expires_at < Date.now()) ||
|
||||
(expires_in && fetched_at + expires_in * 1000 < Date.now())
|
||||
) {
|
||||
if (!(connection instanceof RefreshableConnection))
|
||||
throw new ApiError("Access token expired", 0, 400);
|
||||
if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) {
|
||||
if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400);
|
||||
const tokenData = await connection.refresh(connectedAccount);
|
||||
access_token = tokenData.access_token;
|
||||
}
|
||||
|
||||
@@ -19,62 +19,48 @@
|
||||
import { route } from "@spacebar/api";
|
||||
import { ConnectedAccount, DiscordApiErrors, emitEvent } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { ConnectionUpdateSchema } from "@spacebar/schemas"
|
||||
import { ConnectionUpdateSchema } from "@spacebar/schemas";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
// TODO: connection update schema
|
||||
router.patch(
|
||||
"/",
|
||||
route({ requestBody: "ConnectionUpdateSchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const { connection_name, connection_id } = req.params;
|
||||
const body = req.body as ConnectionUpdateSchema;
|
||||
router.patch("/", route({ requestBody: "ConnectionUpdateSchema" }), async (req: Request, res: Response) => {
|
||||
const { connection_name, connection_id } = req.params;
|
||||
const body = req.body as ConnectionUpdateSchema;
|
||||
|
||||
const connection = await ConnectedAccount.findOne({
|
||||
where: {
|
||||
user_id: req.user_id,
|
||||
external_id: connection_id,
|
||||
type: connection_name,
|
||||
},
|
||||
select: [
|
||||
"external_id",
|
||||
"type",
|
||||
"name",
|
||||
"verified",
|
||||
"visibility",
|
||||
"show_activity",
|
||||
"revoked",
|
||||
"friend_sync",
|
||||
"integrations",
|
||||
],
|
||||
});
|
||||
const connection = await ConnectedAccount.findOne({
|
||||
where: {
|
||||
user_id: req.user_id,
|
||||
external_id: connection_id,
|
||||
type: connection_name,
|
||||
},
|
||||
select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"],
|
||||
});
|
||||
|
||||
if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION;
|
||||
// TODO: do we need to do anything if the connection is revoked?
|
||||
if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION;
|
||||
// TODO: do we need to do anything if the connection is revoked?
|
||||
|
||||
if (typeof body.visibility === "boolean")
|
||||
//@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
|
||||
body.visibility = body.visibility ? 1 : 0;
|
||||
if (typeof body.show_activity === "boolean")
|
||||
//@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
|
||||
body.show_activity = body.show_activity ? 1 : 0;
|
||||
if (typeof body.metadata_visibility === "boolean")
|
||||
//@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
|
||||
body.metadata_visibility = body.metadata_visibility ? 1 : 0;
|
||||
if (typeof body.visibility === "boolean")
|
||||
//@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
|
||||
body.visibility = body.visibility ? 1 : 0;
|
||||
if (typeof body.show_activity === "boolean")
|
||||
//@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
|
||||
body.show_activity = body.show_activity ? 1 : 0;
|
||||
if (typeof body.metadata_visibility === "boolean")
|
||||
//@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
|
||||
body.metadata_visibility = body.metadata_visibility ? 1 : 0;
|
||||
|
||||
connection.assign(req.body);
|
||||
connection.assign(req.body);
|
||||
|
||||
await ConnectedAccount.update(
|
||||
{
|
||||
user_id: req.user_id,
|
||||
external_id: connection_id,
|
||||
type: connection_name,
|
||||
},
|
||||
connection,
|
||||
);
|
||||
res.json(connection.toJSON());
|
||||
},
|
||||
);
|
||||
await ConnectedAccount.update(
|
||||
{
|
||||
user_id: req.user_id,
|
||||
external_id: connection_id,
|
||||
type: connection_name,
|
||||
},
|
||||
connection,
|
||||
);
|
||||
res.json(connection.toJSON());
|
||||
});
|
||||
|
||||
router.delete("/", route({}), async (req: Request, res: Response) => {
|
||||
const { connection_name, connection_id } = req.params;
|
||||
|
||||
@@ -16,26 +16,15 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
DiscordApiErrors,
|
||||
EVENT,
|
||||
FieldErrors,
|
||||
PermissionResolvable,
|
||||
Permissions,
|
||||
RightResolvable,
|
||||
Rights,
|
||||
SpacebarApiErrors,
|
||||
getPermission,
|
||||
getRights,
|
||||
} from "@spacebar/util";
|
||||
import { DiscordApiErrors, EVENT, FieldErrors, PermissionResolvable, Permissions, RightResolvable, Rights, SpacebarApiErrors, getPermission, getRights } from "@spacebar/util";
|
||||
import { AnyValidateFunction } from "ajv/dist/core";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { ajv } from "@spacebar/schemas"
|
||||
import { ajv } from "@spacebar/schemas";
|
||||
|
||||
const ignoredRequestSchemas = [
|
||||
// skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix?
|
||||
"SettingsProtoUpdateJsonSchema"
|
||||
]
|
||||
"SettingsProtoUpdateJsonSchema",
|
||||
];
|
||||
|
||||
declare global {
|
||||
// TODO: fix this
|
||||
@@ -94,27 +83,18 @@ export function route(opts: RouteOptions) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (!validate)
|
||||
throw new Error(`Body schema ${opts.requestBody} not found`);
|
||||
if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`);
|
||||
}
|
||||
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (opts.permission) {
|
||||
req.permission = await getPermission(
|
||||
req.user_id,
|
||||
req.params.guild_id,
|
||||
req.params.channel_id,
|
||||
);
|
||||
req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id);
|
||||
|
||||
const requiredPerms = Array.isArray(opts.permission)
|
||||
? opts.permission
|
||||
: [opts.permission];
|
||||
const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission];
|
||||
requiredPerms.forEach((perm) => {
|
||||
// bitfield comparison: check if user lacks certain permission
|
||||
if (!req.permission!.has(new Permissions(perm))) {
|
||||
throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(
|
||||
perm as string,
|
||||
);
|
||||
throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -124,20 +104,14 @@ export function route(opts: RouteOptions) {
|
||||
req.rights = await getRights(req.user_id);
|
||||
|
||||
if (!req.rights || !req.rights.has(required)) {
|
||||
throw SpacebarApiErrors.MISSING_RIGHTS.withParams(
|
||||
opts.right as string,
|
||||
);
|
||||
throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) {
|
||||
const valid = validate(req.body);
|
||||
if (!valid) {
|
||||
const fields: Record<
|
||||
string,
|
||||
{ code?: string; message: string }
|
||||
> = {};
|
||||
const fields: Record<string, { code?: string; message: string }> = {};
|
||||
validate.errors?.forEach(
|
||||
(x) =>
|
||||
(fields[x.instancePath.slice(1)] = {
|
||||
@@ -145,11 +119,7 @@ export function route(opts: RouteOptions) {
|
||||
message: x.message || "",
|
||||
}),
|
||||
);
|
||||
if (process.env.LOG_VALIDATION_ERRORS)
|
||||
console.log(
|
||||
`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`,
|
||||
validate?.errors,
|
||||
);
|
||||
if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors);
|
||||
throw FieldErrors(fields, validate.errors!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Config }from "@spacebar/util";
|
||||
import { Config } from "@spacebar/util";
|
||||
import { Embed, EmbedImage, EmbedType } from "@spacebar/schemas";
|
||||
import * as cheerio from "cheerio";
|
||||
import crypto from "crypto";
|
||||
@@ -26,18 +26,13 @@ import probe from "probe-image-size";
|
||||
export const DEFAULT_FETCH_OPTIONS: RequestInit = {
|
||||
redirect: "follow",
|
||||
headers: {
|
||||
"user-agent":
|
||||
"Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)",
|
||||
"user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)",
|
||||
},
|
||||
// size: 1024 * 1024 * 5, // grabbed from config later
|
||||
method: "GET",
|
||||
};
|
||||
|
||||
const makeEmbedImage = (
|
||||
url: string | undefined,
|
||||
width: number | undefined,
|
||||
height: number | undefined,
|
||||
): Required<EmbedImage> | undefined => {
|
||||
const makeEmbedImage = (url: string | undefined, width: number | undefined, height: number | undefined): Required<EmbedImage> | undefined => {
|
||||
if (!url || !width || !height) return undefined;
|
||||
return {
|
||||
url,
|
||||
@@ -49,13 +44,8 @@ const makeEmbedImage = (
|
||||
|
||||
let hasWarnedAboutImagor = false;
|
||||
|
||||
export const getProxyUrl = (
|
||||
url: URL,
|
||||
width: number,
|
||||
height: number,
|
||||
): string => {
|
||||
const { resizeWidthMax, resizeHeightMax, imagorServerUrl } =
|
||||
Config.get().cdn;
|
||||
export const getProxyUrl = (url: URL, width: number, height: number): string => {
|
||||
const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn;
|
||||
const secret = Config.get().security.requestSignature;
|
||||
width = Math.min(width || 500, resizeWidthMax || width);
|
||||
height = Math.min(height || 500, resizeHeightMax || width);
|
||||
@@ -64,24 +54,14 @@ export const getProxyUrl = (
|
||||
if (imagorServerUrl) {
|
||||
const path = `${width}x${height}/${url.host}${url.pathname}`;
|
||||
|
||||
const hash = crypto
|
||||
.createHmac("sha1", secret)
|
||||
.update(path)
|
||||
.digest("base64")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_");
|
||||
const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
|
||||
|
||||
return `${imagorServerUrl}/${hash}/${path}`;
|
||||
}
|
||||
|
||||
if (!hasWarnedAboutImagor) {
|
||||
hasWarnedAboutImagor = true;
|
||||
console.log(
|
||||
"[Embeds]",
|
||||
yellow(
|
||||
"Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/",
|
||||
),
|
||||
);
|
||||
console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/"));
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
@@ -161,11 +141,7 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
|
||||
const response = await doFetch(url);
|
||||
if (!response) return null;
|
||||
const metas = getMetaDescriptions(await response.text());
|
||||
image = makeEmbedImage(
|
||||
metas.image || metas.image_fallback,
|
||||
metas.width,
|
||||
metas.height,
|
||||
);
|
||||
image = makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height);
|
||||
}
|
||||
|
||||
if (!image) return null;
|
||||
@@ -186,8 +162,7 @@ export const EmbedHandlers: {
|
||||
...DEFAULT_FETCH_OPTIONS,
|
||||
method: "HEAD",
|
||||
});
|
||||
if (type.headers.get("content-type")?.indexOf("image") !== -1)
|
||||
return await genericImageHandler(url);
|
||||
if (type.headers.get("content-type")?.indexOf("image") !== -1) return await genericImageHandler(url);
|
||||
|
||||
const response = await doFetch(url);
|
||||
if (!response) return null;
|
||||
@@ -300,9 +275,7 @@ export const EmbedHandlers: {
|
||||
const text = json.data.text;
|
||||
const created_at = new Date(json.data.created_at);
|
||||
const metrics = json.data.public_metrics;
|
||||
const media = json.includes.media?.filter(
|
||||
(x: { type: string }) => x.type == "photo",
|
||||
);
|
||||
const media = json.includes.media?.filter((x: { type: string }) => x.type == "photo");
|
||||
|
||||
const embed: Embed = {
|
||||
type: EmbedType.rich,
|
||||
@@ -311,11 +284,7 @@ export const EmbedHandlers: {
|
||||
author: {
|
||||
url: `https://twitter.com/${author.username}`,
|
||||
name: `${author.name} (@${author.username})`,
|
||||
proxy_icon_url: getProxyUrl(
|
||||
new URL(author.profile_image_url),
|
||||
400,
|
||||
400,
|
||||
),
|
||||
proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400),
|
||||
icon_url: author.profile_image_url,
|
||||
},
|
||||
timestamp: created_at,
|
||||
@@ -334,15 +303,8 @@ export const EmbedHandlers: {
|
||||
color: 1942002,
|
||||
footer: {
|
||||
text: "Twitter",
|
||||
proxy_icon_url: getProxyUrl(
|
||||
new URL(
|
||||
"https://abs.twimg.com/icons/apple-touch-icon-192x192.png",
|
||||
),
|
||||
192,
|
||||
192,
|
||||
),
|
||||
icon_url:
|
||||
"https://abs.twimg.com/icons/apple-touch-icon-192x192.png",
|
||||
proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192),
|
||||
icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png",
|
||||
},
|
||||
// Discord doesn't send this?
|
||||
// provider: {
|
||||
@@ -356,11 +318,7 @@ export const EmbedHandlers: {
|
||||
width: media[0].width,
|
||||
height: media[0].height,
|
||||
url: media[0].url,
|
||||
proxy_url: getProxyUrl(
|
||||
new URL(media[0].url),
|
||||
media[0].width,
|
||||
media[0].height,
|
||||
),
|
||||
proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height),
|
||||
};
|
||||
media.shift();
|
||||
}
|
||||
@@ -413,11 +371,7 @@ export const EmbedHandlers: {
|
||||
type: EmbedType.image,
|
||||
title: metas.title,
|
||||
description: metas.description,
|
||||
image: makeEmbedImage(
|
||||
metas.image || metas.image_fallback,
|
||||
metas.width,
|
||||
metas.height,
|
||||
),
|
||||
image: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height),
|
||||
provider: {
|
||||
url: "https://pixiv.net",
|
||||
name: "Pixiv",
|
||||
@@ -429,17 +383,9 @@ export const EmbedHandlers: {
|
||||
const response = await doFetch(url);
|
||||
if (!response) return null;
|
||||
const metas = getMetaDescriptions(await response.text());
|
||||
const numReviews = metas.$("#review_summary_num_reviews").val() as
|
||||
| string
|
||||
| undefined;
|
||||
const price = metas
|
||||
.$(".game_purchase_price.price")
|
||||
.data("price-final") as number | undefined;
|
||||
const releaseDate = metas
|
||||
.$(".release_date")
|
||||
.find("div.date")
|
||||
.text()
|
||||
.trim();
|
||||
const numReviews = metas.$("#review_summary_num_reviews").val() as string | undefined;
|
||||
const price = metas.$(".game_purchase_price.price").data("price-final") as number | undefined;
|
||||
const releaseDate = metas.$(".release_date").find("div.date").text().trim();
|
||||
const isReleased = new Date(releaseDate) < new Date();
|
||||
|
||||
const fields: Embed["fields"] = [];
|
||||
@@ -477,9 +423,7 @@ export const EmbedHandlers: {
|
||||
width: 460,
|
||||
height: 215,
|
||||
url: metas.image,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(new URL(metas.image), 460, 215)
|
||||
: undefined,
|
||||
proxy_url: metas.image ? getProxyUrl(new URL(metas.image), 460, 215) : undefined,
|
||||
},
|
||||
provider: {
|
||||
url: "https://store.steampowered.com",
|
||||
@@ -510,19 +454,11 @@ export const EmbedHandlers: {
|
||||
const metas = getMetaDescriptions(await response.text());
|
||||
|
||||
return {
|
||||
video: makeEmbedImage(
|
||||
metas.youtube_embed,
|
||||
metas.width,
|
||||
metas.height,
|
||||
),
|
||||
video: makeEmbedImage(metas.youtube_embed, metas.width, metas.height),
|
||||
url: url.href,
|
||||
type: metas.youtube_embed ? EmbedType.video : EmbedType.link,
|
||||
title: metas.title,
|
||||
thumbnail: makeEmbedImage(
|
||||
metas.image || metas.image_fallback,
|
||||
metas.width,
|
||||
metas.height,
|
||||
),
|
||||
thumbnail: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height),
|
||||
provider: {
|
||||
url: "https://www.youtube.com",
|
||||
name: "YouTube",
|
||||
|
||||
@@ -35,8 +35,7 @@ const reSYMBOLS = /[A-Za-z0-9]/g;
|
||||
* Returns: 0 > pw > 1
|
||||
*/
|
||||
export function checkPassword(password: string): number {
|
||||
const { minLength, minNumbers, minUpperCase, minSymbols } =
|
||||
Config.get().register.password;
|
||||
const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password;
|
||||
let strength = 0;
|
||||
|
||||
// checks for total password len
|
||||
@@ -60,10 +59,7 @@ export function checkPassword(password: string): number {
|
||||
}
|
||||
|
||||
// checks if password only consists of numbers or only consists of chars
|
||||
if (
|
||||
password.length == password.match(reNUMBER)?.length ||
|
||||
password.length === password.match(reUPPERCASELETTER)?.length
|
||||
) {
|
||||
if (password.length == password.match(reNUMBER)?.length || password.length === password.match(reUPPERCASELETTER)?.length) {
|
||||
strength = 0;
|
||||
}
|
||||
|
||||
@@ -76,8 +72,6 @@ export function checkPassword(password: string): number {
|
||||
const entropies = Object.values(entropyMap);
|
||||
|
||||
entropies.map((x) => x / entropyMap.length);
|
||||
strength +=
|
||||
entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) /
|
||||
Math.log2(password.length);
|
||||
strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length);
|
||||
return strength;
|
||||
}
|
||||
|
||||
+21
-37
@@ -30,50 +30,35 @@ import { multer } from "../util/multer";
|
||||
// TODO: delete old icons
|
||||
|
||||
const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"];
|
||||
const STATIC_MIME_TYPES = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/svg",
|
||||
];
|
||||
const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"];
|
||||
const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES];
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.post(
|
||||
"/:user_id",
|
||||
multer.single("file"),
|
||||
async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature)
|
||||
throw new HTTPError("Invalid request signature");
|
||||
if (!req.file) throw new HTTPError("Missing file");
|
||||
const { buffer, size } = req.file;
|
||||
const { user_id } = req.params;
|
||||
router.post("/:user_id", multer.single("file"), async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
|
||||
if (!req.file) throw new HTTPError("Missing file");
|
||||
const { buffer, size } = req.file;
|
||||
const { user_id } = req.params;
|
||||
|
||||
let hash = crypto
|
||||
.createHash("md5")
|
||||
.update(Snowflake.generate())
|
||||
.digest("hex");
|
||||
let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex");
|
||||
|
||||
const type = await fileTypeFromBuffer(buffer);
|
||||
if (!type || !ALLOWED_MIME_TYPES.includes(type.mime))
|
||||
throw new HTTPError("Invalid file type");
|
||||
if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash
|
||||
const type = await fileTypeFromBuffer(buffer);
|
||||
if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type");
|
||||
if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash
|
||||
|
||||
const path = `avatars/${user_id}/${hash}`;
|
||||
const endpoint = Config.get().cdn.endpointPublic;
|
||||
const path = `avatars/${user_id}/${hash}`;
|
||||
const endpoint = Config.get().cdn.endpointPublic;
|
||||
|
||||
await storage.set(path, buffer);
|
||||
await storage.set(path, buffer);
|
||||
|
||||
return res.json({
|
||||
id: hash,
|
||||
content_type: type.mime,
|
||||
size,
|
||||
url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`,
|
||||
});
|
||||
},
|
||||
);
|
||||
return res.json({
|
||||
id: hash,
|
||||
content_type: type.mime,
|
||||
size,
|
||||
url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`,
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/:user_id", async (req: Request, res: Response) => {
|
||||
let { user_id } = req.params;
|
||||
@@ -109,8 +94,7 @@ export const getAvatar = async (req: Request, res: Response) => {
|
||||
router.get("/:user_id/:hash", getAvatar);
|
||||
|
||||
router.delete("/:user_id/:id", async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature)
|
||||
throw new HTTPError("Invalid request signature");
|
||||
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
|
||||
const { user_id, id } = req.params;
|
||||
const path = `avatars/${user_id}/${id}`;
|
||||
|
||||
|
||||
+2
-18
@@ -63,15 +63,7 @@ router.get("/avatars/:id", async (req: Request, res: Response) => {
|
||||
id = id.split(".")[0]; // remove .file extension
|
||||
const hash = defaultAvatarHashMap.get(id);
|
||||
if (!hash) throw new HTTPError("not found", 404);
|
||||
const path = join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"assets",
|
||||
"public",
|
||||
`${hash}.png`,
|
||||
);
|
||||
const path = join(__dirname, "..", "..", "..", "assets", "public", `${hash}.png`);
|
||||
|
||||
const file = await getFile(path);
|
||||
if (!file) throw new HTTPError("not found", 404);
|
||||
@@ -88,15 +80,7 @@ router.get("/group-avatars/:id", async (req: Request, res: Response) => {
|
||||
id = id.split(".")[0]; // remove .file extension
|
||||
const hash = defaultGroupDMAvatarHashMap.get(id);
|
||||
if (!hash) throw new HTTPError("not found", 404);
|
||||
const path = join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"assets",
|
||||
"public",
|
||||
`${hash}.png`,
|
||||
);
|
||||
const path = join(__dirname, "..", "..", "..", "assets", "public", `${hash}.png`);
|
||||
|
||||
const file = await getFile(path);
|
||||
if (!file) throw new HTTPError("not found", 404);
|
||||
|
||||
@@ -30,32 +30,21 @@ import { fileTypeFromBuffer } from "file-type";
|
||||
// TODO: delete old icons
|
||||
|
||||
const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"];
|
||||
const STATIC_MIME_TYPES = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/svg",
|
||||
];
|
||||
const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"];
|
||||
const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES];
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.post("/", multer.single("file"), async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature)
|
||||
throw new HTTPError("Invalid request signature");
|
||||
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
|
||||
if (!req.file) throw new HTTPError("Missing file");
|
||||
const { buffer, size } = req.file;
|
||||
const { guild_id, user_id } = req.params;
|
||||
|
||||
let hash = crypto
|
||||
.createHash("md5")
|
||||
.update(Snowflake.generate())
|
||||
.digest("hex");
|
||||
let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex");
|
||||
|
||||
const type = await fileTypeFromBuffer(buffer);
|
||||
if (!type || !ALLOWED_MIME_TYPES.includes(type.mime))
|
||||
throw new HTTPError("Invalid file type");
|
||||
if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type");
|
||||
if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash
|
||||
|
||||
const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`;
|
||||
@@ -104,8 +93,7 @@ router.get("/:hash", async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
router.delete("/:id", async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature)
|
||||
throw new HTTPError("Invalid request signature");
|
||||
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
|
||||
const { guild_id, user_id, id } = req.params;
|
||||
const path = `guilds/${guild_id}/users/${user_id}/avatars/${id}`;
|
||||
|
||||
|
||||
@@ -30,49 +30,34 @@ import { multer } from "../util/multer";
|
||||
// TODO: generate different sizes of icon
|
||||
// TODO: generate different image types of icon
|
||||
|
||||
const STATIC_MIME_TYPES = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/svg",
|
||||
];
|
||||
const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"];
|
||||
const ALLOWED_MIME_TYPES = [...STATIC_MIME_TYPES];
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.post(
|
||||
"/:role_id",
|
||||
multer.single("file"),
|
||||
async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature)
|
||||
throw new HTTPError("Invalid request signature");
|
||||
if (!req.file) throw new HTTPError("Missing file");
|
||||
const { buffer, size } = req.file;
|
||||
const { role_id } = req.params;
|
||||
router.post("/:role_id", multer.single("file"), async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
|
||||
if (!req.file) throw new HTTPError("Missing file");
|
||||
const { buffer, size } = req.file;
|
||||
const { role_id } = req.params;
|
||||
|
||||
const hash = crypto
|
||||
.createHash("md5")
|
||||
.update(Snowflake.generate())
|
||||
.digest("hex");
|
||||
const hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex");
|
||||
|
||||
const type = await fileTypeFromBuffer(buffer);
|
||||
if (!type || !ALLOWED_MIME_TYPES.includes(type.mime))
|
||||
throw new HTTPError("Invalid file type");
|
||||
const type = await fileTypeFromBuffer(buffer);
|
||||
if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type");
|
||||
|
||||
const path = `role-icons/${role_id}/${hash}.png`;
|
||||
const endpoint = Config.get().cdn.endpointPublic;
|
||||
const path = `role-icons/${role_id}/${hash}.png`;
|
||||
const endpoint = Config.get().cdn.endpointPublic;
|
||||
|
||||
await storage.set(path, buffer);
|
||||
await storage.set(path, buffer);
|
||||
|
||||
return res.json({
|
||||
id: hash,
|
||||
content_type: type.mime,
|
||||
size,
|
||||
url: `${endpoint}${req.baseUrl}/${role_id}/${hash}`,
|
||||
});
|
||||
},
|
||||
);
|
||||
return res.json({
|
||||
id: hash,
|
||||
content_type: type.mime,
|
||||
size,
|
||||
url: `${endpoint}${req.baseUrl}/${role_id}/${hash}`,
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/:role_id", async (req: Request, res: Response) => {
|
||||
const { role_id } = req.params;
|
||||
@@ -96,19 +81,10 @@ router.get("/:role_id/:hash", async (req: Request, res: Response) => {
|
||||
const role_icon_hash = hash.split(".")[0];
|
||||
let file: Buffer | null = null;
|
||||
|
||||
const extensions_to_try = [
|
||||
requested_extension,
|
||||
"png",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"webp",
|
||||
"svg",
|
||||
];
|
||||
const extensions_to_try = [requested_extension, "png", "jpg", "jpeg", "webp", "svg"];
|
||||
|
||||
for (let i = 0; i < extensions_to_try.length; i++) {
|
||||
file = await storage.get(
|
||||
`role-icons/${role_id}/${role_icon_hash}.${extensions_to_try[i]}`,
|
||||
);
|
||||
file = await storage.get(`role-icons/${role_id}/${role_icon_hash}.${extensions_to_try[i]}`);
|
||||
if (file) break;
|
||||
}
|
||||
|
||||
@@ -122,8 +98,7 @@ router.get("/:role_id/:hash", async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
router.delete("/:role_id/:id", async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature)
|
||||
throw new HTTPError("Invalid request signature");
|
||||
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
|
||||
const { role_id, id } = req.params;
|
||||
const path = `role-icons/${role_id}/${id}`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user