private async Task VoiceWS_SocketMessage(IWebSocketClient client, SocketMessageEventArgs e) { if (!(e is SocketTextMessageEventArgs et)) { this.Discord.Logger.LogCritical(VoiceNextEvents.VoiceGatewayError, "Discord Voice Gateway sent binary data - unable to process"); return; } if (this.Discord.Configuration.MinimumLogLevel == LogLevel.Trace) { var cs = new MemoryStream(); await et.Message.CopyToAsync(cs).ConfigureAwait(false); cs.Seek(0, SeekOrigin.Begin); et.Message.Seek(0, SeekOrigin.Begin); using var sr = new StreamReader(cs, Utilities.UTF8); this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceWsRx, await sr.ReadToEndAsync().ConfigureAwait(false)); } var j = await DiscordJson.LoadJObjectAsync(et.Message).ConfigureAwait(false); await this.HandleDispatch(j).ConfigureAwait(false); }
private async Task <GatewayInfo> GetGatewayInfoAsync() { var url = $"{Utilities.GetApiBaseUri()}{Endpoints.GATEWAY}{Endpoints.BOT}"; var http = new HttpClient(); http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Utilities.GetUserAgent()); http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", Utilities.GetFormattedToken(this.Configuration)); this.Logger.LogDebug(LoggerEvents.ShardRest, $"Obtaining gateway information from GET {Endpoints.GATEWAY}{Endpoints.BOT}..."); var resp = await http.GetAsync(url).ConfigureAwait(false); http.Dispose(); if (!resp.IsSuccessStatusCode) { var ratelimited = await HandleHttpError(url, resp).ConfigureAwait(false); if (ratelimited) { return(await this.GetGatewayInfoAsync().ConfigureAwait(false)); } } var timer = new Stopwatch(); timer.Start(); var jo = await DiscordJson.LoadJObjectAsync(await resp.Content.ReadAsStreamAsync().ConfigureAwait(false)).ConfigureAwait(false); var info = jo.ToObject <GatewayInfo>(); //There is a delay from parsing here. timer.Stop(); info.SessionBucket.resetAfter -= (int)timer.ElapsedMilliseconds; info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.resetAfter); return(info); async Task <bool> HandleHttpError(string reqUrl, HttpResponseMessage msg) { var code = (int)msg.StatusCode; if (code == 401 || code == 403) { throw new Exception($"Authentication failed, check your token and try again: {code} {msg.ReasonPhrase}"); } else if (code == 429) { this.Logger.LogError(LoggerEvents.ShardClientError, $"Ratelimit hit, requeuing request to {reqUrl}"); var hs = msg.Headers.ToDictionary(xh => xh.Key, xh => string.Join("\n", xh.Value), StringComparer.OrdinalIgnoreCase); var waitInterval = 0; if (hs.TryGetValue("Retry-After", out var retry_after_raw)) { waitInterval = int.Parse(retry_after_raw, CultureInfo.InvariantCulture); } await Task.Delay(waitInterval).ConfigureAwait(false); return(true); } else if (code >= 500) { throw new Exception($"Internal Server Error: {code} {msg.ReasonPhrase}"); } else { throw new Exception($"An unsuccessful HTTP status code was encountered: {code} {msg.ReasonPhrase}"); } } }
private async Task WebSocket_OnMessage(IWebSocketClient client, SocketMessageEventArgs e) { if (!(e is SocketTextMessageEventArgs et)) { this.Discord.Logger.LogCritical(LavalinkEvents.LavalinkConnectionError, "Lavalink sent binary data - unable to process"); return; } if (this.Discord.Configuration.MinimumLogLevel == LogLevel.Trace) { var cs = new MemoryStream(); await et.Message.CopyToAsync(cs).ConfigureAwait(false); cs.Seek(0, SeekOrigin.Begin); et.Message.Seek(0, SeekOrigin.Begin); using var sr = new StreamReader(cs, Utilities.UTF8); this.Discord.Logger.LogTrace(LavalinkEvents.LavalinkWsRx, await sr.ReadToEndAsync().ConfigureAwait(false)); } var json = et.Message; var jsonData = await DiscordJson.LoadJObjectAsync(json).ConfigureAwait(false); switch (jsonData["op"].ToString()) { case "playerUpdate": var gid = (ulong)jsonData["guildId"]; var state = jsonData["state"].ToObject <LavalinkState>(); if (this._connectedGuilds.TryGetValue(gid, out var lvl)) { await lvl.InternalUpdatePlayerStateAsync(state).ConfigureAwait(false); } break; case "stats": var statsRaw = jsonData.ToObject <LavalinkStats>(); this.Statistics.Update(statsRaw); await this._statsReceived.InvokeAsync(this, new StatisticsReceivedEventArgs(this.Statistics)).ConfigureAwait(false); break; case "event": var evtype = jsonData["type"].ToObject <EventType>(); var guildId = (ulong)jsonData["guildId"]; switch (evtype) { case EventType.TrackStartEvent: if (this._connectedGuilds.TryGetValue(guildId, out var lvl_evtst)) { await lvl_evtst.InternalPlaybackStartedAsync(jsonData["track"].ToString()).ConfigureAwait(false); } break; case EventType.TrackEndEvent: TrackEndReason reason = TrackEndReason.Cleanup; switch (jsonData["reason"].ToString()) { case "FINISHED": reason = TrackEndReason.Finished; break; case "LOAD_FAILED": reason = TrackEndReason.LoadFailed; break; case "STOPPED": reason = TrackEndReason.Stopped; break; case "REPLACED": reason = TrackEndReason.Replaced; break; case "CLEANUP": reason = TrackEndReason.Cleanup; break; } if (this._connectedGuilds.TryGetValue(guildId, out var lvl_evtf)) { await lvl_evtf.InternalPlaybackFinishedAsync(new TrackFinishData { Track = jsonData["track"].ToString(), Reason = reason }).ConfigureAwait(false); } break; case EventType.TrackStuckEvent: if (this._connectedGuilds.TryGetValue(guildId, out var lvl_evts)) { await lvl_evts.InternalTrackStuckAsync(new TrackStuckData { Track = jsonData["track"].ToString(), Threshold = (long)jsonData["thresholdMs"] }).ConfigureAwait(false); } break; case EventType.TrackExceptionEvent: if (this._connectedGuilds.TryGetValue(guildId, out var lvl_evte)) { await lvl_evte.InternalTrackExceptionAsync(new TrackExceptionData { Track = jsonData["track"].ToString(), Error = jsonData["error"].ToString() }).ConfigureAwait(false); } break; case EventType.WebSocketClosedEvent: if (this._connectedGuilds.TryGetValue(guildId, out var lvl_ewsce)) { lvl_ewsce.VoiceWsDisconnectTcs.SetResult(true); await lvl_ewsce.InternalWebSocketClosedAsync(new WebSocketCloseEventArgs(jsonData["code"].ToObject <int>(), jsonData["reason"].ToString(), jsonData["byRemote"].ToObject <bool>())).ConfigureAwait(false); } break; } break; } }