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 }