diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 111325b..3aa91b9 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -291,35 +291,33 @@ const userMetaQueryTime = taskSw.getElapsedAndReset(); - const { result: memberGuilds, elapsed: queryGuildsTime } = await timePromise(() => - Promise.all( - members.map((m) => - Guild.findOneOrFail({ - where: { id: m.guild_id }, - select: Object.fromEntries( - getDatabase()! - .getMetadata(Guild) - .columns.map((x) => [x.propertyName, true]), - ), - }), - ), - ), - ); - - const guildIds = memberGuilds.map((g) => g.id); + const memberGuildIds = members.map((m) => m.guild_id); // select relations const [ + { result: memberGuilds, elapsed: queryGuildsTime }, { result: memberGuildChannels, elapsed: queryGuildChannelsTime }, { result: memberGuildEmojis, elapsed: queryGuildEmojisTime }, { result: memberGuildRoles, elapsed: queryGuildRolesTime }, { result: memberGuildStickers, elapsed: queryGuildStickersTime }, { result: memberGuildVoiceStates, elapsed: queryGuildVoiceStatesTime }, + { result: threadMembers, elapsed: threadMemberTime }, + { result: allThreadsRaw, elapsed: queryThreadsTime }, ] = await Promise.all([ timePromise(() => + Guild.find({ + where: { id: In(memberGuildIds) }, + select: Object.fromEntries( + getDatabase()! + .getMetadata(Guild) + .columns.map((x) => [x.propertyName, true]), + ), + }), + ), + timePromise(() => Channel.find({ where: { - guild_id: In(guildIds), + guild_id: In(memberGuildIds), type: Not(In([ChannelType.GUILD_PUBLIC_THREAD, ChannelType.GUILD_PRIVATE_THREAD, ChannelType.GUILD_NEWS_THREAD])), }, order: { guild_id: "ASC" }, @@ -328,34 +326,93 @@ ), timePromise(() => Emoji.find({ - where: { guild_id: In(guildIds) }, + where: { guild_id: In(memberGuildIds) }, order: { guild_id: "ASC" }, }), ), timePromise(() => Role.find({ - where: { guild_id: In(guildIds) }, + where: { guild_id: In(memberGuildIds) }, order: { guild_id: "ASC" }, }), ), timePromise(() => Sticker.find({ - where: { guild_id: In(guildIds) }, + where: { guild_id: In(memberGuildIds) }, order: { guild_id: "ASC" }, }), ), timePromise(() => VoiceState.find({ - where: { guild_id: In(guildIds) }, + where: { guild_id: In(memberGuildIds) }, order: { guild_id: "ASC" }, }), ), + timePromise(() => + ThreadMember.find({ + where: { member_idx: In(members.map(({ index }) => index)) }, + }), + ), + timePromise(() => + Channel.find({ + where: { + type: In([ChannelType.GUILD_NEWS_THREAD, ChannelType.GUILD_PUBLIC_THREAD]), + guild_id: In(memberGuildIds), + }, + }), + ), ]); + const guildIds = memberGuilds.map((g) => g.id); + + const allThreads = allThreadsRaw.filter(({ thread_metadata }) => thread_metadata?.archived === false); + const threadMemberMap = new Map(threadMembers.map((member) => [member.id, member] as const)); + + const channelsByGuild = new Map(); + const emojisByGuild = new Map(); + const rolesByGuild = new Map(); + const stickersByGuild = new Map(); + const voiceStatesByGuild = new Map(); + const threadsByGuild = new Map(); + + for (const c of memberGuildChannels) { + const arr = channelsByGuild.get(c.guild_id!); + if (arr) arr.push(c); + else channelsByGuild.set(c.guild_id!, [c]); + } + for (const e of memberGuildEmojis) { + const arr = emojisByGuild.get(e.guild_id!); + if (arr) arr.push(e); + else emojisByGuild.set(e.guild_id!, [e]); + } + for (const r of memberGuildRoles) { + const arr = rolesByGuild.get(r.guild_id!); + if (arr) arr.push(r); + else rolesByGuild.set(r.guild_id!, [r]); + } + for (const s of memberGuildStickers) { + const arr = stickersByGuild.get(s.guild_id!); + if (arr) arr.push(s); + else stickersByGuild.set(s.guild_id!, [s]); + } + for (const v of memberGuildVoiceStates) { + const arr = voiceStatesByGuild.get(v.guild_id!); + if (arr) arr.push(v); + else voiceStatesByGuild.set(v.guild_id!, [v]); + } + for (const t of allThreads) { + const arr = threadsByGuild.get(t.guild_id!); + if (arr) arr.push(t); + else threadsByGuild.set(t.guild_id!, [t]); + } + + const guildMap = new Map(memberGuilds.map((g) => [g.id, g])); + const mergeMemberGuildsTrace: TraceNode = { micros: 0, calls: [], }; + members.forEach((m) => { const sw = Stopwatch.startNew(); const totalSw = Stopwatch.startNew(); @@ -364,32 +421,26 @@ calls: [], }; - const g = memberGuilds.find((mg) => mg.id === m.guild_id); + const g = guildMap.get(m.guild_id); if (g) { m.guild = g; trace.calls.push("findGuild", { micros: sw.getElapsedAndReset().totalMicroseconds }); - //channels - g.channels = memberGuildChannels.filter((c) => c.guild_id === m.guild_id); + g.channels = channelsByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterChannels(${g.channels.length}/${memberGuildChannels.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //emojis - g.emojis = memberGuildEmojis.filter((e) => e.guild_id === m.guild_id); + g.emojis = emojisByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterEmojis(${g.emojis.length}/${memberGuildEmojis.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //roles - g.roles = memberGuildRoles.filter((r) => r.guild_id === m.guild_id); + g.roles = rolesByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterRoles(${g.roles.length}/${memberGuildRoles.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //stickers - g.stickers = memberGuildStickers.filter((s) => s.guild_id === m.guild_id); + g.stickers = stickersByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterStickers(${g.stickers.length}/${memberGuildStickers.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //voice states - g.voice_states = memberGuildVoiceStates.filter((v) => v.guild_id === m.guild_id); + g.voice_states = voiceStatesByGuild.get(m.guild_id) ?? []; trace.calls.push(`filterVoiceStates(${g.voice_states.length}/${memberGuildVoiceStates.length})`, { micros: sw.getElapsedAndReset().totalMicroseconds }); - //total trace.micros = totalSw.elapsed().totalMicroseconds; mergeMemberGuildsTrace.calls!.push(`guild_${m.guild_id}`, trace); } else { @@ -435,31 +486,15 @@ ]; }); const mergedMembersTime = taskSw.getElapsedAndReset(); - const member_idx = members.map(({ index }) => index); - - const threadMembers = await ThreadMember.find({ - where: { member_idx: In(member_idx) }, - }); - const threadMemberMap = new Map(threadMembers.map((member) => [member.id, member] as const)); - const threadMemberTime = taskSw.getElapsedAndReset(); // Populated with guilds 'unavailable' currently // Just for bots //TODO get this a better type const pending_guilds: { id: string }[] = []; - const allThreads = ( - await Channel.find({ - where: { - type: In([ChannelType.GUILD_NEWS_THREAD, ChannelType.GUILD_PUBLIC_THREAD]), - guild_id: In(members.map(({ guild }) => guild.id)), - }, - }) - ).filter(({ thread_metadata }) => thread_metadata?.archived === false); - // Generate guilds list ( make them unavailable if user is bot ) const guilds: GuildOrUnavailable[] = members.map((member) => { - member.guild.channels = member.guild.channels + member.guild.channels = (channelsByGuild.get(member.guild_id) ?? []) /* //TODO maybe implement this correctly, by causing create and delete events for users who can newly view and not view the channels, along with doing these checks correctly, as they don't currently take into account that the owner of the guild is always able to view channels, with potentially other issues .filter((channel) => { @@ -481,7 +516,7 @@ }) .sort((a, b) => a.position - b.position); - const threads: Channel[] = allThreads.filter((_) => _.guild_id === member.guild_id); + const threads: Channel[] = threadsByGuild.get(member.guild_id) ?? []; const guildjson = { ...member.guild.toJSON(), @@ -512,10 +547,7 @@ ...DefaultUserGuildSettings, ...x.settings, guild_id: x.guild_id, - channel_overrides: Object.entries(x.settings.channel_overrides ?? {}).map((y) => ({ - ...y[1], - channel_id: y[0], - })), + channel_overrides: x.settings.channel_overrides ? Object.entries(x.settings.channel_overrides).map(([k, v]) => ({ ...v, channel_id: k })) : [], })); const generateUserGuildSettingsTime = taskSw.getElapsedAndReset(); @@ -750,6 +782,8 @@ queryGuildRolesTime, queryGuildStickersTime, queryGuildVoiceStatesTime, + threadMemberTime, + queryThreadsTime, })) { if (subvalue) { val.calls.push(subkey, { diff --git a/src/gateway/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts index cba2c89..fbb249d 100644 --- a/src/gateway/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts @@ -31,7 +31,8 @@ if (d.user_ids && !Array.isArray(d.user_ids)) d.user_ids = [d.user_ids]; if (Config.get().offload.gateway.guildMembersUrl !== null) { - return await handleOffloadedGatewayRequest(this, Config.get().offload.gateway.guildMembersUrl!, d); + const guildIds: string[] = Array.isArray(d.guild_id) ? d.guild_id : [d.guild_id]; + return await handleOffloadedGatewayRequest(this, Config.get().offload.gateway.guildMembersUrl!, guildIds); } check.call(this, RequestGuildMembersSchema, d); diff --git a/src/gateway/util/Utils.ts b/src/gateway/util/Utils.ts index 1ca45ee..c54c498 100644 --- a/src/gateway/util/Utils.ts +++ b/src/gateway/util/Utils.ts @@ -71,6 +71,7 @@ Authorization: `Bearer ${socket.accessToken}`, // because the session may not have an id in the token! "X-Session-Id": socket.session_id, + "Content-Type": "application/json", }, });