summary refs log tree commit diff
path: root/src/gateway/opcodes/LazyRequest.ts
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-09-25 18:24:21 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-09-25 23:35:18 +1000
commit0d23eaba09a4878520bf346af4cead90d76829fc (patch)
treed930eacceff0b407b44abe55f01d8e3c5dfbfa34 /src/gateway/opcodes/LazyRequest.ts
parentAllow edited_timestamp to passthrough in handleMessage (diff)
downloadserver-0d23eaba09a4878520bf346af4cead90d76829fc.tar.xz
Refactor to mono-repo + upgrade packages
Diffstat (limited to 'src/gateway/opcodes/LazyRequest.ts')
-rw-r--r--src/gateway/opcodes/LazyRequest.ts205
1 files changed, 205 insertions, 0 deletions
diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts
new file mode 100644
index 00000000..82342224
--- /dev/null
+++ b/src/gateway/opcodes/LazyRequest.ts
@@ -0,0 +1,205 @@
+import { getDatabase, getPermission, listenEvent, Member, Role, Session } from "@fosscord/util";
+import { WebSocket, Payload, handlePresenceUpdate, OPCODES, Send } from "@fosscord/gateway";
+import { LazyRequest } from "../schema/LazyRequest";
+import { check } from "./instanceOf";
+
+// TODO: only show roles/members that have access to this channel
+// TODO: config: to list all members (even those who are offline) sorted by role, or just those who are online
+// TODO: rewrite typeorm
+
+async function getMembers(guild_id: string, range: [number, number]) {
+	if (!Array.isArray(range) || range.length !== 2) {
+		throw new Error("range is not a valid array");
+	}
+	// TODO: wait for typeorm to implement ordering for .find queries https://github.com/typeorm/typeorm/issues/2620
+
+	let members: Member[] = [];
+	try {
+		members = await getDatabase()!.getRepository(Member)
+			.createQueryBuilder("member")
+			.where("member.guild_id = :guild_id", { guild_id })
+			.leftJoinAndSelect("member.roles", "role")
+			.leftJoinAndSelect("member.user", "user")
+			.leftJoinAndSelect("user.sessions", "session")
+			.addSelect("user.settings")
+			.addSelect(
+				"CASE WHEN session.status = 'offline' THEN 0 ELSE 1 END",
+				"_status"
+			)
+			.orderBy("role.position", "DESC")
+			.addOrderBy("_status", "DESC")
+			.addOrderBy("user.username", "ASC")
+			.offset(Number(range[0]) || 0)
+			.limit(Number(range[1]) || 100)
+			.getMany();
+	}
+	catch (e) {
+		console.error(`LazyRequest`, e);
+	}
+
+	if (!members) {
+		return {
+			items: [],
+			groups: [],
+			range: [],
+			members: [],
+		};
+	}
+
+	const groups = [] as any[];
+	const items = [];
+	const member_roles = members
+		.map((m) => m.roles)
+		.flat()
+		.unique((r: Role) => r.id);
+	member_roles.push(member_roles.splice(member_roles.findIndex(x => x.id === x.guild_id), 1)[0]);
+
+	const offlineItems = [];
+
+	for (const role of member_roles) {
+		// @ts-ignore
+		const [role_members, other_members]: Member[][] = partition(members, (m: Member) =>
+			m.roles.find((r) => r.id === role.id)
+		);
+		const group = {
+			count: role_members.length,
+			id: role.id === guild_id ? "online" : role.id,
+		};
+
+		items.push({ group });
+		groups.push(group);
+
+		for (const member of role_members) {
+			const roles = member.roles
+				.filter((x: Role) => x.id !== guild_id)
+				.map((x: Role) => x.id);
+
+			const statusMap = {
+				"online": 0,
+				"idle": 1,
+				"dnd": 2,
+				"invisible": 3,
+				"offline": 4,
+			};
+			// sort sessions by relevance
+			const sessions = member.user.sessions.sort((a, b) => {
+				return (statusMap[a.status] - statusMap[b.status]) + ((a.activities.length - b.activities.length) * 2);
+			});
+			var session: Session | undefined = sessions.first();
+
+			if (session?.status == "offline") {
+				session.status = member.user.settings.status || "online";
+			}
+
+			const item = {
+				member: {
+					...member,
+					roles,
+					user: { ...member.user, sessions: undefined },
+					presence: {
+						...session,
+						activities: session?.activities || [],
+						user: { id: member.user.id },
+					},
+				},
+			};
+
+			if (!session || session.status == "invisible" || session.status == "offline") {
+				item.member.presence.status = "offline";
+				offlineItems.push(item);
+				group.count--;
+				continue;
+			}
+
+			items.push(item);
+		}
+		members = other_members;
+	}
+
+	if (offlineItems.length) {
+		const group = {
+			count: offlineItems.length,
+			id: "offline",
+		};
+		items.push({ group });
+		groups.push(group);
+
+		items.push(...offlineItems);
+	}
+
+	return {
+		items,
+		groups,
+		range,
+		members: items.map((x) => 'member' in x ? x.member : undefined).filter(x => !!x),
+	};
+}
+
+export async function onLazyRequest(this: WebSocket, { d }: Payload) {
+	// TODO: check data
+	check.call(this, LazyRequest, d);
+	const { guild_id, typing, channels, activities } = d as LazyRequest;
+
+	const channel_id = Object.keys(channels || {}).first();
+	if (!channel_id) return;
+
+	const permissions = await getPermission(this.user_id, guild_id, channel_id);
+	permissions.hasThrow("VIEW_CHANNEL");
+
+	const ranges = channels![channel_id];
+	if (!Array.isArray(ranges)) throw new Error("Not a valid Array");
+
+	const member_count = await Member.count({ where: { guild_id } });
+	const ops = await Promise.all(ranges.map((x) => getMembers(guild_id, x)));
+
+	// TODO: unsubscribe member_events that are not in op.members
+
+	ops.forEach((op) => {
+		op.members.forEach(async (member) => {
+			if (!member) return;
+			if (this.events[member.user.id]) return; // already subscribed as friend
+			if (this.member_events[member.user.id]) return; // already subscribed in member list
+			this.member_events[member.user.id] = await listenEvent(
+				member.user.id,
+				handlePresenceUpdate.bind(this),
+				this.listen_options
+			);
+		});
+	});
+
+	const groups = ops
+		.map((x) => x.groups)
+		.flat()
+		.unique();
+
+	return await Send(this, {
+		op: OPCODES.Dispatch,
+		s: this.sequence++,
+		t: "GUILD_MEMBER_LIST_UPDATE",
+		d: {
+			ops: ops.map((x) => ({
+				items: x.items,
+				op: "SYNC",
+				range: x.range,
+			})),
+			online_count: member_count - (groups.find(x => x.id == "offline")?.count ?? 0),
+			member_count,
+			id: "everyone",
+			guild_id,
+			groups,
+		},
+	});
+}
+
+function partition<T>(array: T[], isValid: Function) {
+	// @ts-ignore
+	return array.reduce(
+		// @ts-ignore
+		([pass, fail], elem) => {
+			return isValid(elem)
+				? [[...pass, elem], fail]
+				: [pass, [...fail, elem]];
+		},
+		[[], []]
+	);
+}