Added missing websocket support for Redemptions and Actions. Fixed Ad Break actions. Cleaned some code.

This commit is contained in:
Tom 2025-01-07 15:30:13 +00:00
parent 77b37f04b6
commit 64cb0c1f6d
17 changed files with 227 additions and 36 deletions

View File

@ -1,4 +1,3 @@
using System.Net.WebSockets;
using System.Text.Json; using System.Text.Json;
using System.Timers; using System.Timers;
using CommonSocketLibrary.Abstract; using CommonSocketLibrary.Abstract;
@ -9,6 +8,7 @@ using HermesSocketLibrary.Requests.Messages;
using HermesSocketLibrary.Socket.Data; using HermesSocketLibrary.Socket.Data;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Serilog; using Serilog;
using TwitchChatTTS.Bus;
using TwitchChatTTS.Hermes.Socket.Handlers; using TwitchChatTTS.Hermes.Socket.Handlers;
namespace TwitchChatTTS.Hermes.Socket namespace TwitchChatTTS.Hermes.Socket
@ -19,6 +19,7 @@ namespace TwitchChatTTS.Hermes.Socket
private readonly User _user; private readonly User _user;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly ServiceBusCentral _bus;
private readonly ICallbackManager<HermesRequestData> _callbackManager; private readonly ICallbackManager<HermesRequestData> _callbackManager;
private string URL; private string URL;
@ -33,10 +34,13 @@ namespace TwitchChatTTS.Hermes.Socket
public bool LoggedIn { get; set; } public bool LoggedIn { get; set; }
public bool Ready { get; set; } public bool Ready { get; set; }
private bool _attempting;
public HermesSocketClient( public HermesSocketClient(
User user, User user,
Configuration configuration, Configuration configuration,
ServiceBusCentral bus,
ICallbackManager<HermesRequestData> callbackManager, ICallbackManager<HermesRequestData> callbackManager,
[FromKeyedServices("hermes")] IBackoff backoff, [FromKeyedServices("hermes")] IBackoff backoff,
[FromKeyedServices("hermes")] IEnumerable<IWebSocketHandler> handlers, [FromKeyedServices("hermes")] IEnumerable<IWebSocketHandler> handlers,
@ -50,6 +54,7 @@ namespace TwitchChatTTS.Hermes.Socket
{ {
_user = user; _user = user;
_configuration = configuration; _configuration = configuration;
_bus = bus;
_callbackManager = callbackManager; _callbackManager = callbackManager;
_backoff = backoff; _backoff = backoff;
_heartbeatTimer = new System.Timers.Timer(TimeSpan.FromSeconds(15)); _heartbeatTimer = new System.Timers.Timer(TimeSpan.FromSeconds(15));
@ -59,6 +64,20 @@ namespace TwitchChatTTS.Hermes.Socket
URL = $"wss://{BASE_URL}"; URL = $"wss://{BASE_URL}";
_lock = new object(); _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<string, object>) 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<string, object>) data.Value!
}), logger));
} }
@ -66,23 +85,29 @@ namespace TwitchChatTTS.Hermes.Socket
{ {
lock (_lock) lock (_lock)
{ {
if (Connected) if (Connected || _attempting)
return; return;
_attempting = true;
} }
_logger.Debug($"Attempting to connect to {URL}"); _logger.Debug($"Attempting to connect to {URL}");
await ConnectAsync(URL); await ConnectAsync(URL);
_attempting = false;
} }
private async Task Disconnect() private async Task Disconnect()
{ {
lock (_lock) lock (_lock)
{ {
if (!Connected) if (!Connected || _attempting)
return; return;
_attempting = true;
} }
await DisconnectAsync(new SocketDisconnectionEventArgs("Normal disconnection", "Disconnection was executed")); await DisconnectAsync(new SocketDisconnectionEventArgs("Normal disconnection", "Disconnection was executed"));
_attempting = false;
} }
public async Task CreateTTSVoice(string voiceName) public async Task CreateTTSVoice(string voiceName)

View File

@ -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<string, object>? requestData)
{
var redemption = JsonSerializer.Deserialize<Redemption>(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}]");
}
}
}

View File

@ -21,7 +21,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
_logger.Warning("Request data is null."); _logger.Warning("Request data is null.");
return; return;
} }
if (!long.TryParse(requestData["chatter"].ToString(), out long chatterId)) if (!long.TryParse(requestData["chatter"].ToString(), out long chatterId))
{ {
_logger.Warning($"Failed to parse chatter id [chatter id: {requestData["chatter"]}]"); _logger.Warning($"Failed to parse chatter id [chatter id: {requestData["chatter"]}]");

View File

@ -21,7 +21,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
_logger.Warning("Request data is null."); _logger.Warning("Request data is null.");
return; return;
} }
var voice = requestData["voice"].ToString()!; var voice = requestData["voice"].ToString()!;
var voiceId = json; var voiceId = json;
if (string.IsNullOrEmpty(voice)) if (string.IsNullOrEmpty(voice))

View File

@ -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<string, object>? 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}]");
}
}
}

View File

@ -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<string, object>? 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}]");
}
}
}

View File

@ -21,7 +21,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
_logger.Warning("Request data is null."); _logger.Warning("Request data is null.");
return; return;
} }
var id = requestData["id"].ToString(); var id = requestData["id"].ToString();
if (string.IsNullOrEmpty(id)) if (string.IsNullOrEmpty(id))
{ {

View File

@ -21,7 +21,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
_logger.Warning("Request data is null."); _logger.Warning("Request data is null.");
return; return;
} }
var voice = requestData["voice"].ToString(); var voice = requestData["voice"].ToString();
if (string.IsNullOrEmpty(voice)) if (string.IsNullOrEmpty(voice))
{ {

View File

@ -46,7 +46,8 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
} }
_logger.Information($"Redeemable actions loaded [count: {actions.Count()}]"); _logger.Information($"Redeemable actions loaded [count: {actions.Count()}]");
_bus.Send(this, "redemptions_initiation", new RedemptionInitiation() { _bus.Send(this, "redemptions_initiation", new RedemptionInitiation()
{
Redemptions = redemptions, Redemptions = redemptions,
Actions = actions.ToDictionary(a => a.Name, a => a) Actions = actions.ToDictionary(a => a.Name, a => a)
}); });

View File

@ -27,7 +27,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
var temp = new ConcurrentDictionary<long, string>(); var temp = new ConcurrentDictionary<long, string>();
foreach (var entry in users) foreach (var entry in users)
temp.TryAdd(entry.Key, entry.Value); temp.TryAdd(entry.Key, entry.Value);
_user.VoicesSelected = temp; _user.VoicesSelected = temp;
_logger.Information($"Updated chatters' selected voice [count: {temp.Count()}]"); _logger.Information($"Updated chatters' selected voice [count: {temp.Count()}]");
} }

View File

@ -13,7 +13,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
_logger = logger; _logger = logger;
} }
public void Fulfill(string type, string requestId, string? data, IDictionary<string, object>? requestData) public void Fulfill(string type, string requestId, string data, IDictionary<string, object>? requestData)
{ {
if (!_acknowledgements.TryGetValue(type, out var ack)) if (!_acknowledgements.TryGetValue(type, out var ack))
{ {

View File

@ -28,8 +28,10 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
return; return;
} }
_redemptions.Update(action); if (_redemptions.Update(action))
_logger.Information($"A new redeemable action has been created [action name: {action.Name}]"); _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}]");
} }
} }
} }

View File

@ -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<string, object>? requestData)
{
var redemption = JsonSerializer.Deserialize<Redemption>(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}]");
}
}
}

View File

@ -26,14 +26,15 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
_logger.Warning($"TTS Filter data failed: null"); _logger.Warning($"TTS Filter data failed: null");
return; return;
} }
_logger.Debug($"Filter data [filter id: {filter.Id}][search: {filter.Search}][group id: {filter.Replace}]"); _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); 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}]"); _logger.Warning($"TTS Filter doest exist by id [filter id: {filter.Id}]");
return; return;
} }
previous.Search = filter.Search; previous.Search = filter.Search;
previous.Replace = filter.Replace; previous.Replace = filter.Replace;
_logger.Information($"Filter has been updated [filter id: {filter.Id}]"); _logger.Information($"Filter has been updated [filter id: {filter.Id}]");

View File

@ -88,9 +88,15 @@ s.AddTransient<ICommandFactory, CommandFactory>();
// Request acks // Request acks
s.AddSingleton<RequestAckManager>(); s.AddSingleton<RequestAckManager>();
s.AddTransient<IRequestAck, CreatePolicyAck>(); s.AddTransient<IRequestAck, CreatePolicyAck>();
s.AddTransient<IRequestAck, CreateRedeemableActionAck>();
s.AddTransient<IRequestAck, CreateRedemptionAck>();
s.AddTransient<IRequestAck, CreateTTSFilterAck>();
s.AddTransient<IRequestAck, CreateTTSUserAck>(); s.AddTransient<IRequestAck, CreateTTSUserAck>();
s.AddTransient<IRequestAck, CreateTTSVoiceAck>(); s.AddTransient<IRequestAck, CreateTTSVoiceAck>();
s.AddTransient<IRequestAck, DeletePolicyAck>(); s.AddTransient<IRequestAck, DeletePolicyAck>();
s.AddTransient<IRequestAck, DeleteRedeemableActionAck>();
s.AddTransient<IRequestAck, DeleteRedemptionAck>();
s.AddTransient<IRequestAck, DeleteTTSFilterAck>();
s.AddTransient<IRequestAck, DeleteTTSVoiceAck>(); s.AddTransient<IRequestAck, DeleteTTSVoiceAck>();
s.AddTransient<IRequestAck, GetChatterIdsAck>(); s.AddTransient<IRequestAck, GetChatterIdsAck>();
s.AddTransient<IRequestAck, GetConnectionsAck>(); s.AddTransient<IRequestAck, GetConnectionsAck>();
@ -106,6 +112,9 @@ s.AddTransient<IRequestAck, GetTTSVoicesAck>();
s.AddTransient<IRequestAck, GetTTSWordFiltersAck>(); s.AddTransient<IRequestAck, GetTTSWordFiltersAck>();
s.AddTransient<IRequestAck, UpdateDefaultTTSVoiceAck>(); s.AddTransient<IRequestAck, UpdateDefaultTTSVoiceAck>();
s.AddTransient<IRequestAck, UpdatePolicyAck>(); s.AddTransient<IRequestAck, UpdatePolicyAck>();
s.AddTransient<IRequestAck, UpdateRedeemableActionAck>();
s.AddTransient<IRequestAck, UpdateRedemptionAck>();
s.AddTransient<IRequestAck, UpdateTTSFilterAck>();
s.AddTransient<IRequestAck, UpdateTTSUserAck>(); s.AddTransient<IRequestAck, UpdateTTSUserAck>();
s.AddTransient<IRequestAck, UpdateTTSVoiceAck>(); s.AddTransient<IRequestAck, UpdateTTSVoiceAck>();
s.AddTransient<IRequestAck, UpdateTTSVoiceStateAck>(); s.AddTransient<IRequestAck, UpdateTTSVoiceStateAck>();

View File

@ -7,7 +7,6 @@ using org.mariuszgromada.math.mxparser;
using Serilog; using Serilog;
using TwitchChatTTS.Bus; using TwitchChatTTS.Bus;
using TwitchChatTTS.Bus.Data; using TwitchChatTTS.Bus.Data;
using TwitchChatTTS.Hermes.Socket;
using TwitchChatTTS.OBS.Socket; using TwitchChatTTS.OBS.Socket;
using TwitchChatTTS.OBS.Socket.Data; using TwitchChatTTS.OBS.Socket.Data;
using TwitchChatTTS.Veadotube; using TwitchChatTTS.Veadotube;
@ -23,7 +22,6 @@ namespace TwitchChatTTS.Twitch.Redemptions
private readonly ServiceBusCentral _bus; private readonly ServiceBusCentral _bus;
private readonly User _user; private readonly User _user;
private readonly OBSSocketClient _obs; private readonly OBSSocketClient _obs;
private readonly HermesSocketClient _hermes;
private readonly VeadoSocketClient _veado; private readonly VeadoSocketClient _veado;
private readonly NightbotApiClient _nightbot; private readonly NightbotApiClient _nightbot;
private readonly AudioPlaybackEngine _playback; private readonly AudioPlaybackEngine _playback;
@ -36,7 +34,6 @@ namespace TwitchChatTTS.Twitch.Redemptions
ServiceBusCentral bus, ServiceBusCentral bus,
User user, User user,
[FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs, [FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs,
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes,
[FromKeyedServices("veadotube")] SocketClient<object> veado, [FromKeyedServices("veadotube")] SocketClient<object> veado,
NightbotApiClient nightbot, NightbotApiClient nightbot,
AudioPlaybackEngine playback, AudioPlaybackEngine playback,
@ -48,7 +45,6 @@ namespace TwitchChatTTS.Twitch.Redemptions
_bus = bus; _bus = bus;
_user = user; _user = user;
_obs = (obs as OBSSocketClient)!; _obs = (obs as OBSSocketClient)!;
_hermes = (hermes as HermesSocketClient)!;
_veado = (veado as VeadoSocketClient)!; _veado = (veado as VeadoSocketClient)!;
_nightbot = nightbot; _nightbot = nightbot;
_playback = playback; _playback = playback;
@ -284,12 +280,12 @@ namespace TwitchChatTTS.Twitch.Redemptions
if (_user.VoicesSelected.ContainsKey(senderId)) if (_user.VoicesSelected.ContainsKey(senderId))
{ {
await _hermes.UpdateTTSUser(senderId, voiceId); _bus.Send(this, "tts.user.voice.update", new Dictionary<string, object>() { { "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}]"); _logger.Debug($"Sent request to update chat TTS voice [voice: {voiceName}][chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]");
} }
else else
{ {
await _hermes.CreateTTSUser(senderId, voiceId); _bus.Send(this, "tts.user.voice.create", new Dictionary<string, object>() { { "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}]"); _logger.Debug($"Sent request to create chat TTS voice [voice: {voiceName}][chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]");
} }
break; break;

View File

@ -25,14 +25,14 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
if (message.IsAutomatic) if (message.IsAutomatic)
_logger.Information($"Ad break has begun [duration: {message.DurationSeconds} seconds][automatic: true]"); _logger.Information($"Ad break has begun [duration: {message.DurationSeconds} seconds][automatic: true]");
else 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 try
{ {
var actions = _redemptionManager.Get("adbreak_begin"); 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) foreach (var action in actions)
try try
@ -46,20 +46,27 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
} }
else else
_logger.Debug($"No redeemable actions for ad break begin was found"); _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 #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)); await Task.Delay(TimeSpan.FromSeconds(message.DurationSeconds));
if (message.IsAutomatic) if (message.IsAutomatic)
_logger.Information($"Ad break has ended [duration: {message.DurationSeconds} seconds][automatic: true]"); _logger.Information($"Ad break has ended [duration: {message.DurationSeconds} seconds][automatic: true]");
else else
_logger.Information($"Ad break has ended [duration: {message.DurationSeconds} seconds][requester: {message.RequesterUserLogin}][requester id: {message.RequesterUserId}]"); _logger.Information($"Ad break has ended [duration: {message.DurationSeconds} seconds][automatic: false][requester: {message.RequesterUserLogin}][requester id: {message.RequesterUserId}]");
actions = _redemptionManager.Get("adbreak_end"); var actions = _redemptionManager.Get("adbreak_end");
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 end");
foreach (var action in actions) foreach (var action in actions)
try try
@ -73,13 +80,13 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
} }
else else
_logger.Debug($"No redeemable actions for ad break end was found"); _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 #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");
}
} }
} }
} }