diff --git a/Discord.API/Gateway/GatewayClient.cs b/Discord.API/Gateway/GatewayClient.cs new file mode 100644 index 0000000..772fe21 --- /dev/null +++ b/Discord.API/Gateway/GatewayClient.cs @@ -0,0 +1,76 @@ +using System.Collections.Specialized; +using System.Text.Json; +using Serilog; +using Websocket.Client; + +namespace Discord.API; + +public class GatewayClient { + + private const int ApiVersion = 10; + + private string ApiKey; + private WebsocketClient Websocket; + private CancellationTokenSource? heartbeat_cts; + private ulong? Sequence = null; + + public GatewayClient(string url, string api_key){ + this.ApiKey=api_key; + UriBuilder uriBuilder = new(url); + NameValueCollection query = System.Web.HttpUtility.ParseQueryString(""); + query.Add("v", ApiVersion.ToString()); + query.Add("encoding", "json"); + uriBuilder.Query=query.ToString(); + Websocket = new WebsocketClient(uriBuilder.Uri); + Websocket.DisconnectionHappened.Subscribe(DisconnectionHandler); + Websocket.ReconnectionHappened.Subscribe(ReconnectionHandler); + Websocket.MessageReceived.Subscribe(MessageReceivedHandler); + + Log.Debug("GATEWAY: Created new gateway, with url: {url}", uriBuilder.ToString()); + } + + private void DisconnectionHandler(DisconnectionInfo info){ + Log.Information("GATEWAY: Disconnected. Type: {DisconnectionType}", info.Type); + } + private void ReconnectionHandler(ReconnectionInfo info){ + Log.Information("GATEWAY: (Re)Connected to server. Url: {url}, Type: {Type}", Websocket.Url, info.Type); + } + private void MessageReceivedHandler(ResponseMessage msg){ + if(msg.MessageType != System.Net.WebSockets.WebSocketMessageType.Text) return; + try{ + GatewayPacket packet = JsonSerializer.Deserialize(msg.Text!, SourceGenerationContext.Default.GatewayPacket) + ?? throw new Exception("Failed to deserialize packet"); // This can be optimized //TODO + + switch(packet){ + case HelloPacket helloPacket: + HelloPacketHandler(helloPacket); + break; + } + }catch(Exception ex){ + Log.Warning(ex, "GATEWAY: Error processing gateway event"); + } + } + + private void HelloPacketHandler(HelloPacket packet){ + StartHeartbeat((int)packet.Data.HeartbeatInterval); + } + + private void StartHeartbeat(int heartbeat_interval){ + heartbeat_cts?.Cancel(); + heartbeat_cts = new CancellationTokenSource(); + CancellationToken ct = heartbeat_cts.Token; + Task.Run(async ()=>{ + await Task.Delay(Random.Shared.Next(1, heartbeat_interval)); + using PeriodicTimer pd = new(TimeSpan.FromMilliseconds(heartbeat_interval)); + do{ + HeartbeatPacket packet = new HeartbeatPacket(){ + Sequence=this.Sequence + }; + if(Websocket.IsRunning) + if(!Websocket.Send(JsonSerializer.Serialize(packet, SourceGenerationContext.Default.HeartbeatPacket))){ + Log.Warning("GATEWAY: Failed to queue heartbeat message"); + } + }while(await pd.WaitForNextTickAsync(ct) && !ct.IsCancellationRequested); + }); + } +}