Added Actions & Redemptions updates via websocket messages. Updated RedemptionManager due to live changes.

This commit is contained in:
Tom
2025-01-06 14:36:54 +00:00
parent d74b132c0f
commit 77b37f04b6
14 changed files with 326 additions and 59 deletions

View File

@ -4,8 +4,14 @@ namespace TwitchChatTTS.Twitch.Redemptions
{
public interface IRedemptionManager
{
void Add(RedeemableAction action);
void Add(Redemption redemption);
Task Execute(RedeemableAction action, string senderDisplayName, long senderId);
IList<RedeemableAction> Get(string twitchRedemptionId);
void Initialize(IEnumerable<Redemption> redemptions, IDictionary<string, RedeemableAction> actions);
IEnumerable<RedeemableAction> Get(string twitchRedemptionId);
void Initialize();
bool RemoveAction(string actionName);
bool RemoveRedemption(string redemptionId);
bool Update(Redemption redemption);
bool Update(RedeemableAction action);
}
}

View File

@ -16,7 +16,10 @@ namespace TwitchChatTTS.Twitch.Redemptions
{
public class RedemptionManager : IRedemptionManager
{
private readonly IDictionary<string, IList<RedeemableAction>> _store;
private readonly IDictionary<string, RedeemableAction> _actions;
private readonly IDictionary<string, Redemption> _redemptions;
// twitch redemption id -> redemption ids
private readonly IDictionary<string, IList<string>> _redeems;
private readonly ServiceBusCentral _bus;
private readonly User _user;
private readonly OBSSocketClient _obs;
@ -26,7 +29,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
private readonly AudioPlaybackEngine _playback;
private readonly ILogger _logger;
private readonly Random _random;
private bool _isReady;
private readonly object _lock;
public RedemptionManager(
@ -39,7 +42,9 @@ namespace TwitchChatTTS.Twitch.Redemptions
AudioPlaybackEngine playback,
ILogger logger)
{
_store = new Dictionary<string, IList<RedeemableAction>>();
_actions = new Dictionary<string, RedeemableAction>();
_redemptions = new Dictionary<string, Redemption>();
_redeems = new Dictionary<string, IList<string>>();
_bus = bus;
_user = user;
_obs = (obs as OBSSocketClient)!;
@ -49,23 +54,110 @@ namespace TwitchChatTTS.Twitch.Redemptions
_playback = playback;
_logger = logger;
_random = new Random();
_isReady = false;
_lock = new object();
var topic = _bus.GetTopic("redemptions_initiation");
topic.Subscribe(new ServiceBusObserver(data =>
{
if (data.Value is RedemptionInitiation obj)
Initialize(obj.Redemptions, obj.Actions);
if (data.Value is not RedemptionInitiation init)
return;
if (init.Actions == null)
init.Actions = new Dictionary<string, RedeemableAction>();
if (init.Redemptions == null)
init.Redemptions = new List<Redemption>();
if (!init.Actions.Any())
_logger.Warning("No redeemable actions were loaded.");
if (!init.Redemptions.Any())
_logger.Warning("No redemptions were loaded.");
foreach (var action in init.Actions.Values)
Add(action);
foreach (var redemption in init.Redemptions)
Add(redemption);
Initialize();
}, _logger));
}
private void Add(string twitchRedemptionId, RedeemableAction action)
public void Add(RedeemableAction action)
{
if (!_store.TryGetValue(twitchRedemptionId, out var actions))
_store.Add(twitchRedemptionId, actions = new List<RedeemableAction>());
if (!_actions.ContainsKey(action.Name))
{
_actions.Add(action.Name, action);
_logger.Debug($"Added redeemable action to redemption manager [action name: {action.Name}]");
}
else
_logger.Debug($"Redemption manager already has this action stored [action name: {action.Name}]");
}
actions.Add(action);
_logger.Debug($"Added redemption action [name: {action.Name}][type: {action.Type}]");
public void Add(Redemption redemption)
{
_redemptions.Add(redemption.Id, redemption);
Add(redemption.TwitchRedemptionId, redemption);
}
private void Add(string twitchRedemptionId, string redemptionId)
{
lock (_lock)
{
if (!_redeems.TryGetValue(twitchRedemptionId, out var redeems))
_redeems.Add(twitchRedemptionId, redeems = new List<string>());
var item = _redemptions.TryGetValue(redemptionId, out var r) ? r : null;
if (item == null)
{
return;
}
var redemptions = redeems.Select(r => _redemptions.TryGetValue(r, out var rr) ? rr : null);
bool added = false;
for (int i = 0; i < redeems.Count; i++)
{
if (redeems[i] != null && _redemptions.TryGetValue(redeems[i], out var rr))
{
if (item.Order > rr.Order)
{
redeems.Insert(i, redemptionId);
added = true;
break;
}
}
}
if (!added)
redeems.Add(redemptionId);
}
_logger.Debug($"Added redemption action [redemption id: {redemptionId}][twitch redemption id: {twitchRedemptionId}]");
}
private void Add(string twitchRedemptionId, Redemption item)
{
lock (_lock)
{
if (!_redeems.TryGetValue(twitchRedemptionId, out var redemptionNames))
_redeems.Add(twitchRedemptionId, redemptionNames = new List<string>());
var redemptions = redemptionNames.Select(r => _redemptions.TryGetValue(r, out var rr) ? rr : null);
bool added = false;
for (int i = 0; i < redemptionNames.Count; i++)
{
if (redemptionNames[i] != null && _redemptions.TryGetValue(redemptionNames[i], out var rr))
{
if (item.Order > rr.Order)
{
redemptionNames.Insert(i, item.Id);
added = true;
break;
}
}
}
if (!added)
redemptionNames.Add(item.Id);
}
_logger.Debug($"Added redemption action [redemption id: {item.Id}][twitch redemption id: {twitchRedemptionId}]");
}
public async Task Execute(RedeemableAction action, string senderDisplayName, long senderId)
@ -228,7 +320,8 @@ namespace TwitchChatTTS.Twitch.Redemptions
case "VEADOTUBE_SET_STATE":
{
var state = _veado.GetStateId(action.Data["state"]);
if (state == null) {
if (state == null)
{
_logger.Warning($"Could not find the state named '{action.Data["state"]}'.");
break;
}
@ -238,7 +331,8 @@ namespace TwitchChatTTS.Twitch.Redemptions
case "VEADOTUBE_PUSH_STATE":
{
var state = _veado.GetStateId(action.Data["state"]);
if (state == null) {
if (state == null)
{
_logger.Warning($"Could not find the state named '{action.Data["state"]}'.");
break;
}
@ -248,7 +342,8 @@ namespace TwitchChatTTS.Twitch.Redemptions
case "VEADOTUBE_POP_STATE":
{
var state = _veado.GetStateId(action.Data["state"]);
if (state == null) {
if (state == null)
{
_logger.Warning($"Could not find the state named '{action.Data["state"]}'.");
break;
}
@ -256,7 +351,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
break;
}
default:
_logger.Warning($"Unknown redeemable action has occured. Update needed? [type: {action.Type}][chatter: {senderDisplayName}][chatter id: {senderId}]");
_logger.Warning($"Unknown redeemable action has occured [type: {action.Type}][chatter: {senderDisplayName}][chatter id: {senderId}]");
break;
}
}
@ -266,53 +361,149 @@ namespace TwitchChatTTS.Twitch.Redemptions
}
}
public IList<RedeemableAction> Get(string twitchRedemptionId)
public IEnumerable<RedeemableAction> Get(string twitchRedemptionId)
{
if (!_isReady)
throw new InvalidOperationException("Not ready");
if (_store.TryGetValue(twitchRedemptionId, out var actions))
return actions;
return new List<RedeemableAction>(0);
lock (_lock)
{
if (_redeems.TryGetValue(twitchRedemptionId, out var redemptionIds))
return redemptionIds.Select(r => _redemptions.TryGetValue(r, out var redemption) ? redemption : null)
.Where(r => r != null)
.Select(r => _actions.TryGetValue(r!.ActionName, out var action) ? action : null)
.Where(a => a != null)!;
}
return [];
}
public void Initialize(IEnumerable<Redemption> redemptions, IDictionary<string, RedeemableAction> actions)
public void Initialize()
{
_store.Clear();
_logger.Debug($"Redemption manager is about to initialize [redemption count: {_redemptions.Count()}][action count: {_actions.Count}]");
var ordered = redemptions.Where(r => r != null).OrderBy(r => r.Order);
foreach (var redemption in ordered)
lock (_lock)
{
if (redemption.ActionName == null)
{
_logger.Warning("Null value found for the action name of a redemption.");
continue;
}
_redeems.Clear();
try
var ordered = _redemptions.Select(r => r.Value).Where(r => r != null).OrderBy(r => r.Order);
foreach (var redemption in ordered)
{
if (actions.TryGetValue(redemption.ActionName, out var action) && action != null)
if (redemption.ActionName == null)
{
_logger.Debug($"Fetched a redemption action [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]");
Add(redemption.RedemptionId, action);
_logger.Warning("Null value found for the action name of a redemption.");
continue;
}
try
{
if (_actions.ContainsKey(redemption.ActionName))
{
_logger.Debug($"Fetched a redeemable action [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]");
Add(redemption.TwitchRedemptionId, redemption.Id);
}
else
_logger.Warning($"Could not find redeemable action [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]");
}
catch (Exception e)
{
_logger.Error(e, $"Failed to add a redemption [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]");
}
else
_logger.Warning($"Could not find redemption action [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]");
}
catch (Exception e)
{
_logger.Error(e, $"Failed to add a redemption [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]");
}
}
_isReady = true;
_logger.Debug("All redemptions added. Redemption Manager is ready.");
}
public bool RemoveAction(string actionName)
{
return _actions.Remove(actionName);
}
public bool RemoveRedemption(string redemptionId)
{
lock (_lock)
{
if (!_redemptions.TryGetValue(redemptionId, out var redemption))
{
return false;
}
_redemptions.Remove(redemptionId);
if (_redeems.TryGetValue(redemption.TwitchRedemptionId, out var redeem))
{
redeem.Remove(redemptionId);
if (!redeem.Any())
_redeems.Remove(redemption.TwitchRedemptionId);
return true;
}
}
return false;
}
private string ReplaceContentText(string content, string username)
{
return content.Replace("%USER%", username)
.Replace("\\n", "\n");
}
public bool Update(Redemption redemption)
{
lock (_lock)
{
if (_redemptions.TryGetValue(redemption.Id, out var r))
{
if (r.Order != redemption.Order && _redeems.TryGetValue(redemption.TwitchRedemptionId, out var redeems) && redeems.Count > 1)
{
var redemptions = redeems.Select(r => _redemptions.TryGetValue(r, out var rr) ? rr : null).ToArray();
int index = redeems.IndexOf(redemption.Id), i;
if (r.Order < redemption.Order)
{
for (i = index; i >= 1; i--)
{
if (redemptions[i - 1] == null || redemption.Order < redemptions[i - 1]!.Order)
redeems[i] = redeems[i - 1];
else
break;
}
}
else
{
for (i = index; i < redeems.Count - 1; i++)
{
if (redemptions[i + 1] == null || redemption.Order > redemptions[i + 1]!.Order)
redeems[i] = redeems[i + 1];
else
break;
}
}
redeems[i] = redemption.Id;
}
else
{
r.ActionName = redemption.ActionName;
r.State = redemption.State;
r.TwitchRedemptionId = redemption.TwitchRedemptionId;
r.Order = redemption.Order;
}
_logger.Debug($"Updated redemption in redemption manager [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}]");
return true;
}
}
_logger.Warning($"Cannot find redemption by name [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}]");
return false;
}
public bool Update(RedeemableAction action)
{
if (_actions.TryGetValue(action.Name, out var a))
{
a.Type = action.Type;
a.Data = action.Data;
_logger.Debug($"Updated redeemable action in redemption manager [action name: {action.Name}]");
return true;
}
_logger.Warning($"Cannot find redeemable action by name [action name: {action.Name}]");
return false;
}
}
}