public override string ToString() { string res = "[[[\nOp " + OpCode + " (" + (int)OpCode + ")"; if (EventName != null) { res += "\nSeq " + Sequence + "\nEvt " + EventName; } res += "\nData " + DiscordDebug.JsonString(Data) + "\n]]]"; return(res); }
private async Task <Server[]> HandshakeAsync(bool reconnect, bool resumable, GatewaySocket socket, IRef <bool> loop) { DiscordDebug.WriteLine("Getting Hello data..."); ReceiveGatewayData data = await socket.ReadAsync(); DiscordDebug.WriteLine("Got Hello data."); if (reconnect) { _listener.UpdateSocket(socket); } else { DiscordDebug.WriteLine("Initializing response listener and heartbeater..."); _listener = new ResponseListener(socket); _heartbeater = new GatewayHeartbeater(_listener, _seqMutex, () => _lastSequence, ConnectAsync); DiscordDebug.WriteLine("Response listener and heartbeater initialized."); } // Need to handle cases where heartbeat timer elapses, thus the event/semaphore model DiscordDebug.WriteLine("Waiting for initial heartbeat ack..."); var signal = new Semaphore(0, 1); bool success = false; void EvtListener(object sender, bool e) { success = e; signal.Release(); } _heartbeater.Started += EvtListener; _heartbeater.Start(data, reconnect); signal.WaitOne(); _heartbeater.Started -= EvtListener; if (success) { DiscordDebug.WriteLine("Heartbeat ack heard."); return(await IdentAsync(reconnect, resumable)); // ReSharper disable once RedundantIfElseBlock } else { DiscordDebug.WriteLine("Heartbeat ack not heard, trying again..."); // Reconnect immediately await _heartbeater.AbortAsync(); _heartbeater.Dispose(); _listener.Dispose(); loop.Value = true; return(null); } }
private void GuildCreate(ReceiveGatewayData data) { var d = (GatewayEvent.EvtServer)data.Data; _asMutex.Wait(); try { DiscordDebug.WriteLine("GUILD_CREATE " + d.Data.Id + " (" + (_awaitServers.Count - 1) + " remaining)"); _awaitServers.Remove(d.Data.Id); if (_awaitServers.Count == 0) { _asDone.Release(); } } finally { _asMutex.Release(); } }
internal async Task <Server[]> ConnectAsync(bool reconnect, bool resumable) { if (reconnect) { _dispatcher?.TearDown(); } var loop = new SharedRef <bool>(false); int loops = 0; DiscordDebug.WriteLine("Starting connection appempt..."); Server[] res = null; do { if (loops >= _maxConnectAttempts) { throw new GatewayConnectException("Gateway failed to connect " + _maxConnectAttempts + " times, and the connection attempt was aborted."); } loop.Value = false; await _connectMutex.WaitAsync(); if (_listener == null) { string url = await GetGatewayUrlAsync(); DiscordDebug.WriteLine("Opening socket..."); var socket = new GatewaySocket(this); socket.Connect(url); if (socket.State == WebSocketState.Open) { DiscordDebug.WriteLine("Socket opened."); res = await HandshakeAsync(reconnect, resumable, socket, loop); } else { DiscordDebug.WriteLine("Socket opening failed, trying again..."); loop.Value = true; } } _connectMutex.Release(); loops++; } while (loop.Value); DiscordDebug.WriteLine("Connection attempt done."); // ReSharper disable once AssignNullToNotNullAttribute (null value is default to get compiler to shut up - whenever res is null, the loop should continue) return(res); }
public static object Create(string eventName, object data) { object res; if (Ready.EventIsThis(eventName)) { res = new Ready((JObject)data); } else if (Resumed.EventIsThis(eventName)) { res = new Resumed((JObject)data); } else if (InvalidSession.EventIsThis(eventName)) { res = (bool)((JValue)data).Value; } else if (Channel.EventIsThis(eventName)) { res = new Channel((JObject)data); } else if (ChannelPinsUpdate.EventIsThis(eventName)) { res = new ChannelPinsUpdate((JObject)data); } else if (EvtServer.EventIsThis(eventName)) { res = new EvtServer((JObject)data); } else if (ServerDelete.EventIsThis(eventName)) { res = ((JObject)data).ToObject <UaServerJson>().id; } else if (ServerUser.EventIsThis(eventName)) { res = new ServerUser((JObject)data, eventName == ServerUser.MemberRemoveId); } else if (ServerUserSet.EventIsThis(eventName)) { res = new ServerUserSet((JObject)data); } else if (ServerUserUpdate.EventIsThis(eventName)) { res = new ServerUserUpdate((JObject)data); } else if (ServerEmojiUpdate.EventIsThis(eventName)) { res = new ServerEmojiUpdate((JObject)data); } else if (RawServer.EventIsThis(eventName)) { res = new RawServer((JObject)data); } else if (ServerRole.EventIsThis(eventName)) { res = new ServerRole((JObject)data); } else if (ServerRoleDelete.EventIsThis(eventName)) { res = new ServerRoleDelete((JObject)data); } else if (Message.EventIsThis(eventName)) { res = new Message((JObject)data, eventName == Message.UpdateId); } else if (RawMessage.EventIsThis(eventName)) { res = new RawMessage((JObject)data, eventName == RawMessage.BulkDeleteId); } else if (Reaction.EventIsThis(eventName)) { res = new Reaction((JObject)data); } else if (ReactionRemoveAll.EventIsThis(eventName)) { res = new ReactionRemoveAll((JObject)data); } else if (Webhook.EventIsThis(eventName)) { res = new Webhook((JObject)data); } else if (EvtUser.EventIsThis(eventName)) { res = new EvtUser((JObject)data); } else if (Unimplemented.EventIsThis(eventName)) { res = Unimplemented.Instance; } else { throw new UnknownEventException("Gateway event \"" + eventName + "\" is unknown."); } DiscordDebug.WriteLine("Heard event: " + eventName + ", handling w/ object " + res.GetType().Name); return(res); }
private async Task <Server[]> IdentAsync(bool reconnect, bool resumable) { bool ident = true; if (reconnect && resumable) { ReceiveGatewayData resumed = await _listener.SendAsync(SendGatewayData.Create(GatewayOpCode.Resume, MakeResume()), new[] { GatewayOpCode.Dispatch, GatewayOpCode.InvalidSession }, GatewayEvent.Resumed.Id); ident = resumed.OpCode == GatewayOpCode.InvalidSession; if (ident) { // Per specs, client must wait 1-5 seconds before sending a fresh Identify payload (https://discordapp.com/developers/docs/topics/gateway#resuming) await Task.Delay(2000); } } await _asMutex.WaitAsync(); bool needRelease = true; Server[] res = null; try { while (ident) { DiscordDebug.WriteLine("Identing..."); ReceiveGatewayData ready = await _listener.SendAsync(SendGatewayData.Create(GatewayOpCode.Identify, (JObject)MakeIdentify()), new[] { GatewayOpCode.Dispatch, GatewayOpCode.InvalidSession }, GatewayEvent.Ready.Id); ident = ready.OpCode == GatewayOpCode.InvalidSession; if (ident) { DiscordDebug.WriteLine("Ident rate-limited, waiting and trying again..."); // Per specs, the only case in which this occurs is when you hit the 5-second Identify rate limit (https://discordapp.com/developers/docs/topics/gateway#identifying) await Task.Delay(5000); } else { DiscordDebug.WriteLine("Ident successful, copying session data..."); var readyData = (GatewayEvent.Ready)ready.Data; _sessionId = readyData.SessionId; DiscordDebug.WriteLine("Session data copied."); DiscordDebug.WriteLine("Awaiting server create events (expecting [" + string.Join(", ", readyData.ServersToCreate) + "] (" + readyData.ServersToCreate.Count + "))..."); _awaitServers = readyData.ServersToCreate; _listener.Listen(GuildCreate, _awaitServers.Count, GatewayOpCode.Dispatch, GatewayEvent.EvtServer.CreateId); _asDone = new Semaphore(0, 1); _asMutex.Release(); needRelease = false; res = readyData.ServersToCreate.Select(Server.GetCached).ToArray(); _asDone.WaitOne(); DiscordDebug.WriteLine("All expected server create events have occured."); } DiscordDebug.WriteLine("Ident done."); } } finally { if (needRelease) { _asMutex.Release(); } } DiscordDebug.WriteLine("Setting up event listener..."); _dispatcher = new Dispatcher(this, _listener); DiscordDebug.WriteLine("Event listener set up."); DiscordDebug.WriteLine("Socket ready to use"); // ReSharper disable once AssignNullToNotNullAttribute return(res); }