From 64cb0c1f6d97da0513dcdf0f32e79bf2ab2f5695 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 7 Jan 2025 15:30:13 +0000 Subject: [PATCH] Added missing websocket support for Redemptions and Actions. Fixed Ad Break actions. Cleaned some code. --- Hermes/Socket/HermesSocketClient.cs | 31 +++++++++++++-- Hermes/Socket/Requests/CreateRedemptionAck.cs | 35 +++++++++++++++++ Hermes/Socket/Requests/CreateTTSUserAck.cs | 2 +- Hermes/Socket/Requests/CreateTTSVoiceAck.cs | 2 +- .../Requests/DeleteRedeemableActionAck.cs | 39 +++++++++++++++++++ Hermes/Socket/Requests/DeleteRedemptionAck.cs | 39 +++++++++++++++++++ Hermes/Socket/Requests/DeleteTTSFilterAck.cs | 2 +- Hermes/Socket/Requests/DeleteTTSVoiceAck.cs | 2 +- .../Requests/GetRedeemableActionsAck.cs | 3 +- Hermes/Socket/Requests/GetTTSUsersAck.cs | 2 +- Hermes/Socket/Requests/RequestAckManager.cs | 2 +- .../Requests/UpdateRedeemableActionAck.cs | 6 ++- .../Socket/Requests/UpdateRedeemptionAck.cs | 37 ++++++++++++++++++ Hermes/Socket/Requests/UpdateTTSFilterAck.cs | 7 ++-- Startup.cs | 9 +++++ Twitch/Redemptions/RedemptionManager.cs | 8 +--- .../Handlers/ChannelAdBreakBeginHandler.cs | 37 +++++++++++------- 17 files changed, 227 insertions(+), 36 deletions(-) create mode 100644 Hermes/Socket/Requests/CreateRedemptionAck.cs create mode 100644 Hermes/Socket/Requests/DeleteRedeemableActionAck.cs create mode 100644 Hermes/Socket/Requests/DeleteRedemptionAck.cs create mode 100644 Hermes/Socket/Requests/UpdateRedeemptionAck.cs diff --git a/Hermes/Socket/HermesSocketClient.cs b/Hermes/Socket/HermesSocketClient.cs index 78073ea..3c20670 100644 --- a/Hermes/Socket/HermesSocketClient.cs +++ b/Hermes/Socket/HermesSocketClient.cs @@ -1,4 +1,3 @@ -using System.Net.WebSockets; using System.Text.Json; using System.Timers; using CommonSocketLibrary.Abstract; @@ -9,6 +8,7 @@ using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Socket.Data; using Microsoft.Extensions.DependencyInjection; using Serilog; +using TwitchChatTTS.Bus; using TwitchChatTTS.Hermes.Socket.Handlers; namespace TwitchChatTTS.Hermes.Socket @@ -19,6 +19,7 @@ namespace TwitchChatTTS.Hermes.Socket private readonly User _user; private readonly Configuration _configuration; + private readonly ServiceBusCentral _bus; private readonly ICallbackManager _callbackManager; private string URL; @@ -33,10 +34,13 @@ namespace TwitchChatTTS.Hermes.Socket public bool LoggedIn { get; set; } public bool Ready { get; set; } + private bool _attempting; + public HermesSocketClient( User user, Configuration configuration, + ServiceBusCentral bus, ICallbackManager callbackManager, [FromKeyedServices("hermes")] IBackoff backoff, [FromKeyedServices("hermes")] IEnumerable handlers, @@ -50,6 +54,7 @@ namespace TwitchChatTTS.Hermes.Socket { _user = user; _configuration = configuration; + _bus = bus; _callbackManager = callbackManager; _backoff = backoff; _heartbeatTimer = new System.Timers.Timer(TimeSpan.FromSeconds(15)); @@ -59,6 +64,20 @@ namespace TwitchChatTTS.Hermes.Socket URL = $"wss://{BASE_URL}"; _lock = new object(); + + var ttsCreateUserVoice = _bus.GetTopic("tts.user.voice.create"); + ttsCreateUserVoice.Subscribe(new ServiceBusObserver(async data => await Send(3, new RequestMessage() + { + Type = "create_tts_user", + Data = (IDictionary) data.Value! + }), logger)); + + var ttsUpdateUserVoice = _bus.GetTopic("tts.user.voice.update"); + ttsUpdateUserVoice.Subscribe(new ServiceBusObserver(async data => await Send(3, new RequestMessage() + { + Type = "update_tts_user", + Data = (IDictionary) data.Value! + }), logger)); } @@ -66,23 +85,29 @@ namespace TwitchChatTTS.Hermes.Socket { lock (_lock) { - if (Connected) + if (Connected || _attempting) return; + + _attempting = true; } _logger.Debug($"Attempting to connect to {URL}"); await ConnectAsync(URL); + _attempting = false; } private async Task Disconnect() { lock (_lock) { - if (!Connected) + if (!Connected || _attempting) return; + + _attempting = true; } await DisconnectAsync(new SocketDisconnectionEventArgs("Normal disconnection", "Disconnection was executed")); + _attempting = false; } public async Task CreateTTSVoice(string voiceName) diff --git a/Hermes/Socket/Requests/CreateRedemptionAck.cs b/Hermes/Socket/Requests/CreateRedemptionAck.cs new file mode 100644 index 0000000..874cbc9 --- /dev/null +++ b/Hermes/Socket/Requests/CreateRedemptionAck.cs @@ -0,0 +1,35 @@ +using System.Text.Json; +using HermesSocketLibrary.Requests.Messages; +using Serilog; +using TwitchChatTTS.Twitch.Redemptions; + +namespace TwitchChatTTS.Hermes.Socket.Requests +{ + public class CreateRedemptionAck : IRequestAck + { + public string Name => "create_redemption"; + private readonly IRedemptionManager _redemptions; + private readonly JsonSerializerOptions _options; + private readonly ILogger _logger; + + public CreateRedemptionAck(IRedemptionManager redemptions, JsonSerializerOptions options, ILogger logger) + { + _redemptions = redemptions; + _options = options; + _logger = logger; + } + + public void Acknowledge(string requestId, string json, IDictionary? requestData) + { + var redemption = JsonSerializer.Deserialize(json, _options); + if (redemption == null) + { + _logger.Warning($"Redemption data received is null."); + return; + } + + _redemptions.Add(redemption); + _logger.Information($"A new redemption has been created [redemption id: {redemption.Id}][twitch redemption id: {redemption.TwitchRedemptionId}]"); + } + } +} \ No newline at end of file diff --git a/Hermes/Socket/Requests/CreateTTSUserAck.cs b/Hermes/Socket/Requests/CreateTTSUserAck.cs index 619dd61..2e63ed6 100644 --- a/Hermes/Socket/Requests/CreateTTSUserAck.cs +++ b/Hermes/Socket/Requests/CreateTTSUserAck.cs @@ -21,7 +21,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests _logger.Warning("Request data is null."); return; } - + if (!long.TryParse(requestData["chatter"].ToString(), out long chatterId)) { _logger.Warning($"Failed to parse chatter id [chatter id: {requestData["chatter"]}]"); diff --git a/Hermes/Socket/Requests/CreateTTSVoiceAck.cs b/Hermes/Socket/Requests/CreateTTSVoiceAck.cs index bac8daf..885ab91 100644 --- a/Hermes/Socket/Requests/CreateTTSVoiceAck.cs +++ b/Hermes/Socket/Requests/CreateTTSVoiceAck.cs @@ -21,7 +21,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests _logger.Warning("Request data is null."); return; } - + var voice = requestData["voice"].ToString()!; var voiceId = json; if (string.IsNullOrEmpty(voice)) diff --git a/Hermes/Socket/Requests/DeleteRedeemableActionAck.cs b/Hermes/Socket/Requests/DeleteRedeemableActionAck.cs new file mode 100644 index 0000000..44e2472 --- /dev/null +++ b/Hermes/Socket/Requests/DeleteRedeemableActionAck.cs @@ -0,0 +1,39 @@ +using Serilog; +using TwitchChatTTS.Twitch.Redemptions; + +namespace TwitchChatTTS.Hermes.Socket.Requests +{ + public class DeleteRedeemableActionAck : IRequestAck + { + public string Name => "delete_redeemable_action"; + private readonly IRedemptionManager _redemptions; + private readonly ILogger _logger; + + public DeleteRedeemableActionAck(IRedemptionManager redemptions, ILogger logger) + { + _redemptions = redemptions; + _logger = logger; + } + + public void Acknowledge(string requestId, string json, IDictionary? requestData) + { + if (requestData == null) + { + _logger.Warning("Request data is null."); + return; + } + + var name = requestData["name"].ToString(); + if (string.IsNullOrEmpty(name)) + { + _logger.Warning($"Action name is invalid [action name: {name}]"); + return; + } + + if (_redemptions.RemoveAction(name)) + _logger.Information($"Deleted a redeemable action [action name: {name}]"); + else + _logger.Warning($"Failed to delete a redeemable action [action name: {name}]"); + } + } +} \ No newline at end of file diff --git a/Hermes/Socket/Requests/DeleteRedemptionAck.cs b/Hermes/Socket/Requests/DeleteRedemptionAck.cs new file mode 100644 index 0000000..995f702 --- /dev/null +++ b/Hermes/Socket/Requests/DeleteRedemptionAck.cs @@ -0,0 +1,39 @@ +using Serilog; +using TwitchChatTTS.Twitch.Redemptions; + +namespace TwitchChatTTS.Hermes.Socket.Requests +{ + public class DeleteRedemptionAck : IRequestAck + { + public string Name => "delete_redemption"; + private readonly IRedemptionManager _redemptions; + private readonly ILogger _logger; + + public DeleteRedemptionAck(IRedemptionManager redemptions, ILogger logger) + { + _redemptions = redemptions; + _logger = logger; + } + + public void Acknowledge(string requestId, string json, IDictionary? requestData) + { + if (requestData == null) + { + _logger.Warning("Request data is null."); + return; + } + + var id = requestData["id"].ToString(); + if (string.IsNullOrEmpty(id)) + { + _logger.Warning($"Redemption Id is invalid [redemption id: {id}]"); + return; + } + + if (_redemptions.RemoveRedemption(id)) + _logger.Information($"Deleted a redemption [redemption id: {id}]"); + else + _logger.Warning($"Failed to delete a redemption [redemption id: {id}]"); + } + } +} \ No newline at end of file diff --git a/Hermes/Socket/Requests/DeleteTTSFilterAck.cs b/Hermes/Socket/Requests/DeleteTTSFilterAck.cs index d72488f..698c475 100644 --- a/Hermes/Socket/Requests/DeleteTTSFilterAck.cs +++ b/Hermes/Socket/Requests/DeleteTTSFilterAck.cs @@ -21,7 +21,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests _logger.Warning("Request data is null."); return; } - + var id = requestData["id"].ToString(); if (string.IsNullOrEmpty(id)) { diff --git a/Hermes/Socket/Requests/DeleteTTSVoiceAck.cs b/Hermes/Socket/Requests/DeleteTTSVoiceAck.cs index ad6c6c1..6dffa7b 100644 --- a/Hermes/Socket/Requests/DeleteTTSVoiceAck.cs +++ b/Hermes/Socket/Requests/DeleteTTSVoiceAck.cs @@ -21,7 +21,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests _logger.Warning("Request data is null."); return; } - + var voice = requestData["voice"].ToString(); if (string.IsNullOrEmpty(voice)) { diff --git a/Hermes/Socket/Requests/GetRedeemableActionsAck.cs b/Hermes/Socket/Requests/GetRedeemableActionsAck.cs index cd9bee9..54b9dfd 100644 --- a/Hermes/Socket/Requests/GetRedeemableActionsAck.cs +++ b/Hermes/Socket/Requests/GetRedeemableActionsAck.cs @@ -46,7 +46,8 @@ namespace TwitchChatTTS.Hermes.Socket.Requests } _logger.Information($"Redeemable actions loaded [count: {actions.Count()}]"); - _bus.Send(this, "redemptions_initiation", new RedemptionInitiation() { + _bus.Send(this, "redemptions_initiation", new RedemptionInitiation() + { Redemptions = redemptions, Actions = actions.ToDictionary(a => a.Name, a => a) }); diff --git a/Hermes/Socket/Requests/GetTTSUsersAck.cs b/Hermes/Socket/Requests/GetTTSUsersAck.cs index afee93a..66d1228 100644 --- a/Hermes/Socket/Requests/GetTTSUsersAck.cs +++ b/Hermes/Socket/Requests/GetTTSUsersAck.cs @@ -27,7 +27,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests var temp = new ConcurrentDictionary(); foreach (var entry in users) temp.TryAdd(entry.Key, entry.Value); - + _user.VoicesSelected = temp; _logger.Information($"Updated chatters' selected voice [count: {temp.Count()}]"); } diff --git a/Hermes/Socket/Requests/RequestAckManager.cs b/Hermes/Socket/Requests/RequestAckManager.cs index eb50a5a..6d91d0c 100644 --- a/Hermes/Socket/Requests/RequestAckManager.cs +++ b/Hermes/Socket/Requests/RequestAckManager.cs @@ -13,7 +13,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests _logger = logger; } - public void Fulfill(string type, string requestId, string? data, IDictionary? requestData) + public void Fulfill(string type, string requestId, string data, IDictionary? requestData) { if (!_acknowledgements.TryGetValue(type, out var ack)) { diff --git a/Hermes/Socket/Requests/UpdateRedeemableActionAck.cs b/Hermes/Socket/Requests/UpdateRedeemableActionAck.cs index 6e1dfa1..c989036 100644 --- a/Hermes/Socket/Requests/UpdateRedeemableActionAck.cs +++ b/Hermes/Socket/Requests/UpdateRedeemableActionAck.cs @@ -28,8 +28,10 @@ namespace TwitchChatTTS.Hermes.Socket.Requests return; } - _redemptions.Update(action); - _logger.Information($"A new redeemable action has been created [action name: {action.Name}]"); + if (_redemptions.Update(action)) + _logger.Information($"A redeemable action has been updated [action name: {action.Name}]"); + else + _logger.Warning($"Failed to update an existing redeemable action [action name: {action.Name}]"); } } } \ No newline at end of file diff --git a/Hermes/Socket/Requests/UpdateRedeemptionAck.cs b/Hermes/Socket/Requests/UpdateRedeemptionAck.cs new file mode 100644 index 0000000..96864c6 --- /dev/null +++ b/Hermes/Socket/Requests/UpdateRedeemptionAck.cs @@ -0,0 +1,37 @@ +using System.Text.Json; +using HermesSocketLibrary.Requests.Messages; +using Serilog; +using TwitchChatTTS.Twitch.Redemptions; + +namespace TwitchChatTTS.Hermes.Socket.Requests +{ + public class UpdateRedemptionAck : IRequestAck + { + public string Name => "update_redemption"; + private readonly IRedemptionManager _redemptions; + private readonly JsonSerializerOptions _options; + private readonly ILogger _logger; + + public UpdateRedemptionAck(IRedemptionManager redemptions, JsonSerializerOptions options, ILogger logger) + { + _redemptions = redemptions; + _options = options; + _logger = logger; + } + + public void Acknowledge(string requestId, string json, IDictionary? requestData) + { + var redemption = JsonSerializer.Deserialize(json, _options); + if (redemption == null) + { + _logger.Warning($"Redemption data received is null."); + return; + } + + if (_redemptions.Update(redemption)) + _logger.Information($"A redemption has been updated [redemption id: {redemption.Id}][twitch redemption id: {redemption.TwitchRedemptionId}]"); + else + _logger.Warning($"Failed to update an existing redemption [redemption id: {redemption.Id}][twitch redemption id: {redemption.TwitchRedemptionId}]"); + } + } +} \ No newline at end of file diff --git a/Hermes/Socket/Requests/UpdateTTSFilterAck.cs b/Hermes/Socket/Requests/UpdateTTSFilterAck.cs index 9d5fe12..243b33e 100644 --- a/Hermes/Socket/Requests/UpdateTTSFilterAck.cs +++ b/Hermes/Socket/Requests/UpdateTTSFilterAck.cs @@ -26,14 +26,15 @@ namespace TwitchChatTTS.Hermes.Socket.Requests _logger.Warning($"TTS Filter data failed: null"); return; } - + _logger.Debug($"Filter data [filter id: {filter.Id}][search: {filter.Search}][group id: {filter.Replace}]"); var previous = _user.RegexFilters.FirstOrDefault(f => f.Id == filter.Id); - if (previous == null) { + if (previous == null) + { _logger.Warning($"TTS Filter doest exist by id [filter id: {filter.Id}]"); return; } - + previous.Search = filter.Search; previous.Replace = filter.Replace; _logger.Information($"Filter has been updated [filter id: {filter.Id}]"); diff --git a/Startup.cs b/Startup.cs index 7338711..eb8e31a 100644 --- a/Startup.cs +++ b/Startup.cs @@ -88,9 +88,15 @@ s.AddTransient(); // Request acks s.AddSingleton(); s.AddTransient(); +s.AddTransient(); +s.AddTransient(); +s.AddTransient(); s.AddTransient(); s.AddTransient(); s.AddTransient(); +s.AddTransient(); +s.AddTransient(); +s.AddTransient(); s.AddTransient(); s.AddTransient(); s.AddTransient(); @@ -106,6 +112,9 @@ s.AddTransient(); s.AddTransient(); s.AddTransient(); s.AddTransient(); +s.AddTransient(); +s.AddTransient(); +s.AddTransient(); s.AddTransient(); s.AddTransient(); s.AddTransient(); diff --git a/Twitch/Redemptions/RedemptionManager.cs b/Twitch/Redemptions/RedemptionManager.cs index 708d00a..77e6a25 100644 --- a/Twitch/Redemptions/RedemptionManager.cs +++ b/Twitch/Redemptions/RedemptionManager.cs @@ -7,7 +7,6 @@ using org.mariuszgromada.math.mxparser; using Serilog; using TwitchChatTTS.Bus; using TwitchChatTTS.Bus.Data; -using TwitchChatTTS.Hermes.Socket; using TwitchChatTTS.OBS.Socket; using TwitchChatTTS.OBS.Socket.Data; using TwitchChatTTS.Veadotube; @@ -23,7 +22,6 @@ namespace TwitchChatTTS.Twitch.Redemptions private readonly ServiceBusCentral _bus; private readonly User _user; private readonly OBSSocketClient _obs; - private readonly HermesSocketClient _hermes; private readonly VeadoSocketClient _veado; private readonly NightbotApiClient _nightbot; private readonly AudioPlaybackEngine _playback; @@ -36,7 +34,6 @@ namespace TwitchChatTTS.Twitch.Redemptions ServiceBusCentral bus, User user, [FromKeyedServices("obs")] SocketClient obs, - [FromKeyedServices("hermes")] SocketClient hermes, [FromKeyedServices("veadotube")] SocketClient veado, NightbotApiClient nightbot, AudioPlaybackEngine playback, @@ -48,7 +45,6 @@ namespace TwitchChatTTS.Twitch.Redemptions _bus = bus; _user = user; _obs = (obs as OBSSocketClient)!; - _hermes = (hermes as HermesSocketClient)!; _veado = (veado as VeadoSocketClient)!; _nightbot = nightbot; _playback = playback; @@ -284,12 +280,12 @@ namespace TwitchChatTTS.Twitch.Redemptions if (_user.VoicesSelected.ContainsKey(senderId)) { - await _hermes.UpdateTTSUser(senderId, voiceId); + _bus.Send(this, "tts.user.voice.update", new Dictionary() { { "chatter", senderId }, { "voice", voiceId } }); _logger.Debug($"Sent request to update chat TTS voice [voice: {voiceName}][chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]"); } else { - await _hermes.CreateTTSUser(senderId, voiceId); + _bus.Send(this, "tts.user.voice.create", new Dictionary() { { "chatter", senderId }, { "voice", voiceId } }); _logger.Debug($"Sent request to create chat TTS voice [voice: {voiceName}][chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]"); } break; diff --git a/Twitch/Socket/Handlers/ChannelAdBreakBeginHandler.cs b/Twitch/Socket/Handlers/ChannelAdBreakBeginHandler.cs index 0c0270f..77b112d 100644 --- a/Twitch/Socket/Handlers/ChannelAdBreakBeginHandler.cs +++ b/Twitch/Socket/Handlers/ChannelAdBreakBeginHandler.cs @@ -25,14 +25,14 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers if (message.IsAutomatic) _logger.Information($"Ad break has begun [duration: {message.DurationSeconds} seconds][automatic: true]"); else - _logger.Information($"Ad break has begun [duration: {message.DurationSeconds} seconds][requester: {message.RequesterUserLogin}][requester id: {message.RequesterUserId}]"); + _logger.Information($"Ad break has begun [duration: {message.DurationSeconds} seconds][automatic: false][requester: {message.RequesterUserLogin}][requester id: {message.RequesterUserId}]"); try { var actions = _redemptionManager.Get("adbreak_begin"); - if (!actions.Any()) + if (actions.Any()) { - _logger.Debug($"Found {actions.Count()} actions for this Twitch ad break"); + _logger.Debug($"Found {actions.Count()} actions for this Twitch ad break begin"); foreach (var action in actions) try @@ -46,20 +46,27 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers } else _logger.Debug($"No redeemable actions for ad break begin was found"); + } + catch (Exception ex) + { + _logger.Error(ex, $"Failed to fetch the redeemable actions for ad break begin"); + } #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(async () => + Task.Run(async () => + { + try { await Task.Delay(TimeSpan.FromSeconds(message.DurationSeconds)); if (message.IsAutomatic) _logger.Information($"Ad break has ended [duration: {message.DurationSeconds} seconds][automatic: true]"); else - _logger.Information($"Ad break has ended [duration: {message.DurationSeconds} seconds][requester: {message.RequesterUserLogin}][requester id: {message.RequesterUserId}]"); - - actions = _redemptionManager.Get("adbreak_end"); - if (!actions.Any()) + _logger.Information($"Ad break has ended [duration: {message.DurationSeconds} seconds][automatic: false][requester: {message.RequesterUserLogin}][requester id: {message.RequesterUserId}]"); + + var actions = _redemptionManager.Get("adbreak_end"); + if (actions.Any()) { - _logger.Debug($"Found {actions.Count()} actions for this Twitch ad break"); + _logger.Debug($"Found {actions.Count()} actions for this Twitch ad break end"); foreach (var action in actions) try @@ -73,13 +80,13 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers } else _logger.Debug($"No redeemable actions for ad break end was found"); - }); + } + catch (Exception ex) + { + _logger.Error(ex, $"Failed to fetch the redeemable actions for ad break end"); + } + }); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - catch (Exception ex) - { - _logger.Error(ex, $"Failed to fetch the redeemable actions for ad break begin"); - } } } } \ No newline at end of file