diff --git a/Configuration.cs b/Configuration.cs index 35b1359..fcd8a32 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -16,6 +16,8 @@ namespace TwitchChatTTS public class TwitchConfiguration { public IEnumerable? Channels; public bool TtsWhenOffline; + public string? WebsocketUrl; + public string? ApiUrl; } public class OBSConfiguration { diff --git a/Hermes/Socket/HermesSocketClient.cs b/Hermes/Socket/HermesSocketClient.cs index c7e486f..2b2d215 100644 --- a/Hermes/Socket/HermesSocketClient.cs +++ b/Hermes/Socket/HermesSocketClient.cs @@ -360,6 +360,9 @@ namespace TwitchChatTTS.Hermes.Socket catch (Exception ex) { _logger.Error(ex, "Failed to disconnect from Hermes websocket server."); + Ready = false; + LoggedIn = false; + Connected = false; } UserId = null; _heartbeatTimer.Enabled = false; @@ -381,6 +384,9 @@ namespace TwitchChatTTS.Hermes.Socket catch (Exception ex) { _logger.Error(ex, "Failed to disconnect from Hermes websocket server."); + Ready = false; + LoggedIn = false; + Connected = false; } } diff --git a/Twitch/Socket/TwitchConnectionManager.cs b/Twitch/Socket/TwitchConnectionManager.cs index 40ab445..11ef886 100644 --- a/Twitch/Socket/TwitchConnectionManager.cs +++ b/Twitch/Socket/TwitchConnectionManager.cs @@ -94,18 +94,20 @@ namespace TwitchChatTTS.Twitch.Socket if (clientDisconnect) await client.DisconnectAsync(new SocketDisconnectionEventArgs("Closed", "No need for a tertiary client.")); }; - client.OnDisconnected += (s, e) => + client.OnDisconnected += async (s, e) => { + bool reconnecting = false; lock (_lock) { if (_identified?.UID == client.UID) { - _logger.Debug($"Identified Twitch client has disconnected [client: {client.UID}][main: {_identified.UID}][backup: {_backup?.UID}]"); + _logger.Warning($"Identified Twitch client has disconnected [client: {client.UID}][main: {_identified.UID}][backup: {_backup?.UID}]"); _identified = null; + reconnecting = true; } else if (_backup?.UID == client.UID) { - _logger.Debug($"Backup Twitch client has disconnected [client: {client.UID}][main: {_identified?.UID}][backup: {_backup.UID}]"); + _logger.Warning($"Backup Twitch client has disconnected [client: {client.UID}][main: {_identified?.UID}][backup: {_backup.UID}]"); _backup = null; } else if (client.ReceivedReconnecting) @@ -115,6 +117,12 @@ namespace TwitchChatTTS.Twitch.Socket else _logger.Error($"Twitch client disconnected from unknown source [client: {client.UID}][main: {_identified?.UID}][backup: {_backup?.UID}]"); } + + if (reconnecting) + { + var client = GetWorkingClient(); + await client.Connect(); + } }; _logger.Debug("Created a Twitch websocket client."); diff --git a/Twitch/Socket/TwitchWebsocketClient.cs b/Twitch/Socket/TwitchWebsocketClient.cs index 20e35c8..f641b2a 100644 --- a/Twitch/Socket/TwitchWebsocketClient.cs +++ b/Twitch/Socket/TwitchWebsocketClient.cs @@ -16,6 +16,7 @@ namespace TwitchChatTTS.Twitch.Socket private readonly IDictionary _messageTypes; private readonly IDictionary _subscriptions; private readonly IBackoff _backoff; + private readonly Configuration _configuration; private DateTime _lastReceivedMessageTimestamp; private bool _disconnected; private readonly object _lock; @@ -33,6 +34,7 @@ namespace TwitchChatTTS.Twitch.Socket public TwitchWebsocketClient( [FromKeyedServices("twitch")] IEnumerable handlers, [FromKeyedServices("twitch")] IBackoff backoff, + Configuration configuration, ILogger logger ) : base(logger, new JsonSerializerOptions() { @@ -42,6 +44,7 @@ namespace TwitchChatTTS.Twitch.Socket { _handlers = handlers.ToDictionary(h => h.Name, h => h); _backoff = backoff; + _configuration = configuration; _subscriptions = new Dictionary(); _lock = new object(); @@ -52,7 +55,11 @@ namespace TwitchChatTTS.Twitch.Socket _messageTypes.Add("notification", typeof(NotificationMessage)); UID = Guid.NewGuid().ToString("D"); - URL = "wss://eventsub.wss.twitch.tv/ws"; + + if (_configuration.Environment == "PROD" || string.IsNullOrWhiteSpace(_configuration.Twitch?.WebsocketUrl)) + URL = "wss://eventsub.wss.twitch.tv/ws"; + else + URL = _configuration.Twitch.WebsocketUrl; } @@ -96,16 +103,10 @@ namespace TwitchChatTTS.Twitch.Socket _disconnected = true; } - _logger.Information($"Twitch websocket client disconnected [status: {e.Status}][reason: {e.Reason}]"); + _logger.Information($"Twitch websocket client disconnected [status: {e.Status}][reason: {e.Reason}][client: {UID}]"); Connected = false; Identified = false; - - if (!ReceivedReconnecting) - { - _logger.Information("Attempting to reconnect to Twitch websocket server."); - await Reconnect(_backoff, async () => await Connect()); - } }; } diff --git a/Twitch/TwitchApiClient.cs b/Twitch/TwitchApiClient.cs index cd113d8..0070100 100644 --- a/Twitch/TwitchApiClient.cs +++ b/Twitch/TwitchApiClient.cs @@ -4,17 +4,21 @@ using Serilog; using TwitchChatTTS.Twitch.Socket.Messages; using System.Net.Http.Json; using System.Net; +using TwitchChatTTS; public class TwitchApiClient { + private readonly Configuration _configuration; private readonly ILogger _logger; private readonly WebClientWrap _web; public TwitchApiClient( + Configuration configuration, ILogger logger ) { + _configuration = configuration; _logger = logger; _web = new WebClientWrap(new JsonSerializerOptions() @@ -28,19 +32,23 @@ public class TwitchApiClient { var conditions = new Dictionary() { { "user_id", userId }, { "broadcaster_user_id", broadcasterId ?? userId }, { "moderator_user_id", broadcasterId ?? userId } }; var subscriptionData = new EventSubscriptionMessage(type, version, sessionId, conditions); - var response = await _web.Post("https://api.twitch.tv/helix/eventsub/subscriptions", subscriptionData); + var base_url = _configuration.Environment == "PROD" || string.IsNullOrWhiteSpace(_configuration.Twitch?.ApiUrl) + ? "https://api.twitch.tv/helix" : _configuration.Twitch.ApiUrl; + var response = await _web.Post($"{base_url}/eventsub/subscriptions", subscriptionData); if (response.StatusCode == HttpStatusCode.Accepted) { - _logger.Debug("Twitch API call [type: create event subscription]: " + await response.Content.ReadAsStringAsync()); + _logger.Debug($"Twitch API call [type: create event subscription][subscription type: {type}][response: {await response.Content.ReadAsStringAsync()}]"); return await response.Content.ReadFromJsonAsync(typeof(EventResponse)) as EventResponse; } - _logger.Error("Twitch api failed to create event subscription for websocket: " + await response.Content.ReadAsStringAsync()); + _logger.Error($"Twitch API call failed [type: create event subscription][subscription type: {type}][response: {await response.Content.ReadAsStringAsync()}]"); return null; } public async Task DeleteEventSubscription(string subscriptionId) { - await _web.Delete("https://api.twitch.tv/helix/eventsub/subscriptions?id=" + subscriptionId); + var base_url = _configuration.Environment == "PROD" || string.IsNullOrWhiteSpace(_configuration.Twitch?.ApiUrl) + ? "https://api.twitch.tv/helix" : _configuration.Twitch.ApiUrl; + await _web.Delete($"{base_url}/eventsub/subscriptions?id=" + subscriptionId); } public async Task?> GetSubscriptions(string? status = null, string? broadcasterId = null, string? after = null)