summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gateway/opcodes/Identify.ts1
-rw-r--r--src/gateway/opcodes/RequestGuildMembers.ts126
-rw-r--r--src/gateway/util/WebSocket.ts3
-rw-r--r--src/util/schemas/RequestGuildMembersSchema.ts35
-rw-r--r--src/util/schemas/index.ts1
5 files changed, 162 insertions, 4 deletions
diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts

index 330ce561..26a625f7 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 304d4b39..13b8488d 100644 --- a/src/gateway/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts
@@ -16,8 +16,128 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { WebSocket } from "@spacebar/gateway"; +import { OPCODES, Payload, Send, WebSocket } from "@spacebar/gateway"; +import { Member, RequestGuildMembersSchema, getDatabase } from "@spacebar/util"; +import { check } from "./instanceOf"; -export function onRequestGuildMembers(this: WebSocket) { - // return this.close(CLOSECODES.Unknown_error); +function partition(members: Member[], size: number) { + const chunks = []; + + for (let i = 0; i < members.length; i += size) { + chunks.push(members.slice(i, i + size)); + } + + return chunks; +} + +export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { + check.call(this, RequestGuildMembersSchema, d); + + const { guild_id, query, limit, presences, user_ids, nonce } = + d as RequestGuildMembersSchema; + + if (query && user_ids) { + throw new Error("Cannot query and provide user ids"); + } + + if (query && !limit) { + throw new Error("Must provide limit when querying"); + } + + const takeLimit = + (query && query !== "") || user_ids + ? limit && limit !== 0 && limit <= 1000 + ? limit + : 100 + : undefined; + + const member_count = await Member.count({ + where: { + guild_id, + }, + }); + + // TODO: if member count is >75k, only return members in voice plus the connecting users member object + // TODO: if member count is >large_threshold, send members who are online, have a role, have a nickname, or are in a voice channel + // TODO: if member count is <large_threshold, send all members + + let members: Member[] = []; + + if (member_count > 75000) { + // since we dont have voice channels yet, just return the connecting users member object + members = await Member.find({ + where: { + guild_id, + user: { + id: this.user_id, + }, + }, + relations: ["user", "roles"], + }); + } else if (member_count > this.large_threshold) { + try { + // 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(takeLimit); + + 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(); + } catch (e) { + console.error(`request guild members`, e); + } + } else { + members = await Member.find({ + where: { + guild_id, + ...(user_ids && { user_id: user_ids }), + ...(query && { username: { startsWith: query } }), + }, + take: takeLimit, + relations: ["user", "roles"], + }); + } + + const chunks = partition(members, 1000); + + for (const [i, chunk] of chunks.entries()) { + await Send(this, { + op: OPCODES.Dispatch, + s: this.sequence++, + t: "GUILD_MEMBERS_CHUNK", + d: { + guild_id, + members: chunk.map((member) => ({ + ...member, + roles: member.roles.map((role) => role.id), + user: member.user.toPublicUser(), + })), + chunk_index: i + 1, + chunk_count: chunks.length, + // not_found: [] + // presences: [] + nonce, + }, + }); + } } diff --git a/src/gateway/util/WebSocket.ts b/src/gateway/util/WebSocket.ts
index 833756ff..7869bed0 100644 --- a/src/gateway/util/WebSocket.ts +++ b/src/gateway/util/WebSocket.ts
@@ -17,8 +17,8 @@ */ import { Intents, ListenEventOpts, Permissions } from "@spacebar/util"; -import WS from "ws"; import { Deflate, Inflate } from "fast-zlib"; +import WS from "ws"; import { Capabilities } from "./Capabilities"; // import { Client } from "@spacebar/webrtc"; @@ -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 new file mode 100644
index 00000000..89f91e21 --- /dev/null +++ b/src/util/schemas/RequestGuildMembersSchema.ts
@@ -0,0 +1,35 @@ +/* + 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 <https://www.gnu.org/licenses/>. +*/ + +export interface RequestGuildMembersSchema { + guild_id: string; + query?: string; + limit?: number; + presences?: boolean; + user_ids?: string[]; + nonce?: string; +} + +export const RequestGuildMembersSchema = { + guild_id: String, + $query: String, + $limit: Number, + $presences: Boolean, + $user_ids: [] as string[], + $nonce: String, +}; diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts
index 44a504cd..98216b2f 100644 --- a/src/util/schemas/index.ts +++ b/src/util/schemas/index.ts
@@ -58,6 +58,7 @@ export * from "./PurgeSchema"; export * from "./RegisterSchema"; export * from "./RelationshipPostSchema"; export * from "./RelationshipPutSchema"; +export * from "./RequestGuildMembersSchema"; export * from "./RoleModifySchema"; export * from "./RolePositionUpdateSchema"; export * from "./SelectProtocolSchema";