diff options
Diffstat (limited to 'src/gateway/opcodes/RequestGuildMembers.ts')
-rw-r--r-- | src/gateway/opcodes/RequestGuildMembers.ts | 126 |
1 files changed, 123 insertions, 3 deletions
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, + }, + }); + } } |