1 module discord.w.gateway;
2 
3 import core.time;
4 
5 import std.algorithm;
6 import std.array;
7 import std.conv;
8 import std.datetime.systime;
9 import std.random;
10 import std.typecons;
11 
12 import vibe.core.core;
13 import vibe.core.log;
14 import vibe.data.json;
15 import vibe.http.websockets;
16 import vibe.inet.url;
17 
18 import discord.w.api;
19 import discord.w.data;
20 import discord.w.json;
21 import discord.w.minietf;
22 import discord.w.ratelimiter;
23 import discord.w.types;
24 
25 string discordGatewayURL;
26 bool discordGatewayURLvalid = false;
27 
28 enum maxPacketSize = 4096;
29 
30 version (LargeShardedBot)
31 	enum IdentifyRateLimitCount = 2000;
32 else
33 	enum IdentifyRateLimitCount = 1000;
34 
35 __gshared mixin RateLimitObject!(5100.msecs) connectRateLimit;
36 __gshared mixin DynamicRateLimitObject!(IdentifyRateLimitCount, 24.hours, 5100.msecs) identifyRateLimit;
37 __gshared mixin DynamicRateLimitObject!(12, 6.seconds, 100.msecs) sendRateLimit;
38 __gshared mixin DynamicRateLimitObject!(5, 60.seconds, 1000.msecs) statusRateLimit;
39 
40 void validateDisconnectOpcode(short code) @safe
41 {
42 	switch (code)
43 	{
44 	case 4001:
45 		throw new Exception("Sent an invalid payload or opcode and got disconnected");
46 	case 4002:
47 		throw new Exception("Server decode error disconnection");
48 	case 4003:
49 		throw new Exception("Sent packet before Identify, disconnected");
50 	case 4004:
51 		throw new Exception("Identify packet incorrect, disconnected (did you set identifyPacket?)");
52 	case 4005:
53 		throw new Exception("Tried to send Identify more than once, disconnected");
54 	case 4007:
55 		logDiagnostic("Resume had an invalid session and got disconnected");
56 		return; // try reconnecting
57 	case 4008:
58 		throw new Exception("Payload rate limit exceeded, disconnected");
59 	case 4009:
60 		logDiagnostic("Session timed out and got disconnected");
61 		return; // try reconnecting
62 	case 4010:
63 		throw new Exception("Invalid shard in Identify packet, disconnected");
64 	case 4011:
65 		throw new Exception("Too many guilds to handle, requires sharding, disconnected");
66 	case 4000: // unknown
67 	default:
68 		return; // try reconnecting
69 	}
70 }
71 
72 class DiscordGateway
73 {
74 @safe:
75 	enum Encoding : string
76 	{
77 		JSON = "json",
78 		ETF = "etf"
79 	}
80 
81 	private
82 	{
83 		WebSocket socket;
84 		Encoding encoding;
85 		bool disconnected;
86 		bool shouldDisconnect;
87 		bool hasLastSequence;
88 		int lastSequence;
89 		bool receivedAck;
90 		bool workerRunning;
91 		string sessionID;
92 	}
93 
94 	struct ConnectionInfo
95 	{
96 		int protocolVersion;
97 		User user;
98 		Snowflake[] privateChannels;
99 		Snowflake[] guilds;
100 	}
101 
102 	ConnectionInfo info;
103 
104 	bool connected()
105 	{
106 		return socket.connected;
107 	}
108 
109 	OpIdentify identifyPacket;
110 
111 	this(string token)
112 	{
113 		import std.system : os, endian;
114 
115 		identifyPacket.token = token;
116 		identifyPacket.properties["$os"] = os.to!string;
117 		identifyPacket.properties["$browser"] = "discord-w/vibe.d";
118 		version (X86)
119 			identifyPacket.properties["$device"] = "x86 " ~ endian.to!string;
120 		else version (X86_64)
121 			identifyPacket.properties["$device"] = "x86_64 " ~ endian.to!string;
122 		else version (ARM_HardFloat)
123 			identifyPacket.properties["$device"] = "armhf " ~ endian.to!string;
124 		else version (ARM)
125 			identifyPacket.properties["$device"] = "arm " ~ endian.to!string;
126 		else version (PPC)
127 			identifyPacket.properties["$device"] = "powerpc " ~ endian.to!string;
128 	}
129 
130 	void send(OpCode opcode, T)(T data)
131 	{
132 		static assert(opcode != OpCode.dispatch, "dispatch opCode is not sendable");
133 		static assert(opcode != OpCode.reconnect, "reconnect opCode is not sendable");
134 		static assert(opcode != OpCode.invalidSession, "invalidSession opCode is not sendable");
135 		static assert(opcode != OpCode.hello, "hello opCode is not sendable");
136 		static assert(opcode != OpCode.heartbeatAck, "heartbeatAck opCode is not sendable");
137 
138 		struct Packet
139 		{
140 			OpCode op = opcode;
141 			T d;
142 			static if (opcode == OpCode.dispatch)
143 			{
144 				int s;
145 				string t;
146 			}
147 		}
148 
149 		Packet packet;
150 		packet.d = data;
151 
152 		final switch (encoding)
153 		{
154 		case Encoding.JSON:
155 			string s = serializeToJsonString(packet);
156 			if (s.length > maxPacketSize)
157 				throw new Exception("Attempted to send too large packet");
158 			sendRateLimit.waitFor();
159 			static if (opcode != OpCode.identify && opcode != OpCode.resume)
160 				logDebugV("Sending packet %s", s);
161 			socket.send(s);
162 			break;
163 		case Encoding.ETF:
164 			ubyte[] s = ETFBuffer.serialize(packet, maxPacketSize, false).bytes;
165 			assert(s.length <= maxPacketSize);
166 			sendRateLimit.waitFor();
167 			static if (opcode != OpCode.identify && opcode != OpCode.resume)
168 				logDebugV("Sending packet %s", s);
169 			socket.send(s);
170 			break;
171 		}
172 	}
173 
174 	OpFrame receive() @trusted
175 	{
176 		OpFrame ret;
177 
178 		final switch (encoding)
179 		{
180 		case Encoding.JSON:
181 			auto text = socket.receiveText;
182 			logDebugV("Received %s", text);
183 			auto j = parseJsonString(text);
184 			ret.op = cast(OpCode) j["op"].get!int;
185 			ret.j = j["d"];
186 			ret.isJson = true;
187 			if (ret.op == OpCode.dispatch)
188 			{
189 				ret.s = j["s"].get!int;
190 				ret.t = j["t"].get!string;
191 			}
192 			break;
193 		case Encoding.ETF:
194 			auto binary = socket.receiveBinary;
195 			logDebugV("Received %s", binary.binToString);
196 			auto etf = ETFBuffer.deserialzeTree(binary).children[0];
197 			auto e = etf.get!(ETFNode[string]);
198 			ret.op = cast(OpCode) e["op"].get!int;
199 			ret.e = e["d"];
200 			ret.isJson = false;
201 			if (ret.op == OpCode.dispatch)
202 			{
203 				ret.s = e["s"].get!int;
204 				ret.t = e["t"].get!string;
205 			}
206 			break;
207 		}
208 		return ret;
209 	}
210 
211 	void connect(Encoding encoding = Encoding.JSON, bool handled = true)
212 	{
213 		assert(disconnected || !socket, "Attempted to connect when already connected");
214 		disconnected = false;
215 		socket = null;
216 		shouldDisconnect = false;
217 		this.encoding = encoding;
218 		while (!socket)
219 		{
220 			connectRateLimit.waitFor();
221 			if (!discordGatewayURLvalid)
222 			{
223 				discordGatewayURL = requestDiscordEndpoint("/gateway")["url"].get!string;
224 				discordGatewayURLvalid = true;
225 			}
226 			URL ws_url;
227 			try
228 			{
229 				ws_url = URL.parse(discordGatewayURL ~ "/?v=6&encoding=" ~ cast(string) encoding);
230 				socket = connectWebSocket(ws_url);
231 			}
232 			catch (Exception e)
233 			{
234 				socket = null;
235 				logDiagnostic("Failed to connect to %s, trying again in 10s...", ws_url);
236 				() @trusted{ logDebug("Exception: %s", e); }();
237 				discordGatewayURLvalid = false;
238 				sleep(10.seconds);
239 			}
240 		}
241 		if (handled)
242 			runTask({ runWorker(); });
243 	}
244 
245 	void disconnect(short code = 1000)
246 	{
247 		shouldDisconnect = true;
248 		if (socket && socket.connected)
249 			socket.close(code);
250 		disconnected = true;
251 	}
252 
253 	void reconnect(bool resume = false)
254 	{
255 		logDiagnostic("Reconnecting websocket");
256 		runTask({
257 			disconnect(900);
258 			while (workerRunning)
259 				yield();
260 			connect(encoding, false);
261 			runWorker(resume);
262 		});
263 	}
264 
265 	void sendHeartbeat()
266 	{
267 		if (!receivedAck)
268 		{
269 			logDiagnostic("didn't receive an ack between last heartbeat and this heartbeat, reconnecting");
270 			reconnect();
271 			return;
272 		}
273 		logTrace("Sending heartbeat");
274 		receivedAck = false;
275 		if (hasLastSequence)
276 			send!(OpCode.heartbeat)(lastSequence);
277 		else
278 			send!(OpCode.heartbeat)(null);
279 	}
280 
281 	void runHeartbeat(Duration interval)
282 	{
283 		logDebugV("Running heartbeat with %s interval", interval);
284 		while (true)
285 		{
286 			sleep(interval);
287 			if (socket.connected)
288 				sendHeartbeat();
289 			else
290 				break;
291 		}
292 	}
293 
294 	void runWorker(bool resume = false)
295 	{
296 		scope (exit)
297 			workerRunning = false;
298 		assert(socket);
299 		workerRunning = true;
300 		auto hello = receive;
301 		if (hello.op != OpCode.hello)
302 			throw new Exception("Received unexpected opcode " ~ hello.op.to!string);
303 		logDebugV("Received hello packet %s", hello);
304 		receivedAck = true;
305 		runTask({ runHeartbeat(hello.d!OpHello.heartbeat_interval.msecs); });
306 		if (sessionID.length)
307 		{
308 			send!(OpCode.resume)(ResumeData(identifyPacket.token, sessionID, lastSequence));
309 		}
310 		else
311 		{
312 			identifyRateLimit.waitFor();
313 			send!(OpCode.identify)(identifyPacket);
314 		}
315 		try
316 		{
317 			while (socket.connected)
318 			{
319 				auto frame = receive;
320 				logDebugV("Received frame %s", frame);
321 				switch (frame.op)
322 				{
323 				case OpCode.dispatch:
324 					runTask(&processEvent, frame);
325 					break;
326 				case OpCode.reconnect:
327 					reconnect();
328 					break;
329 				case OpCode.invalidSession:
330 					sessionID = "";
331 					connectRateLimit.waitFor();
332 					reconnect();
333 					break;
334 				case OpCode.heartbeat:
335 				case OpCode.heartbeatAck:
336 					receivedAck = true;
337 					break;
338 				default:
339 					break;
340 				}
341 			}
342 		}
343 		catch (Exception e)
344 		{
345 			if (socket.connected)
346 				socket.close(900);
347 			() @trusted{ logDiagnostic("Error in socket worker: %s", e); }();
348 		}
349 		validateDisconnectOpcode(socket.closeCode);
350 		if (socket.closeCode == 4007 || socket.closeCode == 4009)
351 			sessionID = "";
352 		if (!shouldDisconnect)
353 		{
354 			if (!sessionID.length)
355 				sleep(uniform(1000, 5000).msecs);
356 			reconnect(!!sessionID.length);
357 		}
358 	}
359 
360 	void joinVoiceChannel(Snowflake guild, Snowflake channel, bool selfMute, bool selfDeaf)
361 	{
362 		UpdateVoiceStateCommand cmd;
363 		cmd.guild_id = guild;
364 		cmd.channel_id = channel;
365 		cmd.self_mute = selfMute;
366 		cmd.self_deaf = selfDeaf;
367 		send!(OpCode.voiceStatusUpdate)(cmd);
368 	}
369 
370 	void disconnectVoiceChannel(Snowflake guild, bool selfMute = true, bool selfDeaf = true)
371 	{
372 		UpdateVoiceStateCommand cmd;
373 		cmd.guild_id = guild;
374 		cmd.self_mute = selfMute;
375 		cmd.self_deaf = selfDeaf;
376 		send!(OpCode.voiceStatusUpdate)(cmd);
377 	}
378 
379 	void updateStatus(UpdateStatus.StatusType status,
380 			Nullable!Activity game = Nullable!Activity.init, bool afk = false,
381 			SysTime idleSince = SysTime.init)
382 	{
383 		statusRateLimit.waitFor();
384 		Nullable!long idle;
385 		if (idleSince != SysTime.init)
386 			idle = idleSince.toUnixTime!long;
387 		send!(OpCode.statusUpdate)(UpdateStatus(idle, game, status, afk));
388 	}
389 
390 	void onReadyEvent(ReadyPacket packet)
391 	{
392 		sessionID = packet.session_id;
393 		info.protocolVersion = packet.v;
394 		info.user = packet.user;
395 		info.guilds = packet.guilds.map!(a => a.id).array;
396 		info.privateChannels = packet.private_channels.map!(a => a.id).array;
397 	}
398 
399 	void onResumedEvent(ResumedPacket)
400 	{
401 	}
402 
403 	void onChannelCreate(Channel c)
404 	{
405 		gChannelCache.put(c);
406 	}
407 
408 	void onChannelUpdate(Channel c)
409 	{
410 		gChannelCache.patch(c);
411 	}
412 
413 	void onChannelDelete(Channel c)
414 	{
415 		gChannelCache.remove(c.id);
416 	}
417 
418 	void onChannelPinsUpdate(ChannelPinsUpdatePacket)
419 	{
420 		logDiagnostic("TODO: implement channel pins update");
421 	}
422 
423 	void onGuildCreate(Guild g)
424 	{
425 		gGuildCache.put(g);
426 		foreach (channel; g.channels)
427 		{
428 			channel.guild_id = g.id;
429 			gChannelCache.put(channel);
430 		}
431 		foreach (member; g.members)
432 		{
433 			GuildUserCache entry;
434 			entry.guildUserID = [g.id, member.user.id];
435 			entry.roles = member.roles;
436 			entry.joinDate = member.joined_at;
437 			entry.deaf = member.deaf;
438 			entry.mute = member.mute;
439 			entry.nick = member.nick.isNull ? null : member.nick.get;
440 			gGuildUserCache.put(entry);
441 			gUserCache.patch(member.user, true);
442 		}
443 	}
444 
445 	void onGuildUpdate(Guild g)
446 	{
447 		gGuildCache.patch(g);
448 	}
449 
450 	void onGuildDelete(UnavailableGuild g)
451 	{
452 		gGuildCache.update(g.id, (scope ref guild) { guild.unavailable = true; });
453 	}
454 
455 	void onGuildBanAdd(User user, Snowflake guild_id)
456 	{
457 	}
458 
459 	void onGuildBanRemove(User user, Snowflake guild_id)
460 	{
461 	}
462 
463 	void onGuildEmojisUpdate(GuildEmojisUpdatePacket p)
464 	{
465 		gGuildCache.update(p.guild_id, (scope ref guild) { guild.emojis = p.emojis; });
466 	}
467 
468 	void onGuildIntegrationsUpdate(GuildIntegrationsUpdatePacket)
469 	{
470 	}
471 
472 	void onGuildMemberAdd(GuildMember member, Snowflake guild_id, bool isChunk)
473 	{
474 		GuildUserCache entry;
475 		entry.guildUserID = [guild_id, member.user.id];
476 		entry.roles = member.roles;
477 		entry.joinDate = member.joined_at;
478 		entry.deaf = member.deaf;
479 		entry.mute = member.mute;
480 		entry.nick = member.nick.isNull ? null : member.nick.get;
481 		gGuildUserCache.put(entry);
482 		gUserCache.patch(member.user, true);
483 	}
484 
485 	void onGuildMemberRemove(GuildMemberRemovePacket p)
486 	{
487 		gGuildUserCache.remove([p.guild_id, p.user.id]);
488 	}
489 
490 	void onGuildMemberUpdate(GuildMemberUpdatePacket p)
491 	{
492 		gGuildUserCache.update([p.guild_id, p.user.id], (scope ref obj) {
493 			obj.roles = p.roles;
494 			obj.nick = p.nick;
495 		});
496 	}
497 
498 	void onGuildMembersChunk(GuildMembersChunkPacket p)
499 	{
500 		foreach (member; p.members)
501 			onGuildMemberAdd(member, p.guild_id, true);
502 	}
503 
504 	void onGuildRoleCreate(GuildRoleCreatePacket p)
505 	{
506 		gGuildCache.update(p.guild_id, (scope ref guild) { guild.roles ~= p.role; });
507 	}
508 
509 	void onGuildRoleUpdate(GuildRoleUpdatePacket p)
510 	{
511 		gGuildCache.update(p.guild_id, (scope ref guild) {
512 			auto index = guild.roles.countUntil!(a => a.id == p.role.id);
513 			if (index == -1)
514 				guild.roles ~= p.role;
515 			else
516 				guild.roles[index] = p.role;
517 		});
518 	}
519 
520 	void onGuildRoleDelete(GuildRoleDeletePacket p)
521 	{
522 		gGuildCache.update(p.guild_id, (scope ref guild) {
523 			auto index = guild.roles.countUntil!(a => a.id == p.role_id);
524 			if (index != -1)
525 				guild.roles = guild.roles.remove(index);
526 		});
527 	}
528 
529 	void onMessageCreate(Message m) @trusted
530 	{
531 		gMessageCache.put(m);
532 	}
533 
534 	void onMessageUpdate(Message m)
535 	{
536 		gMessageCache.patch(m);
537 	}
538 
539 	void onMessageDelete(MessageDeletePacket m)
540 	{
541 		auto success = gMessageCache.remove(m.id);
542 		if (!success)
543 			(() @trusted => logDiagnostic("Could not delete message %s from cache", m))();
544 	}
545 
546 	void onMessageDeleteBulk(MessageDeleteBulkPacket m)
547 	{
548 		auto failed = gMessageCache.removeAll(m.ids);
549 		if (failed.length)
550 			(() @trusted => logDiagnostic("Could not delete messages %s from cache", failed))();
551 	}
552 
553 	void onMessageReactionAdd(MessageReactionAddPacket p)
554 	{
555 		gMessageCache.update(p.message_id, (scope ref msg) @trusted{
556 			auto index = msg.reactions.countUntil!(a => a.emoji == p.emoji);
557 			if (index != -1)
558 			{
559 				msg.reactions[index].count++;
560 				msg.reactions[index].users ~= p.user_id;
561 			}
562 			else
563 				msg.reactions ~= Reaction(1, false, p.emoji, [p.user_id]);
564 		});
565 	}
566 
567 	void onMessageReactionRemove(MessageReactionRemovePacket p)
568 	{
569 		gMessageCache.update(p.message_id, (scope ref msg) @trusted{
570 			auto index = msg.reactions.countUntil!(a => a.emoji == p.emoji);
571 			if (index != -1)
572 			{
573 				msg.reactions[index].count--;
574 				auto userIndex = msg.reactions[index].users.countUntil(p.user_id);
575 				if (userIndex != -1)
576 					msg.reactions[index].users = msg.reactions[index].users.remove(userIndex);
577 			}
578 		});
579 	}
580 
581 	void onMessageReactionRemoveAll(MessageReactionRemoveAllPacket p)
582 	{
583 		gMessageCache.update(p.message_id, (scope ref msg) @trusted{
584 			msg.reactions = null;
585 		});
586 	}
587 
588 	void onPresenceUpdate(PresenceUpdate p)
589 	{
590 		gGuildUserCache.update([p.guild_id, p.user.id], (scope ref obj) {
591 			obj.status = p.status;
592 			obj.game = p.game;
593 			obj.roles = p.roles;
594 		}, true);
595 	}
596 
597 	void onTypingStart(TypingStartPacket p)
598 	{
599 		gChannelUserCache.update([p.channel_id, p.user_id], (scope ref obj) {
600 			obj.typing = SysTime.fromUnixTime(p.timestamp);
601 		}, true);
602 	}
603 
604 	void onUserUpdate(User u)
605 	{
606 		gUserCache.patch(u, true);
607 	}
608 
609 	void onVoiceStateUpdate(VoiceState s)
610 	{
611 		gVoiceStateCache.update([s.guild_id, s.channel_id, s.user_id], (scope ref obj) @trusted{
612 			obj.state = s;
613 		}, true);
614 	}
615 
616 	void onVoiceServerUpdate(VoiceServerUpdatePacket)
617 	{
618 	}
619 
620 	void onWebhooksUpdate(WebhooksUpdatePacket)
621 	{
622 	}
623 
624 	void processEvent(OpFrame frame) @safe
625 	{
626 		assert(frame.op == OpCode.dispatch);
627 		hasLastSequence = true;
628 		lastSequence = frame.s;
629 		switch (frame.t)
630 		{
631 		case "HELLO":
632 			logDiagnostic("Don't know how to handle a HELLO event (not opcode)");
633 			break;
634 		case "READY":
635 			onReadyEvent(frame.d!ReadyPacket);
636 			break;
637 		case "RESUMED":
638 			onResumedEvent(frame.d!ResumedPacket);
639 			break;
640 		case "INVALID_SESSION":
641 			logDiagnostic("Don't know how to handle an INVALID_SESSION event (not opcode)");
642 			break;
643 		case "CHANNEL_CREATE":
644 			onChannelCreate(frame.d!Channel);
645 			break;
646 		case "CHANNEL_UPDATE":
647 			onChannelUpdate(frame.d!Channel);
648 			break;
649 		case "CHANNEL_DELETE":
650 			onChannelDelete(frame.d!Channel);
651 			break;
652 		case "CHANNEL_PINS_UPDATE":
653 			onChannelPinsUpdate(frame.d!ChannelPinsUpdatePacket);
654 			break;
655 		case "GUILD_CREATE":
656 			onGuildCreate(frame.d!Guild);
657 			break;
658 		case "GUILD_UPDATE":
659 			onGuildUpdate(frame.d!Guild);
660 			break;
661 		case "GUILD_DELETE":
662 			onGuildDelete(frame.d!UnavailableGuild);
663 			break;
664 		case "GUILD_BAN_ADD":
665 			onGuildBanAdd(frame.d!User, frame.dExt!("guild_id", Snowflake));
666 			break;
667 		case "GUILD_BAN_REMOVE":
668 			onGuildBanRemove(frame.d!User, frame.dExt!("guild_id", Snowflake));
669 			break;
670 		case "GUILD_EMOJIS_UPDATE":
671 			onGuildEmojisUpdate(frame.d!GuildEmojisUpdatePacket);
672 			break;
673 		case "GUILD_INTEGRATIONS_UPDATE":
674 			onGuildIntegrationsUpdate(frame.d!GuildIntegrationsUpdatePacket);
675 			break;
676 		case "GUILD_MEMBER_ADD":
677 			onGuildMemberAdd(frame.d!GuildMember, frame.dExt!("guild_id", Snowflake), false);
678 			break;
679 		case "GUILD_MEMBER_REMOVE":
680 			onGuildMemberRemove(frame.d!GuildMemberRemovePacket);
681 			break;
682 		case "GUILD_MEMBER_UPDATE":
683 			onGuildMemberUpdate(frame.d!GuildMemberUpdatePacket);
684 			break;
685 		case "GUILD_MEMBERS_CHUNK":
686 			onGuildMembersChunk(frame.d!GuildMembersChunkPacket);
687 			break;
688 		case "GUILD_ROLE_CREATE":
689 			onGuildRoleCreate(frame.d!GuildRoleCreatePacket);
690 			break;
691 		case "GUILD_ROLE_UPDATE":
692 			onGuildRoleUpdate(frame.d!GuildRoleUpdatePacket);
693 			break;
694 		case "GUILD_ROLE_DELETE":
695 			onGuildRoleDelete(frame.d!GuildRoleDeletePacket);
696 			break;
697 		case "MESSAGE_CREATE":
698 			onMessageCreate(frame.d!Message);
699 			break;
700 		case "MESSAGE_UPDATE":
701 			onMessageUpdate(frame.d!Message);
702 			break;
703 		case "MESSAGE_DELETE":
704 			onMessageDelete(frame.d!MessageDeletePacket);
705 			break;
706 		case "MESSAGE_DELETE_BULK":
707 			onMessageDeleteBulk(frame.d!MessageDeleteBulkPacket);
708 			break;
709 		case "MESSAGE_REACTION_ADD":
710 			onMessageReactionAdd(frame.d!MessageReactionAddPacket);
711 			break;
712 		case "MESSAGE_REACTION_REMOVE":
713 			onMessageReactionRemove(frame.d!MessageReactionRemovePacket);
714 			break;
715 		case "MESSAGE_REACTION_REMOVE_ALL":
716 			onMessageReactionRemoveAll(frame.d!MessageReactionRemoveAllPacket);
717 			break;
718 		case "PRESENCE_UPDATE":
719 			onPresenceUpdate(frame.d!PresenceUpdate);
720 			break;
721 		case "TYPING_START":
722 			onTypingStart(frame.d!TypingStartPacket);
723 			break;
724 		case "USER_UPDATE":
725 			onUserUpdate(frame.d!User);
726 			break;
727 		case "VOICE_STATE_UPDATE":
728 			onVoiceStateUpdate(frame.d!VoiceState);
729 			break;
730 		case "VOICE_SERVER_UPDATE":
731 			onVoiceServerUpdate(frame.d!VoiceServerUpdatePacket);
732 			break;
733 		case "WEBHOOKS_UPDATE":
734 			onWebhooksUpdate(frame.d!WebhooksUpdatePacket);
735 			break;
736 		default:
737 			logDiagnostic("Received unknown event %s: %s", frame.t, frame);
738 			break;
739 		}
740 	}
741 }
742 
743 struct OpFrame
744 {
745 	OpCode op;
746 	union
747 	{
748 		Json j;
749 		ETFNode e;
750 	}
751 
752 	int s;
753 	string t;
754 	bool isJson;
755 
756 	void opAssign(OpFrame other)
757 	{
758 		op = other.op;
759 		s = other.s;
760 		t = other.t;
761 		isJson = other.isJson;
762 		if (other.isJson)
763 			j = other.j;
764 		else
765 			e = other.e;
766 	}
767 
768 	string toString() @trusted
769 	{
770 		string ret = "frame {op=" ~ op.to!string ~ ", s=" ~ s.to!string ~ ", t='" ~ t ~ "', d=";
771 		if (isJson)
772 			ret ~= j.toPrettyString;
773 		else
774 			ret ~= e.toString;
775 		ret ~= '}';
776 		return ret;
777 	}
778 
779 	T d(T)() @trusted
780 	{
781 		if (isJson)
782 			return deserializeJson!T(j);
783 		else
784 			return ETFBuffer.deserialize!T(e.bufferStart, false);
785 	}
786 
787 	T dExt(string member, T)() @trusted
788 	{
789 		if (isJson)
790 			return deserializeJson!T(j[member]);
791 		else
792 			return ETFBuffer.deserialize!T(e[member].bufferStart, false);
793 	}
794 }
795 
796 enum OpCode
797 {
798 	dispatch,
799 	heartbeat,
800 	identify,
801 	statusUpdate,
802 	voiceStatusUpdate,
803 	voiceServerPing,
804 	resume,
805 	reconnect,
806 	requestGuildMembers,
807 	invalidSession,
808 	hello,
809 	heartbeatAck
810 }
811 
812 struct OpHello
813 {
814 	mixin OptionalSerializer!(typeof(this));
815 
816 	int heartbeat_interval;
817 	string[] _trace;
818 }
819 
820 struct OpIdentify
821 {
822 	mixin OptionalSerializer!(typeof(this));
823 
824 	string token;
825 	string[string] properties;
826 	bool compress;
827 	int large_threshold = 100;
828 	int[2] shard = [0, 1];
829 	UpdateStatus presence;
830 }
831 
832 struct ResumeData
833 {
834 	mixin OptionalSerializer!(typeof(this));
835 
836 	string token;
837 	string session_id;
838 	int seq;
839 }
840 
841 struct ReadyPacket
842 {
843 	mixin OptionalSerializer!(typeof(this));
844 
845 	int v;
846 	User user;
847 	Channel[] private_channels;
848 	UnavailableGuild[] guilds;
849 	string session_id;
850 	string[] _trace;
851 }
852 
853 struct ResumedPacket
854 {
855 	mixin OptionalSerializer!(typeof(this));
856 
857 	string[] _trace;
858 }
859 
860 struct ChannelPinsUpdatePacket
861 {
862 	mixin OptionalSerializer!(typeof(this));
863 
864 	Snowflake channel_id;
865 	@optional SafeTime last_pin_timestamp;
866 }
867 
868 struct GuildEmojisUpdatePacket
869 {
870 	mixin OptionalSerializer!(typeof(this));
871 
872 	Snowflake guild_id;
873 	Emoji[] emojis;
874 }
875 
876 struct GuildIntegrationsUpdatePacket
877 {
878 	mixin OptionalSerializer!(typeof(this));
879 
880 	Snowflake guild_id;
881 }
882 
883 struct GuildMemberRemovePacket
884 {
885 	mixin OptionalSerializer!(typeof(this));
886 
887 	Snowflake guild_id;
888 	User user;
889 }
890 
891 struct GuildMemberUpdatePacket
892 {
893 	mixin OptionalSerializer!(typeof(this));
894 
895 	Snowflake guild_id;
896 	Snowflake[] roles;
897 	User user;
898 	string nick;
899 }
900 
901 struct GuildMembersChunkPacket
902 {
903 	mixin OptionalSerializer!(typeof(this));
904 
905 	Snowflake guild_id;
906 	GuildMember[] members;
907 }
908 
909 struct GuildRoleCreatePacket
910 {
911 	mixin OptionalSerializer!(typeof(this));
912 
913 	Snowflake guild_id;
914 	Role role;
915 }
916 
917 struct GuildRoleUpdatePacket
918 {
919 	mixin OptionalSerializer!(typeof(this));
920 
921 	Snowflake guild_id;
922 	Role role;
923 }
924 
925 struct GuildRoleDeletePacket
926 {
927 	mixin OptionalSerializer!(typeof(this));
928 
929 	Snowflake guild_id;
930 	Snowflake role_id;
931 }
932 
933 struct MessageDeletePacket
934 {
935 	mixin OptionalSerializer!(typeof(this));
936 
937 	Snowflake id;
938 	Snowflake channel_id;
939 }
940 
941 struct MessageDeleteBulkPacket
942 {
943 	mixin OptionalSerializer!(typeof(this));
944 
945 	Snowflake[] ids;
946 	Snowflake channel_id;
947 }
948 
949 struct MessageReactionAddPacket
950 {
951 	mixin OptionalSerializer!(typeof(this));
952 
953 	Snowflake user_id;
954 	Snowflake channel_id;
955 	Snowflake message_id;
956 	Emoji emoji;
957 }
958 
959 struct MessageReactionRemovePacket
960 {
961 	mixin OptionalSerializer!(typeof(this));
962 
963 	Snowflake user_id;
964 	Snowflake channel_id;
965 	Snowflake message_id;
966 	Emoji emoji;
967 }
968 
969 struct MessageReactionRemoveAllPacket
970 {
971 	mixin OptionalSerializer!(typeof(this));
972 
973 	Snowflake channel_id;
974 	Snowflake message_id;
975 }
976 
977 struct TypingStartPacket
978 {
979 	mixin OptionalSerializer!(typeof(this));
980 
981 	Snowflake channel_id;
982 	Snowflake user_id;
983 	long timestamp;
984 }
985 
986 struct VoiceServerUpdatePacket
987 {
988 	mixin OptionalSerializer!(typeof(this));
989 
990 	string token;
991 	Snowflake guild_id;
992 	string endpoint;
993 }
994 
995 struct WebhooksUpdatePacket
996 {
997 	mixin OptionalSerializer!(typeof(this));
998 
999 	Snowflake guild_id;
1000 	Snowflake channel_id;
1001 }
1002 
1003 struct UpdateVoiceStateCommand
1004 {
1005 	mixin OptionalSerializer!(typeof(this));
1006 
1007 	Snowflake guild_id;
1008 	Nullable!Snowflake channel_id;
1009 	bool self_mute;
1010 	bool self_deaf;
1011 }
1012 
1013 struct RequestGuildMembersCommand
1014 {
1015 	mixin OptionalSerializer!(typeof(this));
1016 
1017 	Snowflake guild_id;
1018 	string query;
1019 	int limit = 0;
1020 }