diff options
author | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2023-01-05 17:12:21 +1100 |
---|---|---|
committer | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2023-01-05 17:16:55 +1100 |
commit | df449169bde6c0576757700f68f2d406139cc846 (patch) | |
tree | f8bcbaf30ed3ed007d392a51614a134e543e3c17 /src/api | |
parent | channel flags whoops (diff) | |
download | server-df449169bde6c0576757700f68f2d406139cc846.tar.xz |
Prettier
Diffstat (limited to 'src/api')
31 files changed, 765 insertions, 487 deletions
diff --git a/src/api/Server.ts b/src/api/Server.ts index b8d1f9f6..fff94936 100644 --- a/src/api/Server.ts +++ b/src/api/Server.ts @@ -14,7 +14,7 @@ import { initInstance } from "./util/handlers/Instance"; import { registerRoutes } from "@fosscord/util"; import { red } from "picocolors"; -export interface FosscordServerOptions extends ServerOptions { } +export interface FosscordServerOptions extends ServerOptions {} declare global { namespace Express { @@ -76,15 +76,12 @@ export class FosscordServer extends Server { // 404 is not an error in express, so this should not be an error middleware // this is a fine place to put the 404 handler because its after we register the routes // and since its not an error middleware, our error handler below still works. - api.use( - "*", - (req: Request, res: Response, next: NextFunction) => { - res.status(404).json({ - message: "404 endpoint not found", - code: 0, - }); - }, - ); + api.use("*", (req: Request, res: Response, next: NextFunction) => { + res.status(404).json({ + message: "404 endpoint not found", + code: 0, + }); + }); this.app = app; diff --git a/src/api/routes/applications/#id/bot/index.ts b/src/api/routes/applications/#id/bot/index.ts index c21e19ca..c4cfccd8 100644 --- a/src/api/routes/applications/#id/bot/index.ts +++ b/src/api/routes/applications/#id/bot/index.ts @@ -1,13 +1,23 @@ import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; -import { Application, generateToken, User, BotModifySchema, handleFile, DiscordApiErrors } from "@fosscord/util"; +import { + Application, + generateToken, + User, + BotModifySchema, + handleFile, + DiscordApiErrors, +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { verifyToken } from "node-2fa"; const router: Router = Router(); router.post("/", route({}), async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["owner"] }); + const app = await Application.findOneOrFail({ + where: { id: req.params.id }, + relations: ["owner"], + }); if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; @@ -31,7 +41,7 @@ router.post("/", route({}), async (req: Request, res: Response) => { await app.save(); res.send({ - token: await generateToken(user.id) + token: await generateToken(user.id), }).status(204); }); @@ -42,7 +52,10 @@ router.post("/reset", route({}), async (req: Request, res: Response) => { if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) + 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); bot.data = { hash: undefined, valid_tokens_since: new Date() }; @@ -54,30 +67,36 @@ router.post("/reset", route({}), async (req: Request, res: Response) => { res.json({ token }).status(200); }); -router.patch("/", route({ body: "BotModifySchema" }), async (req: Request, res: Response) => { - const body = req.body as BotModifySchema; - if (!body.avatar?.trim()) delete body.avatar; +router.patch( + "/", + route({ body: "BotModifySchema" }), + async (req: Request, res: Response) => { + const body = req.body as BotModifySchema; + if (!body.avatar?.trim()) delete body.avatar; - const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["bot", "owner"] }); + const app = await Application.findOneOrFail({ + where: { id: req.params.id }, + relations: ["bot", "owner"], + }); - if (!app.bot) - throw DiscordApiErrors.BOT_ONLY_ENDPOINT; + if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; - if (app.owner.id != req.user_id) - throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (app.owner.id != req.user_id) + throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (body.avatar) - body.avatar = await handleFile( - `/avatars/${app.id}`, - body.avatar as string, - ); + if (body.avatar) + body.avatar = await handleFile( + `/avatars/${app.id}`, + body.avatar as string, + ); - app.bot.assign(body); + app.bot.assign(body); - app.bot.save(); + app.bot.save(); - await app.save(); - res.json(app).status(200); -}); + await app.save(); + res.json(app).status(200); + }, +); -export default router; \ No newline at end of file +export default router; diff --git a/src/api/routes/applications/#id/index.ts b/src/api/routes/applications/#id/index.ts index 79df256a..11cd5a56 100644 --- a/src/api/routes/applications/#id/index.ts +++ b/src/api/routes/applications/#id/index.ts @@ -1,57 +1,81 @@ import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; -import { Application, OrmUtils, DiscordApiErrors, ApplicationModifySchema, User } from "@fosscord/util"; +import { + Application, + OrmUtils, + DiscordApiErrors, + ApplicationModifySchema, + User, +} from "@fosscord/util"; import { verifyToken } from "node-2fa"; import { HTTPError } from "lambert-server"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["owner", "bot"] }); + 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); }); -router.patch("/", route({ body: "ApplicationModifySchema" }), async (req: Request, res: Response) => { - const body = req.body as ApplicationModifySchema; +router.patch( + "/", + route({ body: "ApplicationModifySchema" }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationModifySchema; - const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["owner", "bot"] }); + 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; + if (app.owner.id != req.user_id) + throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) - throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if ( + app.owner.totp_secret && + (!req.body.code || + verifyToken(app.owner.totp_secret, req.body.code)) + ) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - if (app.bot) { - app.bot.assign({ bio: body.description }); - await app.bot.save(); - } + if (app.bot) { + app.bot.assign({ bio: body.description }); + await app.bot.save(); + } - app.assign(body); + app.assign(body); - await app.save(); + await app.save(); - return res.json(app); -}); + return res.json(app); + }, +); router.post("/delete", route({}), async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["bot", "owner"] }); + 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 (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) + if ( + app.owner.totp_secret && + (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code)) + ) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - if (app.bot) - await User.delete({ id: app.bot.id }); + if (app.bot) await User.delete({ id: app.bot.id }); await Application.delete({ id: app.id }); res.send().status(200); }); - -export default router; \ No newline at end of file +export default router; diff --git a/src/api/routes/applications/#id/skus.ts b/src/api/routes/applications/#id/skus.ts index 5b667f36..2383e6f7 100644 --- a/src/api/routes/applications/#id/skus.ts +++ b/src/api/routes/applications/#id/skus.ts @@ -8,4 +8,4 @@ router.get("/", route({}), async (req: Request, res: Response) => { res.json([]).status(200); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts index 94cfa5c5..a6b35bfa 100644 --- a/src/api/routes/applications/index.ts +++ b/src/api/routes/applications/index.ts @@ -1,30 +1,42 @@ import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; -import { Application, ApplicationCreateSchema, trimSpecial, User } from "@fosscord/util"; +import { + Application, + ApplicationCreateSchema, + trimSpecial, + User, +} from "@fosscord/util"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - let results = await Application.find({ where: { owner: { id: req.user_id } }, relations: ["owner", "bot"] }); + let results = await Application.find({ + where: { owner: { id: req.user_id } }, + relations: ["owner", "bot"], + }); res.json(results).status(200); }); -router.post("/", route({ body: "ApplicationCreateSchema" }), async (req: Request, res: Response) => { - const body = req.body as ApplicationCreateSchema; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); +router.post( + "/", + route({ body: "ApplicationCreateSchema" }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationCreateSchema; + const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const app = Application.create({ - name: trimSpecial(body.name), - description: "", - bot_public: true, - owner: user, - verify_key: "IMPLEMENTME", - flags: 0, - }); + const app = Application.create({ + name: trimSpecial(body.name), + description: "", + bot_public: true, + owner: user, + verify_key: "IMPLEMENTME", + flags: 0, + }); - await app.save(); + await app.save(); - res.json(app); -}); + res.json(app); + }, +); -export default router; \ No newline at end of file +export default router; diff --git a/src/api/routes/auth/generate-registration-tokens.ts b/src/api/routes/auth/generate-registration-tokens.ts index e328fe5e..0d4cf067 100644 --- a/src/api/routes/auth/generate-registration-tokens.ts +++ b/src/api/routes/auth/generate-registration-tokens.ts @@ -5,24 +5,37 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); export default router; -router.get("/", route({ right: "OPERATOR" }), async (req: Request, res: Response) => { - const count = req.query.count ? parseInt(req.query.count as string) : 1; - const length = req.query.length ? parseInt(req.query.length as string) : 255; +router.get( + "/", + route({ right: "OPERATOR" }), + async (req: Request, res: Response) => { + const count = req.query.count ? parseInt(req.query.count as string) : 1; + const length = req.query.length + ? parseInt(req.query.length as string) + : 255; - let tokens: ValidRegistrationToken[] = []; + let tokens: ValidRegistrationToken[] = []; - for (let i = 0; i < count; i++) { - const token = ValidRegistrationToken.create({ - token: random(length), - expires_at: Date.now() + Config.get().security.defaultRegistrationTokenExpiration - }); - tokens.push(token); - } + for (let i = 0; i < count; i++) { + const token = ValidRegistrationToken.create({ + token: random(length), + expires_at: + Date.now() + + Config.get().security.defaultRegistrationTokenExpiration, + }); + tokens.push(token); + } - // Why are these options used, exactly? - await ValidRegistrationToken.save(tokens, { chunk: 1000, reload: false, transaction: false }); + // Why are these options used, exactly? + await ValidRegistrationToken.save(tokens, { + chunk: 1000, + reload: false, + transaction: false, + }); - if (req.query.plain) return res.send(tokens.map(x => x.token).join("\n")); + if (req.query.plain) + return res.send(tokens.map((x) => x.token).join("\n")); - return res.json({ tokens: tokens.map(x => x.token) }); -}); \ No newline at end of file + return res.json({ tokens: tokens.map((x) => x.token) }); + }, +); diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts index c8c515e7..3d968114 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts @@ -33,16 +33,22 @@ router.post( // Reg tokens // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc ) let regTokenUsed = false; - if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { // eg theyre on https://staging.fosscord.com/register?token=whatever + if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { + // eg theyre on https://staging.fosscord.com/register?token=whatever const token = req.get("Referrer")!.split("token=")[1].split("&")[0]; if (token) { - const regToken = await ValidRegistrationToken.findOne({ where: { token, expires_at: MoreThan(new Date()), } }); + const regToken = await ValidRegistrationToken.findOne({ + where: { token, expires_at: MoreThan(new Date()) }, + }); await ValidRegistrationToken.delete({ token }); regTokenUsed = true; - console.log(`[REGISTER] Registration token ${token} used for registration!`); - } - else { - console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`); + console.log( + `[REGISTER] Registration token ${token} used for registration!`, + ); + } else { + console.log( + `[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`, + ); } } @@ -78,7 +84,11 @@ router.post( }); } - if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) { + if ( + !regTokenUsed && + register.requireCaptcha && + security.captcha.enabled + ) { const { sitekey, service } = security.captcha; if (!body.captcha_key) { return res?.status(400).json({ @@ -220,14 +230,26 @@ router.post( if ( !regTokenUsed && limits.absoluteRate.register.enabled && - (await User.count({ where: { created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)) } })) - >= limits.absoluteRate.register.limit + (await User.count({ + where: { + created_at: MoreThan( + new Date( + Date.now() - limits.absoluteRate.register.window, + ), + ), + }, + })) >= limits.absoluteRate.register.limit ) { console.log( - `Global register ratelimit exceeded for ${getIpAdress(req)}, ${req.body.username}, ${req.body.invite || "No invite given"}` + `Global register ratelimit exceeded for ${getIpAdress(req)}, ${ + req.body.username + }, ${req.body.invite || "No invite given"}`, ); throw FieldErrors({ - email: { code: "TOO_MANY_REGISTRATIONS", message: req.t("auth:register.TOO_MANY_REGISTRATIONS") } + email: { + code: "TOO_MANY_REGISTRATIONS", + message: req.t("auth:register.TOO_MANY_REGISTRATIONS"), + }, }); } diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts index cd9da184..d57d9a1b 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts @@ -179,7 +179,7 @@ router.put( channel.save(), ]); - postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error return res.json(message); }, diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts index 523b0cf8..2968437d 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts @@ -21,7 +21,12 @@ import { Rights, } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { handleMessage, postHandleMessage, route, getIpAdress } from "@fosscord/api"; +import { + handleMessage, + postHandleMessage, + route, + getIpAdress, +} from "@fosscord/api"; import multer from "multer"; import { yellow } from "picocolors"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; @@ -80,7 +85,7 @@ router.get("/", async (req: Request, res: Response) => { permissions.hasThrow("VIEW_CHANNEL"); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - var query: FindManyOptions<Message> & { where: { id?: any; }; } = { + var query: FindManyOptions<Message> & { where: { id?: any } } = { order: { timestamp: "DESC" }, take: limit, where: { channel_id }, @@ -138,8 +143,9 @@ router.get("/", async (req: Request, res: Response) => { 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 - }`; + y.proxy_url = `${endpoint == null ? "" : endpoint}${ + new URL(uri).pathname + }`; }); /** @@ -211,8 +217,8 @@ router.post( where: { nonce: body.nonce, channel_id: channel.id, - author_id: req.user_id - } + author_id: req.user_id, + }, }); if (existing) { return res.json(existing); @@ -225,13 +231,21 @@ router.post( const count = await Message.count({ where: { channel_id, - timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)) - } + timestamp: MoreThan( + new Date( + Date.now() - + limits.absoluteRate.sendMessage.window, + ), + ), + }, }); if (count >= limits.absoluteRate.sendMessage.limit) throw FieldErrors({ - channel_id: { code: "TOO_MANY_MESSAGES", message: req.t("common:toomany.MESSAGE") } + channel_id: { + code: "TOO_MANY_MESSAGES", + message: req.t("common:toomany.MESSAGE"), + }, }); } } @@ -247,7 +261,7 @@ router.post( Attachment.create({ ...file, proxy_url: file.url }), ); } catch (error) { - return res.status(400).json({ message: error!.toString() }) + return res.status(400).json({ message: error!.toString() }); } } @@ -296,19 +310,18 @@ router.post( if (!message.member) { message.member = await Member.findOneOrFail({ where: { id: req.user_id, guild_id: message.guild_id }, - relations: ["roles"] + relations: ["roles"], }); } //@ts-ignore - message.member.roles = - message.member.roles. - filter(x => x.id != x.guild_id) - .map(x => x.id); + message.member.roles = message.member.roles + .filter((x) => x.id != x.guild_id) + .map((x) => x.id); } let read_state = await ReadState.findOne({ - where: { user_id: req.user_id, channel_id } + where: { user_id: req.user_id, channel_id }, }); if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); @@ -324,14 +337,14 @@ router.post( } as MessageCreateEvent), message.guild_id ? Member.update( - { id: req.user_id, guild_id: message.guild_id }, - { last_message_id: message.id }, - ) + { id: req.user_id, guild_id: message.guild_id }, + { last_message_id: message.id }, + ) : null, channel.save(), ]); - postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error return res.json(message); }, diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts index 0a816223..b08cd0c8 100644 --- a/src/api/routes/channels/#channel_id/permissions.ts +++ b/src/api/routes/channels/#channel_id/permissions.ts @@ -5,7 +5,7 @@ import { emitEvent, Member, Role, - ChannelPermissionOverwriteSchema + ChannelPermissionOverwriteSchema, } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts index 13f421f1..f303ef80 100644 --- a/src/api/routes/channels/#channel_id/webhooks.ts +++ b/src/api/routes/channels/#channel_id/webhooks.ts @@ -1,6 +1,15 @@ import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; -import { Channel, Config, handleFile, trimSpecial, User, Webhook, WebhookCreateSchema, WebhookType } from "@fosscord/util"; +import { + Channel, + Config, + handleFile, + trimSpecial, + User, + Webhook, + WebhookCreateSchema, + WebhookType, +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; import { DiscordApiErrors } from "@fosscord/util"; @@ -38,8 +47,7 @@ router.post( if (name === "clyde") throw new HTTPError("Invalid name", 400); if (name === "Fosscord Ghost") throw new HTTPError("Invalid name", 400); - if (avatar) - avatar = await handleFile(`/avatars/${channel_id}`, avatar); + if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar); const hook = Webhook.create({ type: WebhookType.Incoming, diff --git a/src/api/routes/download/index.ts b/src/api/routes/download/index.ts index 371c0fd7..1c135f25 100644 --- a/src/api/routes/download/index.ts +++ b/src/api/routes/download/index.ts @@ -12,19 +12,20 @@ const router = Router(); router.get("/", route({}), 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"), - } - }); + 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" } + order: { pub_date: "DESC" }, }); res.redirect(release.url); diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts index 302bbb9c..8bf1e508 100644 --- a/src/api/routes/guild-recommendations.ts +++ b/src/api/routes/guild-recommendations.ts @@ -9,7 +9,7 @@ const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { limit, personalization_disabled } = req.query; var showAllGuilds = Config.get().guild.discovery.showAllGuilds; - + const genLoadId = (size: Number) => [...Array(size)] .map(() => Math.floor(Math.random() * 16).toString(16)) diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts index 1e976293..79c20678 100644 --- a/src/api/routes/guilds/#guild_id/index.ts +++ b/src/api/routes/guilds/#guild_id/index.ts @@ -69,15 +69,21 @@ router.patch( body.splash, ); - if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash) + if ( + body.discovery_splash && + body.discovery_splash !== guild.discovery_splash + ) body.discovery_splash = await handleFile( `/discovery-splashes/${guild_id}`, body.discovery_splash, ); if (body.features) { - const diff = guild.features.filter(x => !body.features?.includes(x)) - .concat(body.features.filter(x => !guild.features.includes(x))); + const diff = guild.features + .filter((x) => !body.features?.includes(x)) + .concat( + body.features.filter((x) => !guild.features.includes(x)), + ); // TODO move these const MUTABLE_FEATURES = [ @@ -89,7 +95,9 @@ router.patch( for (var feature of diff) { if (MUTABLE_FEATURES.includes(feature)) continue; - throw FosscordApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature); + throw FosscordApiErrors.FEATURE_IS_IMMUTABLE.withParams( + feature, + ); } // for some reason, they don't update in the assign. diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts index f1d343d9..0fcdd57c 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts @@ -27,42 +27,56 @@ router.get("/", route({}), async (req: Request, res: Response) => { return res.json(member); }); -router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => { - let { guild_id, member_id } = req.params; - if (member_id === "@me") member_id = req.user_id; - const body = req.body as MemberChangeSchema; - - let member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] }); - const permission = await getPermission(req.user_id, guild_id); - const everyone = await Role.findOneOrFail({ where: { guild_id: guild_id, name: "@everyone", position: 0 } }); - - if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string); - - member.assign(body); - - if ('roles' in body) { - permission.hasThrow("MANAGE_ROLES"); - - body.roles = body.roles || []; - body.roles.filter(x => !!x); - - if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id); - member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist - } - - await member.save(); - - member.roles = member.roles.filter((x) => x.id !== everyone.id); - - // do not use promise.all as we have to first write to db before emitting the event to catch errors - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: { ...member, roles: member.roles.map((x) => x.id) } - } as GuildMemberUpdateEvent); - - res.json(member); -}); +router.patch( + "/", + route({ body: "MemberChangeSchema" }), + async (req: Request, res: Response) => { + let { guild_id, member_id } = req.params; + if (member_id === "@me") member_id = req.user_id; + const body = req.body as MemberChangeSchema; + + let member = await Member.findOneOrFail({ + where: { id: member_id, guild_id }, + relations: ["roles", "user"], + }); + const permission = await getPermission(req.user_id, guild_id); + const everyone = await Role.findOneOrFail({ + where: { guild_id: guild_id, name: "@everyone", position: 0 }, + }); + + if (body.avatar) + body.avatar = await handleFile( + `/guilds/${guild_id}/users/${member_id}/avatars`, + body.avatar as string, + ); + + member.assign(body); + + if ("roles" in body) { + permission.hasThrow("MANAGE_ROLES"); + + body.roles = body.roles || []; + body.roles.filter((x) => !!x); + + if (body.roles.indexOf(everyone.id) === -1) + body.roles.push(everyone.id); + member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist + } + + await member.save(); + + member.roles = member.roles.filter((x) => x.id !== everyone.id); + + // do not use promise.all as we have to first write to db before emitting the event to catch errors + await emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.roles.map((x) => x.id) }, + } as GuildMemberUpdateEvent); + + res.json(member); + }, +); router.put("/", route({}), async (req: Request, res: Response) => { // TODO: Lurker mode diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts index ccee59f7..88488871 100644 --- a/src/api/routes/guilds/#guild_id/messages/search.ts +++ b/src/api/routes/guilds/#guild_id/messages/search.ts @@ -72,12 +72,20 @@ router.get("/", route({}), async (req: Request, res: Response) => { 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 channels = await Channel.find({ + where: { guild_id: req.params.guild_id }, + select: ["id"], + }); const ids = []; for (var channel of channels) { - const perm = await getPermission(req.user_id, req.params.guild_id, channel.id); - if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue; + const perm = await getPermission( + req.user_id, + req.params.guild_id, + channel.id, + ); + if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) + continue; ids.push(channel.id); } diff --git a/src/api/routes/guilds/#guild_id/profile/index.ts b/src/api/routes/guilds/#guild_id/profile/index.ts index ddc30943..20a7fa95 100644 --- a/src/api/routes/guilds/#guild_id/profile/index.ts +++ b/src/api/routes/guilds/#guild_id/profile/index.ts @@ -1,30 +1,48 @@ import { route } from "@fosscord/api"; -import { emitEvent, GuildMemberUpdateEvent, handleFile, Member, MemberChangeProfileSchema, OrmUtils } from "@fosscord/util"; +import { + emitEvent, + GuildMemberUpdateEvent, + handleFile, + Member, + MemberChangeProfileSchema, + OrmUtils, +} from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); -router.patch("/:member_id", route({ body: "MemberChangeProfileSchema" }), async (req: Request, res: Response) => { - let { guild_id, member_id } = req.params; - if (member_id === "@me") member_id = req.user_id; - const body = req.body as MemberChangeProfileSchema; - - let member = await Member.findOneOrFail({ where: { id: req.user_id, guild_id }, relations: ["roles", "user"] }); - - if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string); - - member = await OrmUtils.mergeDeep(member, body); - - await member.save(); - - // do not use promise.all as we have to first write to db before emitting the event to catch errors - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: { ...member, roles: member.roles.map((x) => x.id) } - } as GuildMemberUpdateEvent); - - res.json(member); -}); +router.patch( + "/:member_id", + route({ body: "MemberChangeProfileSchema" }), + async (req: Request, res: Response) => { + let { guild_id, member_id } = req.params; + if (member_id === "@me") member_id = req.user_id; + const body = req.body as MemberChangeProfileSchema; + + let member = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id }, + relations: ["roles", "user"], + }); + + if (body.banner) + body.banner = await handleFile( + `/guilds/${guild_id}/users/${req.user_id}/avatars`, + body.banner as string, + ); + + member = await OrmUtils.mergeDeep(member, body); + + await member.save(); + + // do not use promise.all as we have to first write to db before emitting the event to catch errors + await emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.roles.map((x) => x.id) }, + } as GuildMemberUpdateEvent); + + res.json(member); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts index cd5959ff..84648703 100644 --- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -63,12 +63,14 @@ router.patch( ); else body.icon = undefined; - const role = await Role.findOneOrFail({ where: { id: role_id, guild: { id: guild_id } } }); + const role = await Role.findOneOrFail({ + where: { id: role_id, guild: { id: guild_id } }, + }); role.assign({ ...body, permissions: String( - req.permission!.bitfield & BigInt(body.permissions || "0") - ) + req.permission!.bitfield & BigInt(body.permissions || "0"), + ), }); await Promise.all([ diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts index 534a5967..4cd47cf3 100644 --- a/src/api/routes/guilds/#guild_id/roles/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/index.ts @@ -61,9 +61,13 @@ router.post( await Promise.all([ role.save(), // Move all existing roles up one position, to accommodate the new role - Role.createQueryBuilder('roles') - .where({ guild: { id: guild_id }, name: Not("@everyone"), id: Not(role.id) }) - .update({ position: () => 'position + 1' }) + Role.createQueryBuilder("roles") + .where({ + guild: { id: guild_id }, + name: Not("@everyone"), + id: Not(role.id), + }) + .update({ position: () => "position + 1" }) .execute(), emitEvent({ event: "GUILD_ROLE_CREATE", diff --git a/src/api/routes/oauth2/authorize.ts b/src/api/routes/oauth2/authorize.ts index e4c2e986..6374972e 100644 --- a/src/api/routes/oauth2/authorize.ts +++ b/src/api/routes/oauth2/authorize.ts @@ -1,17 +1,24 @@ import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; -import { ApiError, Application, ApplicationAuthorizeSchema, getPermission, DiscordApiErrors, Member, Permissions, User, getRights, Rights, MemberPrivateProjection } from "@fosscord/util"; +import { + ApiError, + Application, + ApplicationAuthorizeSchema, + getPermission, + DiscordApiErrors, + Member, + Permissions, + User, + getRights, + Rights, + MemberPrivateProjection, +} from "@fosscord/util"; const router = Router(); // TODO: scopes, other oauth types router.get("/", route({}), async (req: Request, res: Response) => { - const { - client_id, - scope, - response_type, - redirect_url, - } = req.query; + const { client_id, scope, response_type, redirect_url } = req.query; const app = await Application.findOne({ where: { @@ -33,7 +40,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { id: req.user_id, bot: false, }, - select: ["id", "username", "avatar", "discriminator", "public_flags"] + select: ["id", "username", "avatar", "discriminator", "public_flags"], }); const guilds = await Member.find({ @@ -44,21 +51,23 @@ router.get("/", route({}), async (req: Request, res: Response) => { }, relations: ["guild", "roles"], //@ts-ignore - select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"] + // prettier-ignore + select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"], }); - const guildsWithPermissions = guilds.map(x => { - const perms = x.guild.owner_id === user.id - ? new Permissions(Permissions.FLAGS.ADMINISTRATOR) - : Permissions.finalPermission({ - user: { - id: user.id, - roles: x.roles?.map(x => x.id) || [], - }, - guild: { - roles: x?.roles || [], - } - }); + const guildsWithPermissions = guilds.map((x) => { + const perms = + x.guild.owner_id === user.id + ? new Permissions(Permissions.FLAGS.ADMINISTRATOR) + : Permissions.finalPermission({ + user: { + id: user.id, + roles: x.roles?.map((x) => x.id) || [], + }, + guild: { + roles: x?.roles || [], + }, + }); return { id: x.guild.id, @@ -75,7 +84,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { id: user.id, username: user.username, avatar: user.avatar, - avatar_decoration: null, // TODO + avatar_decoration: null, // TODO discriminator: user.discriminator, public_flags: user.public_flags, }, @@ -87,7 +96,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { summary: app.summary, type: app.type, hook: app.hook, - guild_id: null, // TODO support guilds + 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, @@ -97,50 +106,63 @@ router.get("/", route({}), async (req: Request, res: Response) => { id: bot.id, username: bot.username, avatar: bot.avatar, - avatar_decoration: null, // TODO + avatar_decoration: null, // TODO discriminator: bot.discriminator, public_flags: bot.public_flags, bot: true, - approximated_guild_count: 0, // TODO + approximated_guild_count: 0, // TODO }, authorized: false, }); }); -router.post("/", route({ body: "ApplicationAuthorizeSchema" }), async (req: Request, res: Response) => { - const body = req.body as ApplicationAuthorizeSchema; - const { - client_id, - scope, - response_type, - redirect_url - } = req.query; - - // TODO: captcha verification - // TODO: MFA verification - - const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] }); - // getPermission cache won't exist if we're owner - if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member!.user.bot) throw DiscordApiErrors.UNAUTHORIZED; - perms.hasThrow("MANAGE_GUILD"); - - const app = await Application.findOne({ - where: { - id: client_id as string, - }, - relations: ["bot"], - }); - - // TODO: use DiscordApiErrors - // findOneOrFail throws code 404 - if (!app) throw new ApiError("Unknown Application", 10002, 404); - if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400); - - await Member.addToGuild(app.id, body.guild_id); - - return res.json({ - location: "/oauth2/authorized", // redirect URL - }); -}); +router.post( + "/", + route({ body: "ApplicationAuthorizeSchema" }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationAuthorizeSchema; + const { client_id, scope, response_type, redirect_url } = req.query; + + // TODO: captcha verification + // TODO: MFA verification + + const perms = await getPermission( + req.user_id, + body.guild_id, + undefined, + { member_relations: ["user"] }, + ); + // getPermission cache won't exist if we're owner + if ( + Object.keys(perms.cache || {}).length > 0 && + perms.cache.member!.user.bot + ) + throw DiscordApiErrors.UNAUTHORIZED; + perms.hasThrow("MANAGE_GUILD"); + + const app = await Application.findOne({ + where: { + id: client_id as string, + }, + relations: ["bot"], + }); + + // TODO: use DiscordApiErrors + // findOneOrFail throws code 404 + if (!app) throw new ApiError("Unknown Application", 10002, 404); + if (!app.bot) + throw new ApiError( + "OAuth2 application does not have a bot", + 50010, + 400, + ); + + await Member.addToGuild(app.id, body.guild_id); + + return res.json({ + location: "/oauth2/authorized", // redirect URL + }); + }, +); export default router; diff --git a/src/api/routes/policies/stats.ts b/src/api/routes/policies/stats.ts index 5ef4c3c6..dc4652fc 100644 --- a/src/api/routes/policies/stats.ts +++ b/src/api/routes/policies/stats.ts @@ -1,5 +1,12 @@ import { route } from "@fosscord/api"; -import { Config, getRights, Guild, Member, Message, User } from "@fosscord/util"; +import { + Config, + getRights, + Guild, + Member, + Message, + User, +} from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); @@ -15,7 +22,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { guild: await Guild.count(), message: await Message.count(), members: await Member.count(), - } + }, }); }); diff --git a/src/api/routes/stop.ts b/src/api/routes/stop.ts index 1b4e1da9..3f49b360 100644 --- a/src/api/routes/stop.ts +++ b/src/api/routes/stop.ts @@ -3,10 +3,14 @@ import { route } from "@fosscord/api"; const router: Router = Router(); -router.post("/", route({ right: "OPERATOR" }), async (req: Request, res: Response) => { - console.log(`/stop was called by ${req.user_id} at ${new Date()}`); - res.sendStatus(200); - process.kill(process.pid, "SIGTERM"); -}); +router.post( + "/", + route({ right: "OPERATOR" }), + async (req: Request, res: Response) => { + console.log(`/stop was called by ${req.user_id} at ${new Date()}`); + res.sendStatus(200); + process.kill(process.pid, "SIGTERM"); + }, +); -export default router; \ No newline at end of file +export default router; diff --git a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts index 7c544921..6b49e959 100644 --- a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts +++ b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts @@ -16,7 +16,7 @@ const skus = new Map([ sku_id: "521842865731534868", currency: "eur", price: 0, - price_tier: null + price_tier: null, }, { id: "511651860671627264", @@ -27,9 +27,9 @@ const skus = new Map([ sku_id: "521842865731534868", currency: "eur", price: 0, - price_tier: null - } - ] + price_tier: null, + }, + ], ], [ "521846918637420545", @@ -43,7 +43,7 @@ const skus = new Map([ sku_id: "521846918637420545", currency: "eur", price: 0, - price_tier: null + price_tier: null, }, { id: "511651876987469824", @@ -54,7 +54,7 @@ const skus = new Map([ sku_id: "521846918637420545", currency: "eur", price: 0, - price_tier: null + price_tier: null, }, { id: "978380684370378761", @@ -65,9 +65,9 @@ const skus = new Map([ sku_id: "521846918637420545", currency: "eur", price: 0, - price_tier: null - } - ] + price_tier: null, + }, + ], ], [ "521847234246082599", @@ -81,7 +81,7 @@ const skus = new Map([ sku_id: "521847234246082599", currency: "eur", price: 0, - price_tier: null + price_tier: null, }, { id: "511651880837840896", @@ -92,7 +92,7 @@ const skus = new Map([ sku_id: "521847234246082599", currency: "eur", price: 0, - price_tier: null + price_tier: null, }, { id: "511651885459963904", @@ -103,9 +103,9 @@ const skus = new Map([ sku_id: "521847234246082599", currency: "eur", price: 0, - price_tier: null - } - ] + price_tier: null, + }, + ], ], [ "590663762298667008", @@ -120,7 +120,7 @@ const skus = new Map([ discount_price: 0, currency: "eur", price: 0, - price_tier: null + price_tier: null, }, { id: "590665538238152709", @@ -132,9 +132,9 @@ const skus = new Map([ discount_price: 0, currency: "eur", price: 0, - price_tier: null - } - ] + price_tier: null, + }, + ], ], [ "978380684370378762", @@ -158,33 +158,33 @@ const skus = new Map([ { currency: "usd", amount: 0, - exponent: 2 - } - ] + exponent: 2, + }, + ], }, payment_source_prices: { "775487223059316758": [ { currency: "usd", amount: 0, - exponent: 2 - } + exponent: 2, + }, ], "736345864146255982": [ { currency: "usd", amount: 0, - exponent: 2 - } + exponent: 2, + }, ], "683074999590060249": [ { currency: "usd", amount: 0, - exponent: 2 - } - ] - } + exponent: 2, + }, + ], + }, }, "3": { country_prices: { @@ -193,33 +193,33 @@ const skus = new Map([ { currency: "usd", amount: 0, - exponent: 2 - } - ] + exponent: 2, + }, + ], }, payment_source_prices: { "775487223059316758": [ { currency: "usd", amount: 0, - exponent: 2 - } + exponent: 2, + }, ], "736345864146255982": [ { currency: "usd", amount: 0, - exponent: 2 - } + exponent: 2, + }, ], "683074999590060249": [ { currency: "usd", amount: 0, - exponent: 2 - } - ] - } + exponent: 2, + }, + ], + }, }, "4": { country_prices: { @@ -228,33 +228,33 @@ const skus = new Map([ { currency: "usd", amount: 0, - exponent: 2 - } - ] + exponent: 2, + }, + ], }, payment_source_prices: { "775487223059316758": [ { currency: "usd", amount: 0, - exponent: 2 - } + exponent: 2, + }, ], "736345864146255982": [ { currency: "usd", amount: 0, - exponent: 2 - } + exponent: 2, + }, ], "683074999590060249": [ { currency: "usd", amount: 0, - exponent: 2 - } - ] - } + exponent: 2, + }, + ], + }, }, "1": { country_prices: { @@ -263,39 +263,39 @@ const skus = new Map([ { currency: "usd", amount: 0, - exponent: 2 - } - ] + exponent: 2, + }, + ], }, payment_source_prices: { "775487223059316758": [ { currency: "usd", amount: 0, - exponent: 2 - } + exponent: 2, + }, ], "736345864146255982": [ { currency: "usd", amount: 0, - exponent: 2 - } + exponent: 2, + }, ], "683074999590060249": [ { currency: "usd", amount: 0, - exponent: 2 - } - ] - } - } - } - } - ] - ] - ] + exponent: 2, + }, + ], + }, + }, + }, + }, + ], + ], + ], ]); router.get("/", route({}), async (req: Request, res: Response) => { diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts index 275c458b..7e9128f4 100644 --- a/src/api/routes/updates.ts +++ b/src/api/routes/updates.ts @@ -8,19 +8,20 @@ router.get("/", route({}), async (req: Request, res: Response) => { const { client } = Config.get(); const platform = req.query.platform; - if (!platform) throw FieldErrors({ - platform: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - } - }); + 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" } + order: { pub_date: "DESC" }, }); res.json({ diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts index ac844427..5c649056 100644 --- a/src/api/routes/users/#id/profile.ts +++ b/src/api/routes/users/#id/profile.ts @@ -89,79 +89,94 @@ router.get( bot: user.bot, }; - const userProfile = { - bio: req.user_bot ? null : user.bio, - accent_color: user.accent_color, - banner: user.banner, - pronouns: user.pronouns, - 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 = { - accent_color: null, - banner: guild_member?.banner || null, - bio: guild_member?.bio || "", - guild_id - }; - res.json({ - connected_accounts: user.connected_accounts, - premium_guild_since: premium_guild_since, // TODO - premium_since: user.premium_since, // TODO - mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true - user: userDto, - premium_type: user.premium_type, - profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason? - user_profile: userProfile, - guild_member: guild_id && guildMemberDto, - guild_member_profile: guild_id && guildMemberProfile - }); -}); - -router.patch("/", route({ body: "UserProfileModifySchema" }), async (req: Request, res: Response) => { - const body = req.body as UserProfileModifySchema; - - if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - let user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] }); - - user.assign(body); - await user.save(); - - // @ts-ignore - delete user.data; - - // TODO: send update member list event in gateway - await emitEvent({ - event: "USER_UPDATE", - user_id: req.user_id, - data: user - } as UserUpdateEvent); - - res.json({ - accent_color: user.accent_color, - bio: user.bio, - banner: user.banner, - theme_colors: user.theme_colors, - pronouns: user.pronouns, - }); -}); + const userProfile = { + bio: req.user_bot ? null : user.bio, + accent_color: user.accent_color, + banner: user.banner, + pronouns: user.pronouns, + 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 = { + accent_color: null, + banner: guild_member?.banner || null, + bio: guild_member?.bio || "", + guild_id, + }; + res.json({ + connected_accounts: user.connected_accounts, + premium_guild_since: premium_guild_since, // TODO + premium_since: user.premium_since, // TODO + mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true + user: userDto, + premium_type: user.premium_type, + profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason? + user_profile: userProfile, + guild_member: guild_id && guildMemberDto, + guild_member_profile: guild_id && guildMemberProfile, + }); + }, +); + +router.patch( + "/", + route({ body: "UserProfileModifySchema" }), + async (req: Request, res: Response) => { + const body = req.body as UserProfileModifySchema; + + if (body.banner) + body.banner = await handleFile( + `/banners/${req.user_id}`, + body.banner as string, + ); + let user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: [...PrivateUserProjection, "data"], + }); + + user.assign(body); + await user.save(); + + // @ts-ignore + delete user.data; + + // TODO: send update member list event in gateway + await emitEvent({ + event: "USER_UPDATE", + user_id: req.user_id, + data: user, + } as UserUpdateEvent); + + res.json({ + accent_color: user.accent_color, + bio: user.bio, + banner: user.banner, + theme_colors: user.theme_colors, + pronouns: user.pronouns, + }); + }, +); export default router; diff --git a/src/api/routes/users/@me/guilds/#guild_id/settings.ts b/src/api/routes/users/@me/guilds/#guild_id/settings.ts index 4538785c..436261d4 100644 --- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts +++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts @@ -32,8 +32,7 @@ router.patch( const user = await Member.findOneOrFail({ where: { id: req.user_id, guild_id: req.params.guild_id }, - select: ["settings"] - + select: ["settings"], }); OrmUtils.mergeDeep(user.settings || {}, body); Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user); diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts index 3ac48f27..37356d9d 100644 --- a/src/api/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts @@ -98,7 +98,7 @@ router.patch( } user.data.hash = await bcrypt.hash(body.new_password, 12); user.data.valid_tokens_since = new Date(); - newToken = await generateToken(user.id) as string; + newToken = (await generateToken(user.id)) as string; } if (body.username) { diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts index 0fd8220a..cce366ac 100644 --- a/src/api/routes/users/@me/settings.ts +++ b/src/api/routes/users/@me/settings.ts @@ -21,7 +21,7 @@ router.patch( const user = await User.findOneOrFail({ where: { id: req.user_id, bot: false }, - relations: ["settings"] + relations: ["settings"], }); user.settings.assign(body); diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts index 37269185..93dc3bf4 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts @@ -53,7 +53,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { channel_id: opts.channel_id, attachments: opts.attachments || [], embeds: opts.embeds || [], - reactions: /*opts.reactions ||*/[], + reactions: /*opts.reactions ||*/ [], type: opts.type ?? 0, }); @@ -180,7 +180,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { // TODO: cache link result in db export async function postHandleMessage(message: Message) { - const content = message.content?.replace(/ *\`[^)]*\` */g, ""); // remove markdown + const content = message.content?.replace(/ *\`[^)]*\` */g, ""); // remove markdown var links = content?.match(LINK_REGEX); if (!links) return; @@ -201,8 +201,12 @@ export async function postHandleMessage(message: Message) { } // bit gross, but whatever! - const endpointPublic = Config.get().cdn.endpointPublic || "http://127.0.0.1"; // lol - const handler = url.hostname == new URL(endpointPublic).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"]; + const endpointPublic = + Config.get().cdn.endpointPublic || "http://127.0.0.1"; // lol + const handler = + url.hostname == new URL(endpointPublic).hostname + ? EmbedHandlers["self"] + : EmbedHandlers[url.hostname] || EmbedHandlers["default"]; try { let res = await handler(url); @@ -218,11 +222,10 @@ export async function postHandleMessage(message: Message) { cachePromises.push(cache.save()); data.embeds.push(embed); } - } - catch (e) { - Sentry.captureException(e, scope => { + } catch (e) { + Sentry.captureException(e, (scope) => { scope.clear(); - scope.setContext("request", { url }) + scope.setContext("request", { url }); return scope; }); continue; @@ -257,7 +260,7 @@ export async function sendMessage(opts: MessageOptions) { } as MessageCreateEvent), ]); - postHandleMessage(message).catch((e) => { }); // no await as it should catch error non-blockingly + postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly return message; } diff --git a/src/api/util/index.ts b/src/api/util/index.ts index ffad0607..5921f011 100644 --- a/src/api/util/index.ts +++ b/src/api/util/index.ts @@ -7,4 +7,4 @@ export * from "./handlers/route"; export * from "./utility/String"; export * from "./handlers/Voice"; export * from "./utility/captcha"; -export * from "./utility/EmbedHandlers"; \ No newline at end of file +export * from "./utility/EmbedHandlers"; diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts index dca264d0..2549937e 100644 --- a/src/api/util/utility/EmbedHandlers.ts +++ b/src/api/util/utility/EmbedHandlers.ts @@ -16,8 +16,13 @@ export const DEFAULT_FETCH_OPTIONS: any = { method: "GET", }; -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); @@ -26,16 +31,20 @@ export const getProxyUrl = (url: URL, width: number, height: number): string => if (imagorServerUrl) { let path = `${width}x${height}/${url.host}${url.pathname}`; - const hash = crypto.createHmac('sha1', secret) + const hash = crypto + .createHmac("sha1", secret) .update(path) - .digest('base64') - .replace(/\+/g, '-').replace(/\//g, '_'); + .digest("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_"); return `${imagorServerUrl}/${hash}/${path}`; } // TODO: Imagor documentation - console.log("Imagor has not been set up correctly. docs.fosscord.com/set/up/a/page/about/this"); + console.log( + "Imagor has not been set up correctly. docs.fosscord.com/set/up/a/page/about/this", + ); return ""; }; @@ -69,8 +78,7 @@ const doFetch = async (url: URL) => { ...DEFAULT_FETCH_OPTIONS, size: Config.get().limits.message.maxEmbedDownloadSize, }); - } - catch (e) { + } catch (e) { return null; } }; @@ -88,12 +96,10 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => { width = result.width; height = result.height; image = url.href; - } - else if (type.headers.get("content-type")?.indexOf("video") !== -1) { + } else if (type.headers.get("content-type")?.indexOf("video") !== -1) { // TODO return null; - } - else { + } else { // have to download the page, unfortunately const response = await doFetch(url); if (!response) return null; @@ -113,13 +119,15 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => { height: height, url: url.href, proxy_url: getProxyUrl(new URL(image), width, height), - } + }, }; }; -export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed[] | null>; } = { +export const EmbedHandlers: { + [key: string]: (url: URL) => Promise<Embed | Embed[] | null>; +} = { // the url does not have a special handler - "default": async (url: URL) => { + default: async (url: URL) => { const type = await fetch(url, { ...DEFAULT_FETCH_OPTIONS, method: "HEAD", @@ -154,7 +162,13 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed width: metas.width, height: metas.height, url: metas.image, - proxy_url: metas.image ? getProxyUrl(new URL(metas.image), metas.width!, metas.height!) : undefined, + proxy_url: metas.image + ? getProxyUrl( + new URL(metas.image), + metas.width!, + metas.height!, + ) + : undefined, }, description: metas.description, }; @@ -169,26 +183,28 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed // TODO: facebook // have to use their APIs or something because they don't send the metas in initial html - "twitter.com": (url: URL) => { return EmbedHandlers["www.twitter.com"](url); }, + "twitter.com": (url: URL) => { + return EmbedHandlers["www.twitter.com"](url); + }, "www.twitter.com": async (url: URL) => { const token = Config.get().external.twitter; if (!token) return null; - if (!url.href.includes("/status/")) return null; // TODO; - const id = url.pathname.split("/")[3]; // super bad lol + if (!url.href.includes("/status/")) return null; // TODO; + const id = url.pathname.split("/")[3]; // super bad lol if (!parseInt(id)) return null; - const endpointUrl = `https://api.twitter.com/2/tweets/${id}` + + const endpointUrl = + `https://api.twitter.com/2/tweets/${id}` + `?expansions=author_id,attachments.media_keys` + - `&media.fields=url,width,height` + - `&tweet.fields=created_at,public_metrics` + - `&user.fields=profile_image_url`; - + `&media.fields=url,width,height` + + `&tweet.fields=created_at,public_metrics` + + `&user.fields=profile_image_url`; const response = await fetch(endpointUrl, { ...DEFAULT_FETCH_OPTIONS, headers: { authorization: `Bearer ${token}`, - } + }, }); const json = await response.json(); if (json.errors) return null; @@ -196,7 +212,9 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed const text = json.data.text; const created_at = new Date(json.data.created_at); const metrics = json.data.public_metrics; - let media = json.includes.media?.filter((x: any) => x.type == "photo") as any[]; // TODO: video + let media = json.includes.media?.filter( + (x: any) => x.type == "photo", + ) as any[]; // TODO: video const embed: Embed = { type: EmbedType.rich, @@ -205,19 +223,38 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed 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, fields: [ - { inline: true, name: "Likes", value: metrics.like_count.toString() }, - { inline: true, name: "Retweet", value: metrics.retweet_count.toString() }, + { + inline: true, + name: "Likes", + value: metrics.like_count.toString(), + }, + { + inline: true, + name: "Retweet", + value: metrics.retweet_count.toString(), + }, ], 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: { @@ -231,7 +268,11 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed 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(); } @@ -265,17 +306,21 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed thumbnail: { width: 640, height: 640, - proxy_url: metas.image ? getProxyUrl(new URL(metas.image!), 640, 640) : undefined, + proxy_url: metas.image + ? getProxyUrl(new URL(metas.image!), 640, 640) + : undefined, url: metas.image, }, provider: { url: "https://spotify.com", name: "Spotify", - } + }, }; }, - "pixiv.net": (url: URL) => { return EmbedHandlers["www.pixiv.net"](url); }, + "pixiv.net": (url: URL) => { + return EmbedHandlers["www.pixiv.net"](url); + }, "www.pixiv.net": async (url: URL) => { const response = await doFetch(url); if (!response) return null; @@ -291,12 +336,18 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed width: metas.width, height: metas.height, url: url.href, - proxy_url: metas.image ? getProxyUrl(new URL(metas.image!), metas.width!, metas.height!) : undefined, + proxy_url: metas.image + ? getProxyUrl( + new URL(metas.image!), + metas.width!, + metas.height!, + ) + : undefined, }, provider: { url: "https://pixiv.net", - name: "Pixiv" - } + name: "Pixiv", + }, }; }, @@ -310,35 +361,42 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed type: EmbedType.rich, title: metas.title, description: metas.description, - image: { // TODO: meant to be thumbnail. + image: { + // TODO: meant to be thumbnail. // isn't this standard across all of steam? 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", - name: "Steam" + name: "Steam", }, // TODO: fields for release date // TODO: Video }; }, - "reddit.com": (url: URL) => { return EmbedHandlers["www.reddit.com"](url); }, + "reddit.com": (url: URL) => { + return EmbedHandlers["www.reddit.com"](url); + }, "www.reddit.com": async (url: URL) => { const res = await EmbedHandlers["default"](url); return { ...res, color: 16777215, provider: { - name: "reddit" - } + name: "reddit", + }, }; }, - "youtube.com": (url: URL) => { return EmbedHandlers["www.youtube.com"](url); }, + "youtube.com": (url: URL) => { + return EmbedHandlers["www.youtube.com"](url); + }, "www.youtube.com": async (url: URL): Promise<Embed | null> => { const response = await doFetch(url); if (!response) return null; @@ -358,7 +416,13 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed width: metas.width, height: metas.height, url: metas.image, - proxy_url: metas.image ? getProxyUrl(new URL(metas.image!), metas.width!, metas.height!) : undefined, + proxy_url: metas.image + ? getProxyUrl( + new URL(metas.image!), + metas.width!, + metas.height!, + ) + : undefined, }, provider: { url: "https://www.youtube.com", @@ -369,12 +433,12 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed author: { name: metas.author, // TODO: author channel url - } + }, }; }, // the url is an image from this instance - "self": async (url: URL): Promise<Embed | null> => { + self: async (url: URL): Promise<Embed | null> => { const result = await probe(url.href); return { @@ -385,7 +449,7 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed height: result.height, url: url.href, proxy_url: url.href, - } + }, }; }, -};; \ No newline at end of file +}; |