From 74bd98737acf89d9d56ee66a68694d82f591d591 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 10 Oct 2021 11:03:32 +0200 Subject: :art: clean up imports + classes --- gateway/src/opcodes/LazyRequest.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/opcodes/LazyRequest.ts b/gateway/src/opcodes/LazyRequest.ts index d37e32da..f5fd561a 100644 --- a/gateway/src/opcodes/LazyRequest.ts +++ b/gateway/src/opcodes/LazyRequest.ts @@ -41,6 +41,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { const items = []; for (const role of roles) { + // @ts-ignore const [role_members, other_members] = partition(members, (m: Member) => m.roles.find((r) => r.id === role.id) ); @@ -53,9 +54,12 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { groups.push(group); for (const member of role_members) { - member.roles = member.roles.filter((x) => x.id !== guild_id); + member.roles = member.roles.filter((x: Role) => x.id !== guild_id); items.push({ - member: { ...member, roles: member.roles.map((x) => x.id) }, + member: { + ...member, + roles: member.roles.map((x: Role) => x.id), + }, }); } members = other_members; @@ -84,7 +88,9 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { } function partition(array: T[], isValid: Function) { + // @ts-ignore return array.reduce( + // @ts-ignore ([pass, fail], elem) => { return isValid(elem) ? [[...pass, elem], fail] -- cgit 1.5.1 From 57c9813f2fca9b8f4e02f09d71515bc7f59b4010 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 10 Oct 2021 11:08:55 +0200 Subject: :bug: convert bigint literal to object --- api/src/routes/channels/#channel_id/permissions.ts | 4 ++-- api/src/routes/guilds/#guild_id/roles.ts | 9 +++++++-- api/src/routes/guilds/templates/index.ts | 2 +- bundle/package.json | 2 +- gateway/src/opcodes/Identify.ts | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 6ebf721a..2eded853 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -44,8 +44,8 @@ router.put( }; channel.permission_overwrites!.push(overwrite); } - overwrite.allow = String(req.permission!.bitfield & (BigInt(body.allow) || 0n)); - overwrite.deny = String(req.permission!.bitfield & (BigInt(body.deny) || 0n)); + overwrite.allow = String(req.permission!.bitfield & (BigInt(body.allow) || BigInt("0"))); + overwrite.deny = String(req.permission!.bitfield & (BigInt(body.deny) || BigInt("0"))); await Promise.all([ channel.save(), diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index d1d60906..0a57c6a2 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -57,7 +57,7 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }) ...body, guild_id: guild_id, managed: false, - permissions: String(req.permission!.bitfield & (body.permissions || 0n)), + permissions: String(req.permission!.bitfield & (body.permissions || BigInt("0"))), tags: undefined }); @@ -105,7 +105,12 @@ router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ const { role_id, guild_id } = req.params; const body = req.body as RoleModifySchema; - const role = new Role({ ...body, id: role_id, guild_id, permissions: String(req.permission!.bitfield & (body.permissions || 0n)) }); + const role = new Role({ + ...body, + id: role_id, + guild_id, + permissions: String(req.permission!.bitfield & (body.permissions || BigInt("0"))) + }); await Promise.all([ role.save(), diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index b5e243e9..86316d23 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -47,7 +47,7 @@ router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: managed: true, mentionable: true, name: "@everyone", - permissions: 2251804225n, + permissions: BigInt("2251804225"), position: 0, tags: null }).save() diff --git a/bundle/package.json b/bundle/package.json index 05cefaab..3bed5b07 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -94,4 +94,4 @@ "ws": "^7.4.2", "cheerio": "^1.0.0-rc.10" } -} +} \ No newline at end of file diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index b81c7bf4..5d5b72d1 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -41,7 +41,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { return this.close(CLOSECODES.Authentication_failed); } this.user_id = decoded.id; - if (!identify.intents) identify.intents = 0b11111111111111n; + if (!identify.intents) identify.intents = BigInt("0b11111111111111"); this.intents = new Intents(identify.intents); if (identify.shard) { this.shard_id = identify.shard[0]; -- cgit 1.5.1 From 404d9e40d1bfb05a8670c0f0879e19d935a228c6 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 10 Oct 2021 14:18:53 +0200 Subject: :bug: fix Identify ready payload missing users --- gateway/src/opcodes/Identify.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 5d5b72d1..16ea1904 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -93,7 +93,9 @@ export async function onIdentify(this: WebSocket, data: Payload) { // @ts-ignore x.channel.recipients = x.channel.recipients?.map((x) => x.user); //TODO is this needed? check if users in group dm that are not friends are sent in the READY event - //users = users.concat(x.channel.recipients); + users = users.concat( + x.channel.recipients?.map((x) => x.user) as User[] + ); if (x.channel.isDm()) { x.channel.recipients = x.channel.recipients!.filter( (x) => x.id !== this.user_id -- cgit 1.5.1 From 82205520ed302aa5a91fc6a770e7ecf2b6ccc043 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 10 Oct 2021 14:31:13 +0200 Subject: :bug: fix Emoji missing in identify --- bundle/package.json | 2 +- gateway/src/opcodes/Identify.ts | 1 + util/src/entities/User.ts | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/bundle/package.json b/bundle/package.json index 3d41f7c2..eedbdd8c 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -9,7 +9,7 @@ "start": "node scripts/build.js && node dist/bundle/src/start.js", "start:bundle": "node dist/bundle/src/start.js", "test": "echo \"Error: no test specified\" && exit 1", - "migrate": "node node_modules/typeorm/cli.js -f ../util/ormconfig.json migration:run" + "migrate": "node --require ts-node/register node_modules/typeorm/cli.js -f ../util/ormconfig.json migration:run" }, "repository": { "type": "git", diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 16ea1904..5950105a 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -65,6 +65,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { "guild", "guild.channels", "guild.emojis", + "guild.emojis.user", "guild.roles", "guild.stickers", "user", diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 662ab031..04f1e9cb 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -248,7 +248,6 @@ export class User extends BaseClass { fingerprints: [], }); - console.log(user); await user.save(); if (Config.get().guild.autoJoin.enabled) { -- cgit 1.5.1 From d430e742963cb599216c20dd664a4fac35f32771 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 10 Oct 2021 14:52:16 +0200 Subject: :bug: fix null user in identify --- gateway/src/opcodes/Identify.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 5950105a..4470857f 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -210,7 +210,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { // @ts-ignore experiments: experiments, // TODO guild_join_requests: [], // TODO what is this? - users: users.unique(), + users: users.filter((x) => x).unique(), merged_members: merged_members, // shard // TODO: only for bots sharding // application // TODO for applications -- cgit 1.5.1 From 25aa1eeb37420ac75dd4ab031776010240b641d3 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 10 Oct 2021 16:11:40 +0200 Subject: :bug: fix Identify --- gateway/src/opcodes/Identify.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 4470857f..673dde9d 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -94,9 +94,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { // @ts-ignore x.channel.recipients = x.channel.recipients?.map((x) => x.user); //TODO is this needed? check if users in group dm that are not friends are sent in the READY event - users = users.concat( - x.channel.recipients?.map((x) => x.user) as User[] - ); + users = users.concat(x.channel.recipients as unknown as User[]); if (x.channel.isDm()) { x.channel.recipients = x.channel.recipients!.filter( (x) => x.id !== this.user_id -- cgit 1.5.1 From 90c5444324d0ff1c1af7f4bd9f16997cee4df0b5 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 12 Oct 2021 21:54:58 +0200 Subject: :pencil: improve logging --- gateway/src/Server.ts | 1 - gateway/src/events/Connection.ts | 1 + gateway/src/events/Message.ts | 2 -- gateway/src/opcodes/Identify.ts | 2 -- 4 files changed, 1 insertion(+), 5 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/Server.ts b/gateway/src/Server.ts index cf4f906c..7e1489be 100644 --- a/gateway/src/Server.ts +++ b/gateway/src/Server.ts @@ -32,7 +32,6 @@ export class Server { } this.server.on("upgrade", (request, socket, head) => { - console.log("socket requests upgrade", request.url); // @ts-ignore this.ws.handleUpgrade(request, socket, head, (socket) => { this.ws.emit("connection", socket, request); diff --git a/gateway/src/events/Connection.ts b/gateway/src/events/Connection.ts index 2cf22f7d..9bb034f0 100644 --- a/gateway/src/events/Connection.ts +++ b/gateway/src/events/Connection.ts @@ -28,6 +28,7 @@ export async function Connection( socket.on("close", Close); // @ts-ignore socket.on("message", Message); + console.log(`[Gateway] Connections: ${this.clients.size}`); const { searchParams } = new URL(`http://localhost${request.url}`); // @ts-ignore diff --git a/gateway/src/events/Message.ts b/gateway/src/events/Message.ts index af318bfd..acc39bb9 100644 --- a/gateway/src/events/Message.ts +++ b/gateway/src/events/Message.ts @@ -37,8 +37,6 @@ export async function Message(this: WebSocket, buffer: WS.Data) { return; } - console.log("[Gateway] Opcode " + OPCODES[data.op]); - try { return await OPCodeHandler.call(this, data); } catch (error) { diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 673dde9d..c91ca5dd 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -214,8 +214,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { // application // TODO for applications }; - console.log("Send ready"); - // TODO: send real proper data structure await Send(this, { op: OPCODES.Dispatch, -- cgit 1.5.1 From c1d786b6e1a9b8562e7ea05ae08934d92abff270 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 15 Oct 2021 00:03:19 +0200 Subject: :bug: fix sticker packs --- api/src/routes/sticker-packs/index.ts | 6 ++++-- gateway/src/opcodes/Identify.ts | 2 ++ util/src/entities/StickerPack.ts | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/api/src/routes/sticker-packs/index.ts b/api/src/routes/sticker-packs/index.ts index 89b91bdb..e6560d12 100644 --- a/api/src/routes/sticker-packs/index.ts +++ b/api/src/routes/sticker-packs/index.ts @@ -1,11 +1,13 @@ import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; -import { StickerPack } from "@fosscord/util/src/entities/StickerPack"; +import { StickerPack } from "@fosscord/util"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - res.json(await StickerPack.find({})); + const sticker_packs = await StickerPack.find({ relations: ["stickers"] }); + + res.json({ sticker_packs }); }); export default router; diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index c91ca5dd..2f9d4632 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -179,6 +179,8 @@ export async function onIdentify(this: WebSocket, data: Payload) { x.guild_hashes = {}; // @ts-ignore x.guild_scheduled_events = []; // @ts-ignore x.threads = []; + x.premium_subscription_count = 30; + x.premium_tier = 3; return x; }), guild_experiments: [], // TODO diff --git a/util/src/entities/StickerPack.ts b/util/src/entities/StickerPack.ts index b80fa9c7..ec8c69a2 100644 --- a/util/src/entities/StickerPack.ts +++ b/util/src/entities/StickerPack.ts @@ -1,8 +1,8 @@ -import { Column, Entity, JoinColumn, OneToMany, OneToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; import { Sticker } from "."; import { BaseClass } from "./BaseClass"; -@Entity("stickers") +@Entity("sticker_packs") export class StickerPack extends BaseClass { @Column() name: string; @@ -13,7 +13,7 @@ export class StickerPack extends BaseClass { @Column({ nullable: true }) banner_asset_id?: string; - @OneToMany(() => Sticker, (sticker: Sticker) => sticker.pack_id, { + @OneToMany(() => Sticker, (sticker: Sticker) => sticker.pack, { cascade: true, orphanedRowAction: "delete", }) @@ -25,7 +25,7 @@ export class StickerPack extends BaseClass { @RelationId((pack: StickerPack) => pack.cover_sticker) cover_sticker_id?: string; - @OneToOne(() => Sticker, { nullable: true }) + @ManyToOne(() => Sticker, { nullable: true }) @JoinColumn() cover_sticker?: Sticker; } -- cgit 1.5.1 From 468e59024941caf224409f093009997f1d11b517 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 16 Oct 2021 00:19:10 +0200 Subject: :bug: fix #450 (only if user is a bot application) --- gateway/src/opcodes/Identify.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 2f9d4632..88b514b2 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -12,6 +12,7 @@ import { PublicUser, PrivateUserProjection, ReadState, + Application, } from "@fosscord/util"; import { Send } from "../util/Send"; import { CLOSECODES, OPCODES } from "../util/Constants"; @@ -171,6 +172,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { const d: ReadyEventData = { v: 8, + application: await Application.findOne({ id: this.user_id }), user: privateUser, user_settings: user.settings, // @ts-ignore @@ -213,7 +215,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { users: users.filter((x) => x).unique(), merged_members: merged_members, // shard // TODO: only for bots sharding - // application // TODO for applications }; // TODO: send real proper data structure -- cgit 1.5.1 From 71aa07bebe2378c42a05e6a1dac6d22f81c22fea Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 16 Oct 2021 00:34:07 +0200 Subject: :sparkles: lazy loading of guilds for bots closes #451 --- gateway/src/opcodes/Identify.ts | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 88b514b2..006dc83c 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -42,6 +42,14 @@ export async function onIdentify(this: WebSocket, data: Payload) { return this.close(CLOSECODES.Authentication_failed); } this.user_id = decoded.id; + + const user = await User.findOneOrFail({ + where: { id: this.user_id }, + relations: ["relationships", "relationships.to"], + select: [...PrivateUserProjection, "relationships"], + }); + if (!user) return this.close(CLOSECODES.Authentication_failed); + if (!identify.intents) identify.intents = BigInt("0b11111111111111"); this.intents = new Intents(identify.intents); if (identify.shard) { @@ -83,7 +91,25 @@ export async function onIdentify(this: WebSocket, data: Payload) { }, ]; }) as PublicMember[][]; - const guilds = members.map((x) => ({ ...x.guild, joined_at: x.joined_at })); + let guilds = members.map((x) => ({ ...x.guild, joined_at: x.joined_at })); + + // @ts-ignore + guilds = guilds.map((guild) => { + if (user.bot) { + setTimeout(() => { + Send(this, { + op: OPCODES.Dispatch, + t: EVENTEnum.GuildCreate, + s: this.sequence++, + d: guild, + }); + }, 500); + return { id: guild.id, unavailable: true }; + } + + return guild; + }); + const user_guild_settings_entries = members.map((x) => x.settings); const recipients = await Recipient.find({ @@ -103,12 +129,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { } return x.channel; }); - const user = await User.findOneOrFail({ - where: { id: this.user_id }, - relations: ["relationships", "relationships.to"], - select: [...PrivateUserProjection, "relationships"], - }); - if (!user) return this.close(CLOSECODES.Authentication_failed); for (let relation of user.relationships) { const related_user = relation.to; -- cgit 1.5.1 From 2b731bffee767f243f5a3484228407240da8ee4d Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 17 Oct 2021 00:37:06 +0200 Subject: :zap: improve performance of identify + listener --- gateway/src/listener/listener.ts | 115 +++++++++++++++++++++++++------------- gateway/src/opcodes/Identify.ts | 116 +++++++++++++++++++++++++-------------- 2 files changed, 152 insertions(+), 79 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index c5b1a576..79659a1f 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -6,6 +6,9 @@ import { EventOpts, ListenEventOpts, Member, + EVENTEnum, + Relationship, + RelationshipType, } from "@fosscord/util"; import { OPCODES } from "../util/Constants"; import { Send } from "../util/Send"; @@ -21,22 +24,45 @@ import { Recipient } from "@fosscord/util"; // Sharding: calculate if the current shard id matches the formula: shard_id = (guild_id >> 22) % num_shards // https://discord.com/developers/docs/topics/gateway#sharding +export function handlePresenceUpdate( + this: WebSocket, + { event, acknowledge, data }: EventOpts +) { + acknowledge?.(); + if (event === EVENTEnum.PresenceUpdate) { + return Send(this, { + op: OPCODES.Dispatch, + t: event, + d: data, + s: this.sequence++, + }); + } +} + // TODO: use already queried guilds/channels of Identify and don't fetch them again export async function setupListener(this: WebSocket) { - const members = await Member.find({ - where: { id: this.user_id }, - relations: ["guild", "guild.channels"], - }); + const [members, recipients, relationships] = await Promise.all([ + Member.find({ + where: { id: this.user_id }, + relations: ["guild", "guild.channels"], + }), + Recipient.find({ + where: { user_id: this.user_id, closed: false }, + relations: ["channel"], + }), + Relationship.find({ + from_id: this.user_id, + type: RelationshipType.friends, + }), + ]); + const guilds = members.map((x) => x.guild); - const recipients = await Recipient.find({ - where: { user_id: this.user_id, closed: false }, - relations: ["channel"], - }); const dm_channels = recipients.map((x) => x.channel); const opts: { acknowledge: boolean; channel?: AMQChannel } = { acknowledge: true, }; + this.listen_options = opts; const consumer = consume.bind(this); if (RabbitMQ.connection) { @@ -47,45 +73,44 @@ export async function setupListener(this: WebSocket) { this.events[this.user_id] = await listenEvent(this.user_id, consumer, opts); - for (const channel of dm_channels) { + relationships.forEach(async (relationship) => { + this.events[relationship.to_id] = await listenEvent( + relationship.to_id, + handlePresenceUpdate.bind(this), + opts + ); + }); + + dm_channels.forEach(async (channel) => { this.events[channel.id] = await listenEvent(channel.id, consumer, opts); - } + }); - for (const guild of guilds) { - // contains guild and dm channels + guilds.forEach(async (guild) => { + const permission = await getPermission(this.user_id, guild.id); + this.permissions[guild.id] = permission; + this.events[guild.id] = await listenEvent(guild.id, consumer, opts); - getPermission(this.user_id, guild.id) - .then(async (x) => { - this.permissions[guild.id] = x; - this.listeners; - this.events[guild.id] = await listenEvent( - guild.id, + guild.channels.forEach(async (channel) => { + if ( + permission + .overwriteChannel(channel.permission_overwrites!) + .has("VIEW_CHANNEL") + ) { + this.events[channel.id] = await listenEvent( + channel.id, consumer, opts ); - - for (const channel of guild.channels) { - if ( - x - .overwriteChannel(channel.permission_overwrites!) - .has("VIEW_CHANNEL") - ) { - this.events[channel.id] = await listenEvent( - channel.id, - consumer, - opts - ); - } - } - }) - .catch((e) => - console.log("couldn't get permission for guild " + guild, e) - ); - } + } + }); + }); this.once("close", () => { if (opts.channel) opts.channel.close(); - else Object.values(this.events).forEach((x) => x()); + else { + Object.values(this.events).forEach((x) => x()); + Object.values(this.member_events).forEach((x) => x()); + } }); } @@ -97,10 +122,23 @@ async function consume(this: WebSocket, opts: EventOpts) { const consumer = consume.bind(this); const listenOpts = opts as ListenEventOpts; + opts.acknowledge?.(); // console.log("event", event); // subscription managment switch (event) { + case "GUILD_MEMBER_REMOVE": + this.member_events[data.user.id]?.(); + delete this.member_events[data.user.id]; + case "GUILD_MEMBER_ADD": + if (this.member_events[data.user.id]) break; // already subscribed + this.member_events[data.user.id] = await listenEvent( + data.user.id, + handlePresenceUpdate.bind(this), + this.listen_options + ); + break; + case "RELATIONSHIP_REMOVE": case "CHANNEL_DELETE": case "GUILD_DELETE": delete this.events[id]; @@ -196,5 +234,4 @@ async function consume(this: WebSocket, opts: EventOpts) { d: data, s: this.sequence++, }); - opts.acknowledge?.(); } 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 -- cgit 1.5.1 From 0ea7d5f35cba312545d56bee8abc878fe34383a0 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 17 Oct 2021 00:39:54 +0200 Subject: :sparkles: User presence/status --- gateway/src/events/Close.ts | 41 +++++++++-- gateway/src/events/Connection.ts | 2 +- gateway/src/opcodes/LazyRequest.ts | 132 ++++++++++++++++++++++++---------- gateway/src/opcodes/PresenceUpdate.ts | 24 ++++++- gateway/src/schema/LazyRequest.ts | 2 +- gateway/src/util/WebSocket.ts | 2 + gateway/tsconfig.json | 2 +- util/src/entities/Member.ts | 18 ++++- util/src/entities/Session.ts | 20 ++++-- 9 files changed, 193 insertions(+), 50 deletions(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/events/Close.ts b/gateway/src/events/Close.ts index 5c1bd292..5b7c512c 100644 --- a/gateway/src/events/Close.ts +++ b/gateway/src/events/Close.ts @@ -1,13 +1,46 @@ import { WebSocket } from "@fosscord/gateway"; -import { Session } from "@fosscord/util"; +import { + emitEvent, + PresenceUpdateEvent, + PrivateSessionProjection, + Session, + SessionsReplace, + User, +} from "@fosscord/util"; export async function Close(this: WebSocket, code: number, reason: string) { console.log("[WebSocket] closed", code, reason); - if (this.session_id) await Session.delete({ session_id: this.session_id }); if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); if (this.readyTimeout) clearTimeout(this.readyTimeout); - this.deflate?.close(); - this.removeAllListeners(); + + if (this.session_id) { + await Session.delete({ session_id: this.session_id }); + const sessions = await Session.find({ + where: { user_id: this.user_id }, + select: PrivateSessionProjection, + }); + await emitEvent({ + event: "SESSIONS_REPLACE", + user_id: this.user_id, + data: sessions, + } as SessionsReplace); + const session = sessions.first() || { + activities: [], + client_info: {}, + status: "offline", + }; + + await 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); + } } diff --git a/gateway/src/events/Connection.ts b/gateway/src/events/Connection.ts index 9bb034f0..4954cd08 100644 --- a/gateway/src/events/Connection.ts +++ b/gateway/src/events/Connection.ts @@ -8,7 +8,6 @@ import { Close } from "./Close"; import { Message } from "./Message"; import { createDeflate } from "zlib"; import { URL } from "url"; -import { Session } from "@fosscord/util"; var erlpack: any; try { erlpack = require("@yukikaze-bot/erlpack"); @@ -57,6 +56,7 @@ export async function Connection( } socket.events = {}; + socket.member_events = {}; socket.permissions = {}; socket.sequence = 0; 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); } diff --git a/gateway/src/schema/LazyRequest.ts b/gateway/src/schema/LazyRequest.ts index 7c828ac6..1fe658bb 100644 --- a/gateway/src/schema/LazyRequest.ts +++ b/gateway/src/schema/LazyRequest.ts @@ -1,6 +1,6 @@ export interface LazyRequest { guild_id: string; - channels?: Record; + channels?: Record; activities?: boolean; threads?: boolean; typing?: true; diff --git a/gateway/src/util/WebSocket.ts b/gateway/src/util/WebSocket.ts index 49626b2a..e3313f40 100644 --- a/gateway/src/util/WebSocket.ts +++ b/gateway/src/util/WebSocket.ts @@ -17,4 +17,6 @@ export interface WebSocket extends WS { sequence: number; permissions: Record; events: Record; + member_events: Record; + listen_options: any; } diff --git a/gateway/tsconfig.json b/gateway/tsconfig.json index 2ad38f93..b6ae9455 100644 --- a/gateway/tsconfig.json +++ b/gateway/tsconfig.json @@ -27,7 +27,7 @@ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ - "strict": false /* Enable all strict type-checking options. */, + "strict": true /* Enable all strict type-checking options. */, "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, "strictNullChecks": true /* Enable strict null checks. */, // "strictFunctionTypes": true, /* Enable strict checking of function types. */ diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts index 12b0b49a..0f7be2a7 100644 --- a/util/src/entities/Member.ts +++ b/util/src/entities/Member.ts @@ -26,6 +26,22 @@ import { BaseClassWithoutId } from "./BaseClass"; import { Ban, PublicGuildRelations } from "."; import { DiscordApiErrors } from "../util/Constants"; +export const MemberPrivateProjection: (keyof Member)[] = [ + "id", + "guild", + "guild_id", + "deaf", + "joined_at", + "last_message_id", + "mute", + "nick", + "pending", + "premium_since", + "roles", + "settings", + "user", +]; + @Entity("members") @Index(["id", "guild_id"], { unique: true }) export class Member extends BaseClassWithoutId { @@ -81,7 +97,7 @@ export class Member extends BaseClassWithoutId { @Column() pending: boolean; - @Column({ type: "simple-json" }) + @Column({ type: "simple-json", select: false }) settings: UserGuildSettings; @Column({ nullable: true }) diff --git a/util/src/entities/Session.ts b/util/src/entities/Session.ts index 7cc325f5..ac5313f1 100644 --- a/util/src/entities/Session.ts +++ b/util/src/entities/Session.ts @@ -1,6 +1,8 @@ import { User } from "./User"; import { BaseClass } from "./BaseClass"; import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Status } from "../interfaces/Status"; +import { Activity } from "../interfaces/Activity"; //TODO we need to remove all sessions on server start because if the server crashes without closing websockets it won't delete them @@ -17,11 +19,13 @@ export class Session extends BaseClass { user: User; //TODO check, should be 32 char long hex string - @Column({ nullable: false }) + @Column({ nullable: false, select: false }) session_id: string; - activities: []; //TODO + @Column({ type: "simple-json", nullable: true }) + activities: Activity[] = []; + // TODO client_status @Column({ type: "simple-json", select: false }) client_info: { client: string; @@ -29,6 +33,14 @@ export class Session extends BaseClass { version: number; }; - @Column({ nullable: false }) - status: string; //TODO enum + @Column({ nullable: false, type: "varchar" }) + status: Status; //TODO enum } + +export const PrivateSessionProjection: (keyof Session)[] = [ + "user_id", + "session_id", + "activities", + "client_info", + "status", +]; -- cgit 1.5.1 From 9fa1081803bb4e0cc043cd554b8c1d67f7ae5e68 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 17 Oct 2021 00:57:31 +0200 Subject: :bug: default session activites --- gateway/src/opcodes/Identify.ts | 1 + util/src/entities/Session.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'gateway/src/opcodes') diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index bd7fc894..f39ac808 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -94,6 +94,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { os: identify.properties?.os, version: 0, }, + activities: [], }).save(), Application.findOne({ id: this.user_id }), ]); diff --git a/util/src/entities/Session.ts b/util/src/entities/Session.ts index ac5313f1..969efa89 100644 --- a/util/src/entities/Session.ts +++ b/util/src/entities/Session.ts @@ -23,7 +23,7 @@ export class Session extends BaseClass { session_id: string; @Column({ type: "simple-json", nullable: true }) - activities: Activity[] = []; + activities: Activity[]; // TODO client_status @Column({ type: "simple-json", select: false }) -- cgit 1.5.1