1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-22 10:22:39 +01:00

Merge pull request #1181 from DEVTomatoCake/fix/not_found-request-guild-members-error

Bug fixes & handling for >75k/large_threshold guilds in Request Guild Members
This commit is contained in:
Madeline 2024-08-18 15:01:58 +10:00 committed by GitHub
commit c8c6ea4d39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 70337 additions and 3291 deletions

View File

@ -6262,7 +6262,21 @@
"type": "object", "type": "object",
"properties": { "properties": {
"guild_id": { "guild_id": {
"type": "string" "anyOf": [
{
"type": "array",
"items": [
{
"type": "string"
}
],
"minItems": 1,
"maxItems": 1
},
{
"type": "string"
}
]
}, },
"query": { "query": {
"type": "string" "type": "string"

File diff suppressed because it is too large Load Diff

View File

@ -82,6 +82,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
const identify: IdentifySchema = data.d; const identify: IdentifySchema = data.d;
this.capabilities = new Capabilities(identify.capabilities || 0); this.capabilities = new Capabilities(identify.capabilities || 0);
this.large_threshold = identify.large_threshold || 250;
const user = await tryGetUserFromToken(identify.token, { const user = await tryGetUserFromToken(identify.token, {
relations: ["relationships", "relationships.to", "settings"], relations: ["relationships", "relationships.to", "settings"],

View File

@ -17,6 +17,7 @@
*/ */
import { import {
getDatabase,
getPermission, getPermission,
GuildMembersChunkEvent, GuildMembersChunkEvent,
Member, Member,
@ -29,51 +30,103 @@ import { check } from "./instanceOf";
import { FindManyOptions, In, Like } from "typeorm"; import { FindManyOptions, In, Like } from "typeorm";
export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { 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); check.call(this, RequestGuildMembersSchema, d);
const { guild_id, query, presences, nonce } = const { query, presences, nonce } = d as RequestGuildMembersSchema;
d as RequestGuildMembersSchema; let { limit, user_ids, guild_id } = d as RequestGuildMembersSchema;
let { limit, user_ids } = d as RequestGuildMembersSchema;
guild_id = guild_id as string;
user_ids = user_ids as string[] | undefined;
if ("query" in d && (!limit || Number.isNaN(limit))) if ("query" in d && (!limit || Number.isNaN(limit)))
throw new Error('"query" requires "limit" to be set'); throw new Error('"query" requires "limit" to be set');
if ("query" in d && user_ids) if ("query" in d && user_ids)
throw new Error('"query" and "user_ids" are mutually exclusive'); 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? // TODO: Configurable limit?
if ((query || (user_ids && user_ids.length > 0)) && (!limit || limit > 100)) if ((query || (user_ids && user_ids.length > 0)) && (!limit || limit > 100))
limit = 100; limit = 100;
const permissions = await getPermission( const permissions = await getPermission(this.user_id, guild_id);
this.user_id,
Array.isArray(guild_id) ? guild_id[0] : guild_id,
);
permissions.hasThrow("VIEW_CHANNEL"); permissions.hasThrow("VIEW_CHANNEL");
const whereQuery: FindManyOptions["where"] = {}; const memberCount = await Member.count({
if (query) { where: {
whereQuery.user = { guild_id,
username: Like(query + "%"), },
}; });
} else if (user_ids && user_ids.length > 0) {
whereQuery.id = In(user_ids);
}
const memberFind: FindManyOptions = { const memberFind: FindManyOptions = {
where: { where: {
...whereQuery, guild_id,
guild_id: Array.isArray(guild_id) ? guild_id[0] : guild_id,
}, },
relations: ["user", "roles"], relations: ["user", "roles"],
}; };
if (limit) memberFind.take = Math.abs(Number(limit || 100)); 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 = { const baseData = {
guild_id: Array.isArray(guild_id) ? guild_id[0] : guild_id, guild_id,
nonce, 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) => { chunks.forEach((chunk) => {
Send(this, { Send(this, {

View File

@ -1,17 +1,17 @@
/* /*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify 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 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 by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
@ -43,4 +43,5 @@ export interface WebSocket extends WS {
listen_options: ListenEventOpts; listen_options: ListenEventOpts;
capabilities?: Capabilities; capabilities?: Capabilities;
// client?: Client; // client?: Client;
large_threshold: number;
} }

View File

@ -26,7 +26,7 @@ export interface RequestGuildMembersSchema {
} }
export const RequestGuildMembersSchema = { export const RequestGuildMembersSchema = {
guild_id: [] as string | string[], guild_id: "" as string | string[],
$query: String, $query: String,
$limit: Number, $limit: Number,
$presences: Boolean, $presences: Boolean,