diff options
author | Chris Chrome <christophercookman@gmail.com> | 2022-01-05 05:44:14 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-05 13:44:14 +0300 |
commit | 26bea41048302fe19a381969fa0ea8da8c133e83 (patch) | |
tree | a295ffda4dc8de989b7daf122615f9102141dd8c | |
parent | Update documents (diff) | |
download | server-26bea41048302fe19a381969fa0ea8da8c133e83.tar.xz |
Add Role Icons (#574)
* Role Icons Co-authored-by: Erkin Alp Güney <erkinalp9035@gmail.com> * Cache coherency rules Co-authored-by: MANIKILLER <manikillrorg@gmail.com> Co-authored-by: ImAaronFR <96433859+ImAaronFR@users.noreply.github.com>
-rw-r--r-- | api/src/routes/guilds/#guild_id/roles.ts | 11 | ||||
-rw-r--r-- | bundle/package.json | 2 | ||||
-rw-r--r-- | cdn/src/Server.ts | 4 | ||||
-rw-r--r-- | cdn/src/routes/role-icons.ts | 102 | ||||
-rw-r--r-- | util/src/entities/Guild.ts | 2 | ||||
-rw-r--r-- | util/src/entities/Role.ts | 6 |
6 files changed, 124 insertions, 3 deletions
diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index b1875598..b6894e3f 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -8,7 +8,8 @@ import { GuildRoleDeleteEvent, emitEvent, Config, - DiscordApiErrors + DiscordApiErrors, + handleFile } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; @@ -22,6 +23,8 @@ export interface RoleModifySchema { hoist?: boolean; // whether the role should be displayed separately in the sidebar mentionable?: boolean; // whether the role should be mentionable position?: number; + icon?: string; + unicode_emoji?: string; } export type RolePositionUpdateSchema = { @@ -58,7 +61,9 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }) guild_id: guild_id, managed: false, permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")), - tags: undefined + tags: undefined, + icon: null, + unicode_emoji: null }); await Promise.all([ @@ -105,6 +110,8 @@ router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ const { role_id, guild_id } = req.params; const body = req.body as RoleModifySchema; + if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); + const role = new Role({ ...body, id: role_id, diff --git a/bundle/package.json b/bundle/package.json index e0ae6156..456c89d7 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -107,4 +107,4 @@ "ws": "^7.4.2", "nanocolors": "^0.2.12" } -} \ No newline at end of file +} diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts index cac34a80..b8d71fa9 100644 --- a/cdn/src/Server.ts +++ b/cdn/src/Server.ts @@ -2,6 +2,7 @@ import { Server, ServerOptions } from "lambert-server"; import { Config, initDatabase, registerRoutes } from "@fosscord/util"; import path from "path"; import avatarsRoute from "./routes/avatars"; +import iconsRoute from "./routes/role-icons"; import bodyParser from "body-parser"; export interface CDNServerOptions extends ServerOptions {} @@ -40,6 +41,9 @@ export class CDNServer extends Server { this.app.use("/icons/", avatarsRoute); this.log("verbose", "[Server] Route /icons registered"); + this.app.use("/role-icons/", iconsRoute); + this.log("verbose", "[Server] Route /role-icons registered"); + this.app.use("/emojis/", avatarsRoute); this.log("verbose", "[Server] Route /emojis registered"); diff --git a/cdn/src/routes/role-icons.ts b/cdn/src/routes/role-icons.ts new file mode 100644 index 00000000..1ad1637d --- /dev/null +++ b/cdn/src/routes/role-icons.ts @@ -0,0 +1,102 @@ +import { Router, Response, Request } from "express"; +import { Config, Snowflake } from "@fosscord/util"; +import { storage } from "../util/Storage"; +import FileType from "file-type"; +import { HTTPError } from "lambert-server"; +import crypto from "crypto"; +import { multer } from "../util/multer"; + +//Role icons ---> avatars.ts modified + +// TODO: check premium and animated pfp are allowed in the config +// TODO: generate different sizes of icon +// TODO: generate different image types of icon +// TODO: delete old icons + +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(); + +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, mimetype, size, originalname, fieldname } = req.file; + const { role_id } = req.params; + + var hash = crypto + .createHash("md5") + .update(Snowflake.generate()) + .digest("hex"); + + const type = await FileType.fromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) + throw new HTTPError("Invalid file type"); + + const path = `role-icons/${role_id}/${hash}.png`; + const endpoint = + Config.get().cdn.endpointPublic || "http://localhost:3003"; + + await storage.set(path, buffer); + + 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) => { + var { role_id } = req.params; + //role_id = role_id.split(".")[0]; // remove .file extension + const path = `role-icons/${role_id}`; + + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await FileType.fromBuffer(file); + + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000, must-revalidate"); + + return res.send(file); +}); + +router.get("/:role_id/:hash", async (req: Request, res: Response) => { + var { role_id, hash } = req.params; + //hash = hash.split(".")[0]; // remove .file extension + const path = `role-icons/${role_id}/${hash}`; + + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await FileType.fromBuffer(file); + + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000, must-revalidate"); + + return res.send(file); +}); + +router.delete("/:role_id/:id", async (req: Request, res: Response) => { + 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}`; + + await storage.delete(path); + + return res.send({ success: true }); +}); + +export default router; diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts index 157f0921..f4c94a64 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts @@ -340,6 +340,8 @@ export class Guild extends BaseClass { name: "@everyone", permissions: String("2251804225"), position: 0, + icon: null, + unicode_emoji: null }).save(); if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }]; diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts index 9fca99a5..4b721b5b 100644 --- a/util/src/entities/Role.ts +++ b/util/src/entities/Role.ts @@ -36,6 +36,12 @@ export class Role extends BaseClass { @Column() position: number; + @Column({ nullable: true }) + icon: string; + + @Column({ nullable: true }) + unicode_emoji: string; + @Column({ type: "simple-json", nullable: true }) tags?: { bot_id?: string; |