using Maki.Structures.Gateway; using Newtonsoft.Json; using System; using WebSocketSharp; namespace Maki.Gateway { /// /// Gateway connection shard /// class GatewayShard : IDisposable { private const string GATEWAY_URL = "{0}?v={1}&encoding=json"; private readonly string GatewayUrl; /// /// Session key for continuing a resuming after disconnecting /// private string Session; /// /// Websocket container /// private WebSocket WebSocket; /// /// Interval at which heartbeats are sent /// public TimeSpan HeartbeatInterval { get; private set; } /// /// Last sequence received from the gateway, primarily used for resuming connections and getting the data that was missed /// public int? LastSequence { get; private set; } = null; /// /// Identifier for this Shard /// public readonly int Id; /// /// Parent DiscordClient instance /// private readonly Discord Client; /// /// Heartbeat handler /// private readonly GatewayHeartbeatManager HeartbeatHandler; /// /// Fires when a payload is received. /// public event Action OnSocketPayload; /// /// Constructor /// /// Shard Id /// Parent DiscordClient instance /// Whether to immediately call Connect() public GatewayShard(int id, Discord client, bool connect = true) { Id = id; Client = client; HeartbeatHandler = new GatewayHeartbeatManager(this); GatewayUrl = string.Format(GATEWAY_URL, Client.Gateway, Discord.GATEWAY_VERSION); if (connect) Connect(); } /// /// Event handler for WebSocketSharp's OnClose event, stops heartbeats and reconnects if the close wasn't clean /// /// Sender object /// Event arguments private void WebSocket_OnClose(object sender, CloseEventArgs e) { HeartbeatHandler.Stop(); if (!e.WasClean || e.Code != 1000) Connect(); } /// /// Event handler for WebSocketSharp's OnOpen event, handles gateway instructions and forwards to our events /// /// Sender object /// Event arguments private void WebSocket_OnMessage(object sender, MessageEventArgs e) { // ignore non-text messages if (!e.IsText) return; Console.WriteLine(e.Data.Replace(Client.Token, new string('*', Client.Token.Length))); GatewayPayload payload = JsonConvert.DeserializeObject(e.Data); OnSocketPayload?.Invoke(this, payload); switch (payload.OPCode) { case GatewayOPCode.Dispatch: LastSequence = payload.Sequence; if (payload.Name == "READY") { GatewayReady ready = payload.DataAs(); Session = ready.Session; } break; case GatewayOPCode.Hello: GatewayHello hello = payload.DataAs(); HeartbeatInterval = TimeSpan.FromMilliseconds(hello.HeartbeatInterval); HeartbeatHandler.Start(); if (string.IsNullOrEmpty(Session)) Send(GatewayOPCode.Identify, new GatewayIdentification { Token = Client.Token, Compress = false, LargeThreshold = 250, Shard = new int[] { Id, Client.ShardClient.ShardCount }, Properties = new GatewayIdentificationProperties { OperatingSystem = "windows", Browser = "Maki", Device = "Maki", Referrer = string.Empty, ReferringDomain = string.Empty, } }); else Send(GatewayOPCode.Resume, new GatewayResume { LastSequence = LastSequence ?? default(int), Token = Client.Token, Session = Session, }); break; } } #region IDisposable private bool IsDisposed = false; private void Dispose(bool disposing) { if (IsDisposed) return; IsDisposed = true; Disconnect(); if (disposing) GC.SuppressFinalize(this); } ~GatewayShard() => Dispose(false); public void Dispose() => Dispose(true); #endregion /// /// Connects to the gateway /// public void Connect() { WebSocket = new WebSocket(GatewayUrl); // make wss not log anything on its own WebSocket.Log.Output = (LogData logData, string path) => { }; WebSocket.OnClose += WebSocket_OnClose; WebSocket.OnMessage += WebSocket_OnMessage; WebSocket.Connect(); } /// /// Stops heartbeats and closes gateway connection for this shard /// public void Disconnect() { HeartbeatHandler.Stop(); if (WebSocket.ReadyState != WebSocketState.Closed) WebSocket?.Close(CloseStatusCode.Normal); } /// /// Serialises and sends a json object /// /// Type to serialise as /// Data to serialise and send public void Send(T data) { string json = JsonConvert.SerializeObject(data, typeof(T), new JsonSerializerSettings()); Console.WriteLine(json.Replace(Client.Token, new string('*', Client.Token.Length))); WebSocket.Send(json); } /// /// Serialises and sends a Gateway Payload /// /// Opcode to use /// Data to send public void Send(GatewayOPCode opcode, object data) => Send(new GatewayPayload { OPCode = opcode, Data = data }); } }