Newer
Older
percord / gateway / src / opcodes / Identify.ts
@Flam3rboy Flam3rboy on 3 Sep 2021 4 KB :bug: fix #311
import { CLOSECODES, Payload, OPCODES } from "../util/Constants";
import WebSocket from "../util/WebSocket";
import {
	Channel,
	checkToken,
	Guild,
	Intents,
	Member,
	ReadyEventData,
	User,
	Session,
	EVENTEnum,
	Config,
	dbConnection,
} from "@fosscord/util";
import { setupListener } from "../listener/listener";
import { IdentifySchema } from "../schema/Identify";
import { Send } from "../util/Send";
// import experiments from "./experiments.json";
const experiments: any = [];
import { check } from "./instanceOf";
import { Recipient } from "../../../util/dist/entities/Recipient";
import { genSessionId } from "../util/SessionUtils";

// TODO: bot sharding
// TODO: check priviliged intents
// TODO: check if already identified

export async function onIdentify(this: WebSocket, data: Payload) {
	clearTimeout(this.readyTimeout);
	check.call(this, IdentifySchema, data.d);

	const identify: IdentifySchema = data.d;

	try {
		const { jwtSecret } = Config.get().security;
		var { decoded } = await checkToken(identify.token, jwtSecret); // will throw an error if invalid
	} catch (error) {
		console.error("invalid token", error);
		return this.close(CLOSECODES.Authentication_failed);
	}
	this.user_id = decoded.id;
	if (!identify.intents) identify.intents = 0b11111111111111n;
	this.intents = new Intents(identify.intents);
	if (identify.shard) {
		this.shard_id = identify.shard[0];
		this.shard_count = identify.shard[1];
		if (
			!this.shard_count ||
			!this.shard_id ||
			this.shard_id >= this.shard_count ||
			this.shard_id < 0 ||
			this.shard_count <= 0
		) {
			return this.close(CLOSECODES.Invalid_shard);
		}
	}

	const members = await Member.find({
		where: { id: this.user_id },
		relations: [
			"guild",
			"guild.channels",
			"guild.emojis",
			"guild.roles",
			"guild.stickers",
			"user",
			"roles",
		],
	});
	const merged_members = members.map((x: any) => {
		return [x];
	}) as Member[][];
	const guilds = members.map((x) => ({ ...x.guild, joined_at: x.joined_at }));
	const user_guild_settings_entries = members.map((x) => x.settings);

	const recipients = await Recipient.find({
		where: { user_id: this.user_id },
		relations: ["channel", "channel.recipients"],
	});
	const channels = recipients.map((x) => x.channel);
	const user = await User.findOneOrFail({ id: this.user_id });
	if (!user) return this.close(CLOSECODES.Authentication_failed);

	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,
		},
	});

	//We save the session and we delete it when the websocket is closed
	await session.save();

	const public_user = {
		username: user.username,
		discriminator: user.discriminator,
		id: user.id,
		public_flags: user.public_flags,
		avatar: user.avatar,
		bot: user.bot,
		bio: user.bio,
	};

	const privateUser = {
		avatar: user.avatar,
		mobile: user.mobile,
		desktop: user.desktop,
		discriminator: user.discriminator,
		email: user.email,
		flags: user.flags,
		id: user.id,
		mfa_enabled: user.mfa_enabled,
		nsfw_allowed: user.nsfw_allowed,
		phone: user.phone,
		premium: user.premium,
		premium_type: user.premium_type,
		public_flags: user.public_flags,
		username: user.username,
		verified: user.verified,
		bot: user.bot,
		accent_color: user.accent_color || 0,
		banner: user.banner,
		bio: user.bio,
	};

	const d: ReadyEventData = {
		v: 8,
		user: privateUser,
		user_settings: user.settings,
		// @ts-ignore
		guilds: guilds.map((x) => {
			// @ts-ignore
			x.guild_hashes = {};
			return x;
		}),
		guild_experiments: [], // TODO
		geo_ordered_rtc_regions: [], // TODO
		relationships: user.relationships,
		read_state: {
			// TODO
			entries: [],
			partial: false,
			version: 304128,
		},
		user_guild_settings: {
			entries: user_guild_settings_entries,
			partial: false, // TODO partial
			version: 642,
		},
		private_channels: channels,
		session_id: session_id,
		analytics_token: "", // TODO
		connected_accounts: [], // TODO
		consents: {
			personalization: {
				consented: false, // TODO
			},
		},
		country_code: user.settings.locale,
		friend_suggestion_count: 0, // TODO
		// @ts-ignore
		experiments: experiments, // TODO
		guild_join_requests: [], // TODO what is this?
		users: [public_user].unique(), // TODO
		merged_members: merged_members,
		// shard // TODO: only for bots sharding
		// application // TODO for applications
	};

	console.log("Send ready");

	// TODO: send real proper data structure
	await Send(this, {
		op: OPCODES.Dispatch,
		t: EVENTEnum.Ready,
		s: this.sequence++,
		d,
	});

	//TODO send READY_SUPPLEMENTAL
	//TODO send GUILD_MEMBER_LIST_UPDATE
	//TODO send SESSIONS_REPLACE
	//TODO send VOICE_STATE_UPDATE to let the client know if another device is already connected to a voice channel

	await setupListener.call(this);
}