diff --git a/packages/api/src/routes/dash/server.ts b/packages/api/src/routes/dash/server.ts index 8f56e39..cba1b5c 100644 --- a/packages/api/src/routes/dash/server.ts +++ b/packages/api/src/routes/dash/server.ts @@ -16,12 +16,10 @@ type ServerDetails = { serverConfig: any; users: User[]; channels: Channel[]; - memberCount: number | null; channelCount: number; ownerName?: string; createdAt: number; roleCount: number; - botCount: number | null; dmOnKick?: boolean; dmOnWarn?: boolean; }; diff --git a/packages/api/src/routes/dash/servers.ts b/packages/api/src/routes/dash/servers.ts index c8cd277..341e26c 100644 --- a/packages/api/src/routes/dash/servers.ts +++ b/packages/api/src/routes/dash/servers.ts @@ -9,12 +9,10 @@ type Server = { name: string; iconURL?: string; bannerURL?: string; - memberCount: number | null; channelCount: number; ownerName?: string; createdAt: number; roleCount: number; - botCount: number | null; }; app.get("/dash/servers", requireAuth({ requireLogin: true }), async (req: Request, res: Response) => { diff --git a/packages/bot/src/bot/modules/api/server_details.ts b/packages/bot/src/bot/modules/api/server_details.ts index 85186b6..40a8cd0 100644 --- a/packages/bot/src/bot/modules/api/server_details.ts +++ b/packages/bot/src/bot/modules/api/server_details.ts @@ -20,12 +20,10 @@ type ServerDetails = { serverConfig?: ServerConfig; users: APIUser[]; channels: APIChannel[]; - memberCount: number | null; channelCount: number; ownerName?: string; createdAt: number; roleCount: number; - botCount: number | null; dmOnKick?: boolean; dmOnBan?: boolean; dmOnWarn?: boolean; @@ -65,13 +63,8 @@ wsEvents.on("req:getUserServerDetails", async (data: ReqData, cb: (data: WSRespo } } - const [users, membersData] = await Promise.all([ - Promise.allSettled([...(serverConfig?.botManagers?.map((u) => fetchUser(u)) ?? []), ...(serverConfig?.moderators?.map((u) => fetchUser(u)) ?? []), fetchUser(user.id)]), - server.fetchMembers().catch(() => null), - ]); + const users = await Promise.allSettled([...(serverConfig?.botManagers?.map((u) => fetchUser(u)) ?? []), ...(serverConfig?.moderators?.map((u) => fetchUser(u)) ?? []), fetchUser(user.id)]); - const members = membersData?.members ?? null; - const memberUsers = membersData?.users ?? null; const channels = server.channels.filter((c) => c != undefined); const response: ServerDetails = { @@ -90,12 +83,10 @@ wsEvents.on("req:getUserServerDetails", async (data: ReqData, cb: (data: WSRespo type: "TEXT", icon: c!.iconURL, })), - memberCount: members ? members.length : null, channelCount: channels.length, - ownerName: server.owner?.username ?? undefined, + ownerName: (() => { const o = server.owner; return o ? o.username : server.ownerId; })(), createdAt: server.createdAt.getTime(), roleCount: server.roles?.size ?? 0, - botCount: members && memberUsers ? members.filter((m: any) => memberUsers.find((u: any) => u._id === (m._id?.user ?? m._id))?.bot).length : null, dmOnKick: serverConfig?.dmOnKick, dmOnBan: serverConfig?.dmOnBan, dmOnWarn: serverConfig?.dmOnWarn, diff --git a/packages/bot/src/bot/modules/api/servers.ts b/packages/bot/src/bot/modules/api/servers.ts index 017338e..675e1a8 100644 --- a/packages/bot/src/bot/modules/api/servers.ts +++ b/packages/bot/src/bot/modules/api/servers.ts @@ -20,12 +20,10 @@ wsEvents.on("req:getUserServers", async (data: ReqData, cb: (data: WSResponse) = name: string; iconURL?: string; bannerURL?: string; - memberCount: number | null; channelCount: number; ownerName?: string; createdAt: number; roleCount: number; - botCount: number | null; }; const promises: Promise[] = []; @@ -42,12 +40,10 @@ wsEvents.on("req:getUserServers", async (data: ReqData, cb: (data: WSResponse) = name: server.name, bannerURL: server.bannerURL, iconURL: server.iconURL, - memberCount: null, // too expensive to fetch per-server in list; detail page has accurate count channelCount: server.channels.filter((c) => c != null).length, - ownerName: server.owner?.username ?? undefined, + ownerName: (() => { const o = server.owner; return o ? o.username : server.ownerId; })(), createdAt: server.createdAt.getTime(), roleCount: server.roles?.size ?? 0, - botCount: null, }); } catch (e) { console.error(e); diff --git a/packages/bot/src/bot/util.ts b/packages/bot/src/bot/util.ts index c32a1c6..8f4fa37 100644 --- a/packages/bot/src/bot/util.ts +++ b/packages/bot/src/bot/util.ts @@ -88,7 +88,8 @@ async function getPermissionLevel(member: ServerMember | User, server: Server): } if (isSudo(serverMember.user!)) return 3; - if (serverMember.hasPermission(server, "ManageServer")) return 3; + if (server.ownerId === serverMember.id.user) return 3; + if (serverMember.hasPermission(server, "ManageServer")) return 2; const config = await dbs.SERVERS.findOne({ id: server.id }); @@ -99,7 +100,8 @@ async function getPermissionLevel(member: ServerMember | User, server: Server): } function getPermissionBasedOnRole(member: ServerMember): 0 | 1 | 2 | 3 { - if (member.hasPermission(member.server!, "ManageServer")) return 3; + if (member.server && member.server.ownerId === member.id.user) return 3; + if (member.hasPermission(member.server!, "ManageServer")) return 2; if (member.hasPermission(member.server!, "KickMembers")) return 1; return 0; } diff --git a/packages/bot/src/stoat/structures/Server.ts b/packages/bot/src/stoat/structures/Server.ts index e2c9dde..2949651 100644 --- a/packages/bot/src/stoat/structures/Server.ts +++ b/packages/bot/src/stoat/structures/Server.ts @@ -140,8 +140,24 @@ export class Server { async fetchMembers() { const data: any = await this.client.api.get(`/servers/${this.id}/members`); - for (const user of data.users) this.client.users.getOrCreate(user._id, user); - for (const member of data.members) this.client.serverMembers.getOrCreate(member._id, member); + + // Process in chunks to avoid blocking the event loop. + // Synchronous processing would prevent the bot from responding to WebSocket messages for big servers. + const CHUNK = 500; + const users = data.users; + for (let i = 0; i < users.length; i += CHUNK) { + const slice = users.slice(i, i + CHUNK); + for (const user of slice) this.client.users.getOrCreate(user._id, user); + await new Promise((r) => setTimeout(r, 0)); + } + + const members = data.members; + for (let i = 0; i < members.length; i += CHUNK) { + const slice = members.slice(i, i + CHUNK); + for (const member of slice) this.client.serverMembers.getOrCreate(member._id, member); + await new Promise((r) => setTimeout(r, 0)); + } + return data; } } diff --git a/packages/web/static/js/helpers.js b/packages/web/static/js/helpers.js index a9d8096..9a940cf 100644 --- a/packages/web/static/js/helpers.js +++ b/packages/web/static/js/helpers.js @@ -74,16 +74,14 @@ const ACTIONS = { 0: "Delete", 1: "Message", 2: "Warn", 3: "Kick", 4: "Ban" }; function fmtServerStats(s) { const parts = []; - if (s.memberCount != null) parts.push(`${s.memberCount} members`); if (s.channelCount != null) parts.push(`${s.channelCount} channels`); if (s.roleCount != null) parts.push(`${s.roleCount} roles`); - if (s.botCount != null) parts.push(`${s.botCount} bots`); return parts.join(" ยท "); } function fmtServerSub(s) { const parts = []; - if (s.ownerName) parts.push(`Owned by @${escHtml(s.ownerName)}`); + if (s.ownerName) parts.push(`Owned by ${escHtml(s.ownerName)}`); if (s.createdAt) { const d = new Date(s.createdAt); parts.push(`Created ${d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" })}`);