summary refs log tree commit diff
path: root/gateway/src/opcodes
diff options
context:
space:
mode:
Diffstat (limited to 'gateway/src/opcodes')
-rw-r--r--gateway/src/opcodes/Identify.ts116
-rw-r--r--gateway/src/opcodes/LazyRequest.ts132
-rw-r--r--gateway/src/opcodes/PresenceUpdate.ts24
3 files changed, 194 insertions, 78 deletions
diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts

index 006dc83c..bd7fc894 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts
@@ -13,6 +13,11 @@ import { PrivateUserProjection, ReadState, Application, + emitEvent, + SessionsReplace, + PrivateSessionProjection, + MemberPrivateProjection, + PresenceUpdateEvent, } from "@fosscord/util"; import { Send } from "../util/Send"; import { CLOSECODES, OPCODES } from "../util/Constants"; @@ -43,11 +48,56 @@ export async function onIdentify(this: WebSocket, data: Payload) { } this.user_id = decoded.id; - const user = await User.findOneOrFail({ - where: { id: this.user_id }, - relations: ["relationships", "relationships.to"], - select: [...PrivateUserProjection, "relationships"], - }); + const session_id = genSessionId(); + this.session_id = session_id; //Set the session of the WebSocket object + + const [user, read_states, members, recipients, session, application] = + await Promise.all([ + User.findOneOrFail({ + where: { id: this.user_id }, + relations: ["relationships", "relationships.to"], + select: [...PrivateUserProjection, "relationships"], + }), + ReadState.find({ user_id: this.user_id }), + Member.find({ + where: { id: this.user_id }, + select: MemberPrivateProjection, + relations: [ + "guild", + "guild.channels", + "guild.emojis", + "guild.emojis.user", + "guild.roles", + "guild.stickers", + "user", + "roles", + ], + }), + Recipient.find({ + where: { user_id: this.user_id, closed: false }, + relations: [ + "channel", + "channel.recipients", + "channel.recipients.user", + ], + // TODO: public user selection + }), + // save the session and delete it when the websocket is closed + new Session({ + user_id: this.user_id, + session_id: session_id, + // TODO: check if status is only one of: online, dnd, offline, idle + status: identify.presence?.status || "online", //does the session always start as online? + client_info: { + //TODO read from identity + client: "desktop", + os: identify.properties?.os, + version: 0, + }, + }).save(), + Application.findOne({ id: this.user_id }), + ]); + if (!user) return this.close(CLOSECODES.Authentication_failed); if (!identify.intents) identify.intents = BigInt("0b11111111111111"); @@ -68,19 +118,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { } var users: PublicUser[] = []; - const members = await Member.find({ - where: { id: this.user_id }, - relations: [ - "guild", - "guild.channels", - "guild.emojis", - "guild.emojis.user", - "guild.roles", - "guild.stickers", - "user", - "roles", - ], - }); const merged_members = members.map((x: Member) => { return [ { @@ -112,11 +149,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { const user_guild_settings_entries = members.map((x) => x.settings); - const recipients = await Recipient.find({ - where: { user_id: this.user_id, closed: false }, - relations: ["channel", "channel.recipients", "channel.recipients.user"], - // TODO: public user selection - }); const channels = recipients.map((x) => { // @ts-ignore x.channel.recipients = x.channel.recipients?.map((x) => x.user); @@ -144,24 +176,28 @@ export async function onIdentify(this: WebSocket, data: Payload) { users.push(public_related_user); } - const session_id = genSessionId(); - this.session_id = session_id; //Set the session of the WebSocket object - const session = new Session({ - user_id: this.user_id, - session_id: session_id, - status: "online", //does the session always start as online? - client_info: { - //TODO read from identity - client: "desktop", - os: "linux", - version: 0, - }, + setImmediate(async () => { + // run in seperate "promise context" because ready payload is not dependent on those events + emitEvent({ + event: "SESSIONS_REPLACE", + user_id: this.user_id, + data: await Session.find({ + where: { user_id: this.user_id }, + select: PrivateSessionProjection, + }), + } as SessionsReplace); + emitEvent({ + event: "PRESENCE_UPDATE", + user_id: this.user_id, + data: { + user: await User.getPublicUser(this.user_id), + activities: session.activities, + client_status: session?.client_info, + status: session.status, + }, + } as PresenceUpdateEvent); }); - //We save the session and we delete it when the websocket is closed - await session.save(); - - const read_states = await ReadState.find({ user_id: this.user_id }); read_states.forEach((s: any) => { s.id = s.channel_id; delete s.user_id; @@ -192,7 +228,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { const d: ReadyEventData = { v: 8, - application: await Application.findOne({ id: this.user_id }), + application, user: privateUser, user_settings: user.settings, // @ts-ignore diff --git a/gateway/src/opcodes/LazyRequest.ts b/gateway/src/opcodes/LazyRequest.ts
index f5fd561a..c304dfe7 100644 --- a/gateway/src/opcodes/LazyRequest.ts +++ b/gateway/src/opcodes/LazyRequest.ts
@@ -1,46 +1,55 @@ import { + EVENTEnum, + EventOpts, getPermission, + listenEvent, Member, - PublicMemberProjection, Role, } from "@fosscord/util"; import { LazyRequest } from "../schema/LazyRequest"; import { Send } from "../util/Send"; import { OPCODES } from "../util/Constants"; -import { WebSocket, Payload } from "@fosscord/gateway"; +import { WebSocket, Payload, handlePresenceUpdate } from "@fosscord/gateway"; import { check } from "./instanceOf"; import "missing-native-js-functions"; +import { getRepository } from "typeorm"; +import "missing-native-js-functions"; -// TODO: check permission and only show roles/members that have access to this channel +// 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 -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 permissions = await getPermission(this.user_id, guild_id); - permissions.hasThrow("VIEW_CHANNEL"); - - var members = await Member.find({ - where: { guild_id: guild_id }, - relations: ["roles", "user"], - select: PublicMemberProjection, - }); +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 - const roles = await Role.find({ - where: { guild_id: guild_id }, - order: { - position: "DESC", - }, - }); + let members = await 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( + "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(); const groups = [] as any[]; - var member_count = 0; const items = []; + const member_roles = members + .map((m) => m.roles) + .flat() + .unique((r) => r.id); - for (const role of roles) { + for (const role of member_roles) { // @ts-ignore const [role_members, other_members] = partition(members, (m: Member) => m.roles.find((r) => r.id === role.id) @@ -54,35 +63,86 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { groups.push(group); for (const member of role_members) { - member.roles = member.roles.filter((x: Role) => x.id !== guild_id); + const roles = member.roles + .filter((x: Role) => x.id !== guild_id) + .map((x: Role) => x.id); + + const session = member.user.sessions.first(); + + // TODO: properly mock/hide offline/invisible status items.push({ member: { ...member, - roles: member.roles.map((x: Role) => x.id), + roles, + user: { ...member.user, sessions: undefined }, + presence: { + ...session, + activities: session?.activities || [], + user: { id: member.user.id }, + }, }, }); } members = other_members; - member_count += role_members.length; } + return { + items, + groups, + range, + members: items.map((x) => x.member).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({ 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 (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 + ); + }); + }); + return Send(this, { op: OPCODES.Dispatch, s: this.sequence++, t: "GUILD_MEMBER_LIST_UPDATE", d: { - ops: [ - { - range: [0, 99], - op: "SYNC", - items, - }, - ], - online_count: member_count, // TODO count online count + ops: ops.map((x) => ({ + items: x.items, + op: "SYNC", + range: x.range, + })), + online_count: member_count, member_count, id: "everyone", guild_id, - groups, + groups: ops + .map((x) => x.groups) + .flat() + .unique(), }, }); } diff --git a/gateway/src/opcodes/PresenceUpdate.ts b/gateway/src/opcodes/PresenceUpdate.ts
index 53d7b9d2..415df6ee 100644 --- a/gateway/src/opcodes/PresenceUpdate.ts +++ b/gateway/src/opcodes/PresenceUpdate.ts
@@ -1,5 +1,25 @@ import { WebSocket, Payload } from "@fosscord/gateway"; +import { emitEvent, PresenceUpdateEvent, Session, User } from "@fosscord/util"; +import { ActivitySchema } from "../schema/Activity"; +import { check } from "./instanceOf"; -export function onPresenceUpdate(this: WebSocket, data: Payload) { - // return this.close(CLOSECODES.Unknown_error); +export async function onPresenceUpdate(this: WebSocket, { d }: Payload) { + check.call(this, ActivitySchema, d); + const presence = d as ActivitySchema; + + await Session.update( + { session_id: this.session_id }, + { status: presence.status, activities: presence.activities } + ); + + await emitEvent({ + event: "PRESENCE_UPDATE", + user_id: this.user_id, + data: { + user: await User.getPublicUser(this.user_id), + activities: presence.activities, + client_status: {}, // TODO: + status: presence.status, + }, + } as PresenceUpdateEvent); }