From d028a785feeae21764fcd5ca81191e364d2ef3a8 Mon Sep 17 00:00:00 2001 From: greysilly7 Date: Sat, 17 Aug 2024 00:59:57 -0400 Subject: [PATCH 01/16] Fix: Logins Failing due to null client_status --- src/gateway/opcodes/Identify.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index e30a1ee0..c535cd45 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -126,6 +126,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { os: identify.properties?.os || identify.properties?.$os, version: 0, }, + client_status: {}, activities: identify.presence?.activities, // TODO: validation }); From af36ea0bb5e5d168e3b3a2fee271e0053a43a73d Mon Sep 17 00:00:00 2001 From: Cyber Date: Sat, 17 Aug 2024 11:22:37 +0200 Subject: [PATCH 02/16] fix: allow array in op 8 --- src/gateway/opcodes/RequestGuildMembers.ts | 9 ++++++--- src/util/schemas/RequestGuildMembersSchema.ts | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/gateway/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts index d294f4d3..c84bf893 100644 --- a/src/gateway/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts @@ -47,7 +47,10 @@ export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { if ((query || (user_ids && user_ids.length > 0)) && (!limit || limit > 100)) limit = 100; - const permissions = await getPermission(this.user_id, guild_id); + const permissions = await getPermission( + this.user_id, + Array.isArray(guild_id) ? guild_id[0] : guild_id, + ); permissions.hasThrow("VIEW_CHANNEL"); const whereQuery: FindManyOptions["where"] = {}; @@ -62,7 +65,7 @@ export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { const memberFind: FindManyOptions = { where: { ...whereQuery, - guild_id, + guild_id: Array.isArray(guild_id) ? guild_id[0] : guild_id, }, relations: ["user", "roles"], }; @@ -70,7 +73,7 @@ export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { const members = await Member.find(memberFind); const baseData = { - guild_id, + guild_id: Array.isArray(guild_id) ? guild_id[0] : guild_id, nonce, }; diff --git a/src/util/schemas/RequestGuildMembersSchema.ts b/src/util/schemas/RequestGuildMembersSchema.ts index 01ba4f2e..8271a453 100644 --- a/src/util/schemas/RequestGuildMembersSchema.ts +++ b/src/util/schemas/RequestGuildMembersSchema.ts @@ -17,7 +17,7 @@ */ export interface RequestGuildMembersSchema { - guild_id: string; + guild_id: string | string[]; query?: string; limit?: number; presences?: boolean; @@ -26,7 +26,7 @@ export interface RequestGuildMembersSchema { } export const RequestGuildMembersSchema = { - guild_id: String, + guild_id: [] as string | string[], $query: String, $limit: Number, $presences: Boolean, From 601a9d535b137a2912dad39789e074c92194b8b7 Mon Sep 17 00:00:00 2001 From: Cyber Date: Sat, 17 Aug 2024 11:40:34 +0200 Subject: [PATCH 03/16] regenerate openapi.json --- assets/openapi.json | Bin 614194 -> 617024 bytes hashes.json | 4 ++-- src/util/schemas/RequestGuildMembersSchema.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/openapi.json b/assets/openapi.json index 50d4fca1fb924f0737abce6130996d1f285551f9..2af0a2c700b59b034ce0c5ad1fdb65d6592a5404 100644 GIT binary patch delta 135 zcmdnAPxZhR)rJF7M2#)7Pc1lEgYqDr~4Lga81A4$-&8$R9aMAQmbEGT0DJy zADhHBBi55l+aKt21TamXAjfXKeZhJT0Y(tha=UB?2M2`70@5|PQBZdJg. */ @@ -19,7 +19,6 @@ export interface RegisterSchema { /** * @minLength 2 - * @maxLength 32 */ username: string; /** diff --git a/src/util/schemas/UserModifySchema.ts b/src/util/schemas/UserModifySchema.ts index 4be6ad43..e4ed1071 100644 --- a/src/util/schemas/UserModifySchema.ts +++ b/src/util/schemas/UserModifySchema.ts @@ -18,8 +18,7 @@ export interface UserModifySchema { /** - * @minLength 1 - * @maxLength 100 + * @minLength 2 */ username?: string; avatar?: string | null; From 95bbccb6f723264e514618b18d1af9e3679e0e6d Mon Sep 17 00:00:00 2001 From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:24:38 +0200 Subject: [PATCH 06/16] Same error message if username too long --- src/api/routes/auth/register.ts | 4 ++-- src/api/routes/users/@me/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts index ea5de53b..62152440 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts @@ -291,8 +291,8 @@ router.post( if (body.username.length > maxUsername) { throw FieldErrors({ username: { - code: "USERNAME_INVALID", - message: `Username must be less than ${maxUsername} in length`, + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 2 and ${maxUsername} in length.`, }, }); } diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts index 5caf0d11..9cd8bfda 100644 --- a/src/api/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts @@ -155,8 +155,8 @@ router.patch( if (check_username.length > maxUsername) { throw FieldErrors({ username: { - code: "USERNAME_INVALID", - message: `Username must be less than ${maxUsername} in length`, + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 2 and ${maxUsername} in length.`, }, }); } From e0b6dd05814453d54103d64d1631519019405a8b Mon Sep 17 00:00:00 2001 From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com> Date: Sun, 18 Aug 2024 06:45:11 +0200 Subject: [PATCH 07/16] Fix bug & handle 75k/large_threshold in opcode 8 Also fixes a bug that only allowed "guild_id" and "user_ids" to be an Array instead of also a String Co-Authored-By: Puyodead1 --- assets/openapi.json | Bin 617024 -> 617567 bytes assets/schemas.json | Bin 21209522 -> 23504303 bytes src/gateway/opcodes/Identify.ts | 1 + src/gateway/opcodes/RequestGuildMembers.ts | 109 ++++++++++++++---- src/gateway/util/WebSocket.ts | 7 +- src/util/schemas/RequestGuildMembersSchema.ts | 2 +- 6 files changed, 92 insertions(+), 27 deletions(-) diff --git a/assets/openapi.json b/assets/openapi.json index 2af0a2c700b59b034ce0c5ad1fdb65d6592a5404..68adb4556eac99d06647d081f6de81280f125da2 100644 GIT binary patch delta 153 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 delta 20655 zcmd6v`BPI@6vyX6LIObupaI!5ZfHdot-B7O2n7WZabXz@xPz_WiVK*6fYo797*43l ziwiB{0@@C@TBk1#h#L;nI$E_=JBYPaMB9!IR};WAIaRm90G6recsHRCplvV=AtFX`lZ8WB=8T#q59QQyx0qlTnJv zV+OySzjCL@!3!5&$d`ODL1W~`vV$moGpqbCibI0qg=>YDpXGI z0M0>@s2q689{&~W|8kbb-uS66~#q}|vB{D3u%rRIz?=?Jk{wxDr#Ph1Q$je~fNj2bui0B5q zB=0QF`2@~ElBgWH!=$<4c!ISz%nQNGq-7%#3SF?$L!X8gCOHoh8aBo;cQTDlbffzx z7mkF3lnhl+>CnVj9vPI7$S5nWPELkV33F+Qj3!fgXr2pY{mncp;_SzelTM}Jxqbyz=SDITOvor5>?-x+i}@Miuy=cViMs9qK=55Ze5pJuD};c3;9laY-_)zq_QIi7FsWd_I2>~1^XwPa3bKr_pE81_`T}60{ddX04OfLnKI-DuE!L{TR z-um7$53cPgD{?}+KJgpQ`-Z_e$~PBPuEVRwG8o-~3ohQWwYnRx?zv^F4B)!A`)%QZ zi<-JzMw2lnbRyL}J2J?;&mnZAjb;&4c2=(t)OQ!Lv1DYe3n`!>bs*_C)WpKg+PS|Q zhvNw=Q7QR5-H|LfH2(Z{?M^rCV5UJFNIM}0AzV7w&qWC&cr#$%cr`r#Gd zl2%IIIpvHao{d?jzc%1eeKwNUz}d*_jAswvEd(xlp2u6D^W?qX>EYta+L4f^yzmUp zMl0G6`OU7Ns%7?G{L}%h`m(4-_uAD?T)*XVFQ|0yUt9&;cCj=L2`jIoqJ`XVe*4 zI(^2o5#-#fa=qk!&Wc2d)upIS`m7et&d`a$G5$dDnftsg>;l{<|OS-$@mh|+E z{fj!dy0hoiUvl!A9zB2>NAk(kSX?=aV%wa3grL0vQqP$>-o=vy*1CxLw%fRqPp*xc zylT&mLK_E{#^D7Qh-6lmmw)`a2M(>Nt2e_P%|+_?wNFMp*>81dWLzQ(hAa1~x_Xtu z?v;#HqUj|FKijgSa8xbx?bs1CD$C7z@!1t6+Xfe5)+bJdKciGE6HPY@TMi_ZO6VM~ zX3OE&t{it|wQy8D+5v1o9F@x8&JMy+jdJn?q>9|>&Fay#lCF+2anl=LmZLyJ6V`6l z0@_&8J(N|$Y4zpO5qRn0zfx5$w%r-Y?BF_!%(bDIoG_X_8VKj8sdEe?II15Lg8=8` ztM;)>K-0>y@bHZo&km!JiDBm_vdwTCfWqYQQvfTkCC8_+VmJ#|YC~8y*bNNnQB0}h zR!O$kZ_W`@u8U;-&NjY#kofxjTzkBj&uMnh(7O zSni#PA@ND98IMwWS%zHPbv1=W!x?LSIfb2=7`B9Yz_E#c@HEyR534v}89M=n^;%=& Gkoym>8_Wv; diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index c535cd45..94320eee 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -82,6 +82,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { const identify: IdentifySchema = data.d; this.capabilities = new Capabilities(identify.capabilities || 0); + this.large_threshold = identify.large_threshold || 250; const user = await tryGetUserFromToken(identify.token, { relations: ["relationships", "relationships.to", "settings"], diff --git a/src/gateway/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts index c84bf893..3381caed 100644 --- a/src/gateway/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts @@ -17,6 +17,7 @@ */ import { + getDatabase, getPermission, GuildMembersChunkEvent, Member, @@ -29,51 +30,103 @@ import { check } from "./instanceOf"; import { FindManyOptions, In, Like } from "typeorm"; export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { - // TODO: check data + // Schema validation can only accept either string or array, so transforming it here to support both + if (!d.guild_id) throw new Error('"guild_id" is required'); + d.guild_id = Array.isArray(d.guild_id) ? d.guild_id[0] : d.guild_id; + + if (d.user_ids && !Array.isArray(d.user_ids)) d.user_ids = [d.user_ids]; + check.call(this, RequestGuildMembersSchema, d); - const { guild_id, query, presences, nonce } = - d as RequestGuildMembersSchema; - let { limit, user_ids } = d as RequestGuildMembersSchema; + const { query, presences, nonce } = d as RequestGuildMembersSchema; + let { limit, user_ids, guild_id } = d as RequestGuildMembersSchema; + + guild_id = guild_id as string; + user_ids = user_ids as string[] | undefined; if ("query" in d && (!limit || Number.isNaN(limit))) throw new Error('"query" requires "limit" to be set'); if ("query" in d && user_ids) throw new Error('"query" and "user_ids" are mutually exclusive'); - if (user_ids && !Array.isArray(user_ids)) user_ids = [user_ids]; - user_ids = user_ids as string[] | undefined; // TODO: Configurable limit? if ((query || (user_ids && user_ids.length > 0)) && (!limit || limit > 100)) limit = 100; - const permissions = await getPermission( - this.user_id, - Array.isArray(guild_id) ? guild_id[0] : guild_id, - ); + const permissions = await getPermission(this.user_id, guild_id); permissions.hasThrow("VIEW_CHANNEL"); - const whereQuery: FindManyOptions["where"] = {}; - if (query) { - whereQuery.user = { - username: Like(query + "%"), - }; - } else if (user_ids && user_ids.length > 0) { - whereQuery.id = In(user_ids); - } + const memberCount = await Member.count({ + where: { + guild_id, + }, + }); const memberFind: FindManyOptions = { where: { - ...whereQuery, - guild_id: Array.isArray(guild_id) ? guild_id[0] : guild_id, + guild_id, }, relations: ["user", "roles"], }; if (limit) memberFind.take = Math.abs(Number(limit || 100)); - const members = await Member.find(memberFind); + + let members: Member[] = []; + + if (memberCount > 75000) { + // since we dont have voice channels yet, just return the connecting users member object + members = await Member.find({ + ...memberFind, + where: { + ...memberFind.where, + user: { + id: this.user_id, + }, + }, + }); + } else if (memberCount > this.large_threshold) { + // find all members who are online, have a role, have a nickname, or are in a voice channel, as well as respecting the query and user_ids + const db = getDatabase(); + if (!db) throw new Error("Database not initialized"); + const repo = db.getRepository(Member); + const q = repo + .createQueryBuilder("member") + .where("member.guild_id = :guild_id", { guild_id }) + .leftJoinAndSelect("member.roles", "role") + .leftJoinAndSelect("member.user", "user") + .leftJoinAndSelect("user.sessions", "session") + .andWhere( + "',' || member.roles || ',' NOT LIKE :everyoneRoleIdList", + { everyoneRoleIdList: "%," + guild_id + ",%" }, + ) + .andWhere("session.status != 'offline'") + .addOrderBy("user.username", "ASC") + .limit(memberFind.take); + + if (query && query != "") { + q.andWhere(`user.username ILIKE :query`, { + query: `${query}%`, + }); + } else if (user_ids) { + q.andWhere(`user.id IN (:...user_ids)`, { user_ids }); + } + + members = await q.getMany(); + } else { + if (query) { + // @ts-expect-error memberFind.where is very much defined + memberFind.where.user = { + username: Like(query + "%"), + }; + } else if (user_ids && user_ids.length > 0) { + // @ts-expect-error memberFind.where is still very much defined + memberFind.where.id = In(user_ids); + } + + members = await Member.find(memberFind); + } const baseData = { - guild_id: Array.isArray(guild_id) ? guild_id[0] : guild_id, + guild_id, nonce, }; @@ -114,7 +167,17 @@ export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { }); } - if (notFound.length > 0) chunks[0].not_found = notFound; + if (notFound.length > 0) { + if (chunks.length == 0) + chunks.push({ + ...baseData, + members: [], + presences: presences ? [] : undefined, + chunk_index: 0, + chunk_count: 1, + }); + chunks[0].not_found = notFound; + } chunks.forEach((chunk) => { Send(this, { diff --git a/src/gateway/util/WebSocket.ts b/src/gateway/util/WebSocket.ts index 833756ff..8cfc5e08 100644 --- a/src/gateway/util/WebSocket.ts +++ b/src/gateway/util/WebSocket.ts @@ -1,17 +1,17 @@ /* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Copyright (C) 2023 Spacebar and Spacebar Contributors - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ @@ -43,4 +43,5 @@ export interface WebSocket extends WS { listen_options: ListenEventOpts; capabilities?: Capabilities; // client?: Client; + large_threshold: number; } diff --git a/src/util/schemas/RequestGuildMembersSchema.ts b/src/util/schemas/RequestGuildMembersSchema.ts index 6909ba85..9e60d26e 100644 --- a/src/util/schemas/RequestGuildMembersSchema.ts +++ b/src/util/schemas/RequestGuildMembersSchema.ts @@ -26,7 +26,7 @@ export interface RequestGuildMembersSchema { } export const RequestGuildMembersSchema = { - guild_id: [] as string | string[], + guild_id: "" as string | string[], $query: String, $limit: Number, $presences: Boolean, From db38e3e3ed7d3fa33b4092fa78de559d74a6299e Mon Sep 17 00:00:00 2001 From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com> Date: Sun, 18 Aug 2024 09:31:54 +0200 Subject: [PATCH 08/16] Send "pinned" as "true" in pins update event --- src/api/routes/channels/#channel_id/pins.ts | 23 +++++++++------------ 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts index 724ebffd..1e1da018 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts @@ -1,24 +1,23 @@ /* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Copyright (C) 2023 Spacebar and Spacebar Contributors - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { route } from "@spacebar/api"; import { - Channel, ChannelPinsUpdateEvent, Config, DiscordApiErrors, @@ -60,8 +59,10 @@ router.put( if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); + message.pinned = true; + await Promise.all([ - Message.update({ id: message_id }, { pinned: true }), + message.save(), emitEvent({ event: "MESSAGE_UPDATE", channel_id, @@ -98,31 +99,27 @@ router.delete( async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (channel.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - const message = await Message.findOneOrFail({ where: { id: message_id }, }); + + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + message.pinned = false; await Promise.all([ message.save(), - emitEvent({ event: "MESSAGE_UPDATE", channel_id, data: message, } as MessageUpdateEvent), - emitEvent({ event: "CHANNEL_PINS_UPDATE", channel_id, data: { channel_id, - guild_id: channel.guild_id, + guild_id: message.guild_id, last_pin_timestamp: undefined, }, } as ChannelPinsUpdateEvent), From 5679318be0d4bc96002864f21be1b966d578a567 Mon Sep 17 00:00:00 2001 From: Cyber Date: Sun, 18 Aug 2024 11:17:19 +0200 Subject: [PATCH 09/16] feat: message pinned message --- src/api/routes/channels/#channel_id/pins.ts | 33 ++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts index 1e1da018..9a806b5a 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts @@ -1,23 +1,24 @@ /* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Copyright (C) 2023 Spacebar and Spacebar Contributors - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { route } from "@spacebar/api"; import { + Channel, ChannelPinsUpdateEvent, Config, DiscordApiErrors, @@ -77,6 +78,20 @@ router.put( last_pin_timestamp: undefined, }, } as ChannelPinsUpdateEvent), + + Message.create({ + type: 6, + embeds: [], + reactions: [], + channel_id: message.channel_id, + guild_id: message.guild_id, + author_id: req.user_id, + message_reference: { + message_id: message.id, + channel_id: message.channel_id, + guild_id: message.guild_id, + }, + }).save(), ]); res.sendStatus(204); @@ -99,27 +114,31 @@ router.delete( async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (channel.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + const message = await Message.findOneOrFail({ where: { id: message_id }, }); - - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - message.pinned = false; await Promise.all([ message.save(), + emitEvent({ event: "MESSAGE_UPDATE", channel_id, data: message, } as MessageUpdateEvent), + emitEvent({ event: "CHANNEL_PINS_UPDATE", channel_id, data: { channel_id, - guild_id: message.guild_id, + guild_id: channel.guild_id, last_pin_timestamp: undefined, }, } as ChannelPinsUpdateEvent), From 7853ad3acf8f79d904a808382aa4ccc6d5df78fd Mon Sep 17 00:00:00 2001 From: Cyber Date: Sun, 18 Aug 2024 11:31:15 +0200 Subject: [PATCH 10/16] fix: update the code with latest commit --- src/api/routes/channels/#channel_id/pins.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts index 9a806b5a..7b2236e8 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts @@ -1,24 +1,23 @@ /* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Copyright (C) 2023 Spacebar and Spacebar Contributors - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { route } from "@spacebar/api"; import { - Channel, ChannelPinsUpdateEvent, Config, DiscordApiErrors, @@ -114,31 +113,27 @@ router.delete( async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (channel.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - const message = await Message.findOneOrFail({ where: { id: message_id }, }); + + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + message.pinned = false; await Promise.all([ message.save(), - emitEvent({ event: "MESSAGE_UPDATE", channel_id, data: message, } as MessageUpdateEvent), - emitEvent({ event: "CHANNEL_PINS_UPDATE", channel_id, data: { channel_id, - guild_id: channel.guild_id, + guild_id: message.guild_id, last_pin_timestamp: undefined, }, } as ChannelPinsUpdateEvent), From 917f394cc562bbf5c23b8a14ee0c8d70783bc29d Mon Sep 17 00:00:00 2001 From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com> Date: Sun, 18 Aug 2024 12:05:50 +0200 Subject: [PATCH 11/16] Consistent widget disabled error --- src/api/routes/guilds/#guild_id/widget.json.ts | 18 ++++++++++++------ src/api/routes/guilds/#guild_id/widget.png.ts | 10 +++++----- src/util/util/Constants.ts | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts index 39f49804..eb2dd102 100644 --- a/src/api/routes/guilds/#guild_id/widget.json.ts +++ b/src/api/routes/guilds/#guild_id/widget.json.ts @@ -1,25 +1,31 @@ /* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Copyright (C) 2023 Spacebar and Spacebar Contributors - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { random, route } from "@spacebar/api"; -import { Channel, Guild, Invite, Member, Permissions } from "@spacebar/util"; +import { + Channel, + DiscordApiErrors, + Guild, + Invite, + Member, + Permissions, +} from "@spacebar/util"; import { Request, Response, Router } from "express"; -import { HTTPError } from "lambert-server"; const router: Router = Router(); @@ -48,7 +54,7 @@ router.get( const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); + if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; // Fetch existing widget invite for widget channel let invite = await Invite.findOne({ diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts index c9ba8afc..f0f94ea0 100644 --- a/src/api/routes/guilds/#guild_id/widget.png.ts +++ b/src/api/routes/guilds/#guild_id/widget.png.ts @@ -1,17 +1,17 @@ /* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Copyright (C) 2023 Spacebar and Spacebar Contributors - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ @@ -19,7 +19,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { route } from "@spacebar/api"; -import { Guild } from "@spacebar/util"; +import { DiscordApiErrors, Guild } from "@spacebar/util"; import { Request, Response, Router } from "express"; import fs from "fs"; import { HTTPError } from "lambert-server"; @@ -48,7 +48,7 @@ router.get( const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404); + if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; // Fetch guild information const icon = guild.icon; diff --git a/src/util/util/Constants.ts b/src/util/util/Constants.ts index a6caae00..34e925e5 100644 --- a/src/util/util/Constants.ts +++ b/src/util/util/Constants.ts @@ -812,7 +812,7 @@ export const DiscordApiErrors = { "Cannot execute action on a DM channel", 50003, ), - EMBED_DISABLED: new ApiError("Guild widget disabled", 50004), + EMBED_DISABLED: new ApiError("Widget Disabled", 50004), CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError( "Cannot edit a message authored by another user", 50005, From a401f63318ededb7a20c88dc5a06a7dd73f27076 Mon Sep 17 00:00:00 2001 From: Cyber Date: Sun, 18 Aug 2024 12:08:04 +0200 Subject: [PATCH 12/16] fix: message timestamp --- src/api/routes/channels/#channel_id/pins.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts index 7b2236e8..b6bb66b9 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts @@ -79,6 +79,7 @@ router.put( } as ChannelPinsUpdateEvent), Message.create({ + timestamp: new Date(), type: 6, embeds: [], reactions: [], From c505db07236e9c786b8540d6ef80604ff3003f22 Mon Sep 17 00:00:00 2001 From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com> Date: Sun, 18 Aug 2024 13:28:24 +0200 Subject: [PATCH 13/16] Fix widget.png guild icon loading --- src/api/routes/guilds/#guild_id/widget.png.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts index c9ba8afc..271728a4 100644 --- a/src/api/routes/guilds/#guild_id/widget.png.ts +++ b/src/api/routes/guilds/#guild_id/widget.png.ts @@ -1,17 +1,17 @@ /* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Copyright (C) 2023 Spacebar and Spacebar Contributors - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ @@ -24,6 +24,7 @@ import { Request, Response, Router } from "express"; import fs from "fs"; import { HTTPError } from "lambert-server"; import path from "path"; +import { storage } from "../../../../cdn/util/Storage"; const router: Router = Router(); @@ -51,7 +52,7 @@ router.get( if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404); // Fetch guild information - const icon = guild.icon; + const icon = "avatars/" + guild_id + "/" + guild.icon; const name = guild.name; const presence = guild.presence_count + " ONLINE"; @@ -69,8 +70,7 @@ router.get( } // Setup canvas - const { createCanvas } = require("canvas"); - const { loadImage } = require("canvas"); + const { createCanvas, loadImage } = require("canvas"); const sizeOf = require("image-size"); // TODO: Widget style templates need Spacebar branding @@ -211,8 +211,8 @@ async function drawIcon( scale: number, icon: string, ) { - const img = new (require("canvas").Image)(); - img.src = icon; + const { loadImage } = require("canvas"); + const img = await loadImage(await storage.get(icon)); // Do some canvas clipping magic! canvas.save(); From 39e3b5ad7a8eb8f33d3d2278ba70c3747affd4ee Mon Sep 17 00:00:00 2001 From: Cyber Date: Sun, 18 Aug 2024 13:32:19 +0200 Subject: [PATCH 14/16] feat: emit `MESSAGE_CREATE` --- src/api/routes/channels/#channel_id/pins.ts | 45 ++++++++++++++------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts index b6bb66b9..d43db6ec 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts @@ -23,7 +23,9 @@ import { DiscordApiErrors, emitEvent, Message, + MessageCreateEvent, MessageUpdateEvent, + User, } from "@spacebar/util"; import { Request, Response, Router } from "express"; @@ -61,6 +63,30 @@ router.put( message.pinned = true; + const author = await User.getPublicUser(req.user_id); + + const systemPinMessage = Message.create({ + timestamp: new Date(), + type: 6, + guild_id: message.guild_id, + channel_id: message.channel_id, + author, + message_reference: { + message_id: message.id, + channel_id: message.channel_id, + guild_id: message.guild_id, + }, + reactions: [], + attachments: [], + embeds: [], + sticker_items: [], + edited_timestamp: undefined, + mentions: [], + mention_channels: [], + mention_roles: [], + mention_everyone: false, + }); + await Promise.all([ message.save(), emitEvent({ @@ -77,21 +103,12 @@ router.put( last_pin_timestamp: undefined, }, } as ChannelPinsUpdateEvent), - - Message.create({ - timestamp: new Date(), - type: 6, - embeds: [], - reactions: [], + systemPinMessage.save(), + emitEvent({ + event: "MESSAGE_CREATE", channel_id: message.channel_id, - guild_id: message.guild_id, - author_id: req.user_id, - message_reference: { - message_id: message.id, - channel_id: message.channel_id, - guild_id: message.guild_id, - }, - }).save(), + data: systemPinMessage, + } as MessageCreateEvent), ]); res.sendStatus(204); From c9a51cfd179270775f631a6139c87d271bab3a66 Mon Sep 17 00:00:00 2001 From: "Emma [it/its]@Rory&" Date: Sun, 18 Aug 2024 18:54:51 +0200 Subject: [PATCH 15/16] Fix .DS_Store ignore, add gitattributes for nix/scripts --- .gitattributes | 4 ++++ .gitignore | 4 ++-- src/webrtc/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .gitattributes delete mode 100644 src/webrtc/.DS_Store diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..5859d46d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto +*.sh -crlf +*.nix -crlf +.husky/pre-commit -crlf \ No newline at end of file diff --git a/.gitignore b/.gitignore index e62c03d6..0fcd6d2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.DS_STORE +**/.DS_STORE db/ dist/ node_modules @@ -19,4 +19,4 @@ build *.tmp tmp/ dump/ -result +result \ No newline at end of file diff --git a/src/webrtc/.DS_Store b/src/webrtc/.DS_Store deleted file mode 100644 index bfb0a4165ee92c5850a98ab16b2c02a383ec7a7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ5Iwu5S<||EYYN-px6^2asv~Y6XXKOiGU)JBM`kiI1IPo2AqgDAHb1CN(y)* z%|7qUyj}Sf9*>CV@@caWnTtpbHeE23 zRDcRlfiDH@`%vJ9P2v#fpAIZO0s!qd-VM(_O8|=nfKB2MhzLxB3Jj{|h@nA8ykuRI zI0Ob=)NjT)d9&t(qJBH##mhyTK(17P3LGjhi|yR{|26!F`Tvl_9TlJge@X$Z+Eu&6 zD`jt;yqxvg0>6QO8fv{9i??E+w_ZG%D~L3VZ+r CM Date: Sun, 18 Aug 2024 18:57:19 +0200 Subject: [PATCH 16/16] Update nix inputs --- flake.lock | Bin 1497 -> 1497 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/flake.lock b/flake.lock index 29505fc365815eccadfee3b290d344f4e9d9dcd9..42952374c13a35769b490038fb91fbf81b588142 100644 GIT binary patch delta 134 zcmV;10D1q}3)u^>Dgp*JGdDOjG?9=UD{W4l4H)1$BGdN-~ oH(_OBWHMxBWn*SHVr4KkGd3_VFl06|VKQPllfVHZvkU{x0{+t|>i_@% delta 134 zcmcb~eUp2G789GXnUST5@x%$T+9_^PL1Dq^xn9|oz6MFz0R@SXK_%|`*+ITRA^An2 zo-SDyP8Iq7C59m;ljpNYPBv#2n7p2mZE_y7uzpc$nUa-)QlhCvQnE#2in+O^nW<^2 lxut=jp{bFHiK)4vxv52}MM|mxP}n$S@&QKW%{