Added chat message to redemptions. Added Subscription End to Twitch. Added more variables to certain redemptions.

This commit is contained in:
Tom
2025-01-18 21:51:50 +00:00
parent 9a17ad16b3
commit 5067ffe119
16 changed files with 152 additions and 25 deletions

View File

@ -202,6 +202,7 @@ s.AddKeyedSingleton<ITwitchSocketHandler, ChannelFollowHandler>("twitch-notifica
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelRaidHandler>("twitch-notifications"); s.AddKeyedSingleton<ITwitchSocketHandler, ChannelRaidHandler>("twitch-notifications");
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelResubscriptionHandler>("twitch-notifications"); s.AddKeyedSingleton<ITwitchSocketHandler, ChannelResubscriptionHandler>("twitch-notifications");
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionHandler>("twitch-notifications"); s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionHandler>("twitch-notifications");
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionEndHandler>("twitch-notifications");
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionGiftHandler>("twitch-notifications"); s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionGiftHandler>("twitch-notifications");
// hermes websocket // hermes websocket

View File

@ -1,4 +1,5 @@
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using TwitchChatTTS.Twitch.Socket.Messages;
namespace TwitchChatTTS.Twitch.Redemptions namespace TwitchChatTTS.Twitch.Redemptions
{ {
@ -6,7 +7,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
{ {
void Add(RedeemableAction action); void Add(RedeemableAction action);
void Add(Redemption redemption); void Add(Redemption redemption);
Task Execute(RedeemableAction action, string senderDisplayName, long senderId); Task Execute(RedeemableAction action, string senderDisplayName, long senderId, string senderMessage);
IEnumerable<RedeemableAction> Get(string twitchRedemptionId); IEnumerable<RedeemableAction> Get(string twitchRedemptionId);
void Initialize(); void Initialize();
bool RemoveAction(string actionName); bool RemoveAction(string actionName);

View File

@ -166,7 +166,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
_logger.Debug($"Added redemption action [redemption id: {item.Id}][twitch redemption id: {twitchRedemptionId}]"); _logger.Debug($"Added redemption action [redemption id: {item.Id}][twitch redemption id: {twitchRedemptionId}]");
} }
public async Task Execute(RedeemableAction action, string senderDisplayName, long senderId) public async Task Execute(RedeemableAction action, string senderDisplayName, long senderId, string senderMessage)
{ {
_logger.Debug($"Executing an action for a redemption [action: {action.Name}][action type: {action.Type}][chatter: {senderDisplayName}][chatter id: {senderId}]"); _logger.Debug($"Executing an action for a redemption [action: {action.Name}][action type: {action.Type}][chatter: {senderDisplayName}][chatter id: {senderId}]");
@ -190,7 +190,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
if (!string.IsNullOrWhiteSpace(directory)) if (!string.IsNullOrWhiteSpace(directory))
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
await File.WriteAllTextAsync(path, ReplaceContentText(action.Data["file_content"], senderDisplayName)); await File.WriteAllTextAsync(path, ReplaceContentText(action.Data["file_content"], senderDisplayName, senderId, senderMessage));
_logger.Debug($"Overwritten text to file [file: {action.Data["file_path"]}][chatter: {senderDisplayName}][chatter id: {senderId}]"); _logger.Debug($"Overwritten text to file [file: {action.Data["file_path"]}][chatter: {senderDisplayName}][chatter id: {senderId}]");
break; break;
} }
@ -204,7 +204,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
if (!string.IsNullOrWhiteSpace(directory)) if (!string.IsNullOrWhiteSpace(directory))
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
await File.AppendAllTextAsync(path, ReplaceContentText(action.Data["file_content"], senderDisplayName)); await File.AppendAllTextAsync(path, ReplaceContentText(action.Data["file_content"], senderDisplayName, senderId, senderMessage));
_logger.Debug($"Appended text to file [file: {action.Data["file_path"]}][chatter: {senderDisplayName}][chatter id: {senderId}]"); _logger.Debug($"Appended text to file [file: {action.Data["file_path"]}][chatter: {senderDisplayName}][chatter id: {senderId}]");
break; break;
} }
@ -462,9 +462,14 @@ namespace TwitchChatTTS.Twitch.Redemptions
return false; return false;
} }
private string ReplaceContentText(string content, string username) private string ReplaceContentText(string content, string chatter, long chatterId, string message)
{ {
return content.Replace("%USER%", username) return content.Replace("%USER%", chatter)
.Replace("%chatter%", chatter)
.Replace("%chatterid%", chatterId.ToString())
.Replace("%broadcaster%", _user.TwitchUsername)
.Replace("%broadcasterid%", _user.TwitchUserId.ToString())
.Replace("%message%", message)
.Replace("\\n", "\n"); .Replace("\\n", "\n");
} }

View File

@ -37,7 +37,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
foreach (var action in actions) foreach (var action in actions)
try try
{ {
await _redemptionManager.Execute(action, message.RequesterUserLogin, long.Parse(message.RequesterUserId)); await _redemptionManager.Execute(action, message.RequesterUserLogin, long.Parse(message.RequesterUserId), string.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -71,7 +71,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
foreach (var action in actions) foreach (var action in actions)
try try
{ {
await _redemptionManager.Execute(action, message.RequesterUserLogin, long.Parse(message.RequesterUserId)); await _redemptionManager.Execute(action, message.RequesterUserLogin, long.Parse(message.RequesterUserId), string.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -1,4 +1,5 @@
using Serilog; using Serilog;
using TwitchChatTTS.Bus;
using TwitchChatTTS.Chat.Commands; using TwitchChatTTS.Chat.Commands;
using TwitchChatTTS.Chat.Commands.Limits; using TwitchChatTTS.Chat.Commands.Limits;
using TwitchChatTTS.Chat.Groups; using TwitchChatTTS.Chat.Groups;
@ -18,6 +19,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
private readonly IGroupPermissionManager _permissionManager; private readonly IGroupPermissionManager _permissionManager;
private readonly IUsagePolicy<long> _permissionPolicy; private readonly IUsagePolicy<long> _permissionPolicy;
private readonly IChatterGroupManager _chatterGroupManager; private readonly IChatterGroupManager _chatterGroupManager;
private readonly ServiceBusCentral _bus;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -27,6 +29,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
IGroupPermissionManager permissionManager, IGroupPermissionManager permissionManager,
IUsagePolicy<long> permissionPolicy, IUsagePolicy<long> permissionPolicy,
IChatterGroupManager chatterGroupManager, IChatterGroupManager chatterGroupManager,
ServiceBusCentral bus,
User user, User user,
ILogger logger ILogger logger
) )
@ -36,12 +39,9 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
_commands = commands; _commands = commands;
_permissionManager = permissionManager; _permissionManager = permissionManager;
_permissionPolicy = permissionPolicy; _permissionPolicy = permissionPolicy;
_chatterGroupManager = chatterGroupManager; _chatterGroupManager = chatterGroupManager;
_bus = bus;
_logger = logger; _logger = logger;
_permissionPolicy.Set("everyone", "tts", 100, TimeSpan.FromSeconds(15));
_permissionPolicy.Set("everyone", "tts.chat.messages.read", 3, TimeSpan.FromMilliseconds(15000));
} }
@ -58,9 +58,16 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
var groups = GetGroups(message.Badges, chatterId); var groups = GetGroups(message.Badges, chatterId);
var bits = GetTotalBits(fragments); var bits = GetTotalBits(fragments);
var commandResult = await CheckForChatCommand(message.Message.Text, message, groups); if (message.ChannelPointsCustomRewardId == null)
if (commandResult != ChatCommandResult.Unknown) {
return; var commandResult = await CheckForChatCommand(message.Message.Text, message, groups);
if (commandResult != ChatCommandResult.Unknown)
return;
}
else
{
_bus.Send(this, "chat_message_redemption", message);
}
string permission = GetPermissionPath(message.ChannelPointsCustomRewardId, bits); string permission = GetPermissionPath(message.ChannelPointsCustomRewardId, bits);
if (!HasPermission(chatterId, groups, permission)) if (!HasPermission(chatterId, groups, permission))
@ -106,7 +113,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
{ {
var defaultGroups = new string[] { "everyone" }; var defaultGroups = new string[] { "everyone" };
var badgesGroups = badges.Select(b => b.SetId).Select(GetGroupNameByBadgeName); var badgesGroups = badges.Select(b => b.SetId).Select(GetGroupNameByBadgeName);
var customGroups = _chatterGroupManager.GetGroupNamesFor(chatterId); var customGroups = _chatterGroupManager.GetGroupNamesFor(chatterId).ToArray();
return defaultGroups.Union(badgesGroups).Union(customGroups); return defaultGroups.Union(badgesGroups).Union(customGroups);
} }

View File

@ -1,4 +1,5 @@
using Serilog; using Serilog;
using TwitchChatTTS.Bus;
using TwitchChatTTS.Twitch.Redemptions; using TwitchChatTTS.Twitch.Redemptions;
using TwitchChatTTS.Twitch.Socket.Messages; using TwitchChatTTS.Twitch.Socket.Messages;
@ -8,16 +9,38 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
{ {
public string Name => "channel.channel_points_custom_reward_redemption.add"; public string Name => "channel.channel_points_custom_reward_redemption.add";
private readonly IDictionary<string, ChannelChatMessage> _messages;
private readonly IRedemptionManager _redemptionManager; private readonly IRedemptionManager _redemptionManager;
private readonly ServiceBusCentral _bus;
private readonly ILogger _logger; private readonly ILogger _logger;
public ChannelCustomRedemptionHandler( public ChannelCustomRedemptionHandler(
IRedemptionManager redemptionManager, IRedemptionManager redemptionManager,
ServiceBusCentral bus,
ILogger logger ILogger logger
) )
{ {
_messages = new Dictionary<string, ChannelChatMessage>();
_redemptionManager = redemptionManager; _redemptionManager = redemptionManager;
_bus = bus;
_logger = logger; _logger = logger;
var topic = _bus.GetTopic("chat_message_redemption");
topic.Subscribe((d) =>
{
var message = d.Value as ChannelChatMessage;
if (message != null && !string.IsNullOrEmpty(message.ChannelPointsCustomRewardId))
{
_messages.Add(message.ChannelPointsCustomRewardId, message);
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMilliseconds(5000));
_messages.Remove(message.ChannelPointsCustomRewardId);
});
}
});
} }
public async Task Execute(TwitchWebsocketClient sender, object data) public async Task Execute(TwitchWebsocketClient sender, object data)
@ -37,10 +60,32 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
} }
_logger.Debug($"Found {actions.Count()} actions for this Twitch channel point redemption [redeem: {message.Reward.Title}][redeem id: {message.Reward.Id}][transaction: {message.Id}]"); _logger.Debug($"Found {actions.Count()} actions for this Twitch channel point redemption [redeem: {message.Reward.Title}][redeem id: {message.Reward.Id}][transaction: {message.Id}]");
ChannelChatMessage? chatMessage = null;
if (actions.Any(a => a.HasMessage))
{
if (!_messages.TryGetValue(message.Reward.Id, out chatMessage))
{
DateTime start = DateTime.Now;
_logger.Debug("Waiting on redemption message...");
while (DateTime.Now - start < TimeSpan.FromMilliseconds(1000) && !_messages.ContainsKey(message.Reward.Id))
await Task.Delay(100);
if (!_messages.TryGetValue(message.Reward.Id, out chatMessage))
{
_logger.Warning("Chat message was not found within a second of the channel redemption being received. Skipping all redeemable actions.");
return;
}
}
if (chatMessage != null)
_logger.Information($"Linked redemption to chat message [redemption id: {message.Reward.Id}][message id: {chatMessage.MessageId}].");
}
foreach (var action in actions) foreach (var action in actions)
try try
{ {
await _redemptionManager.Execute(action, message.UserName, long.Parse(message.UserId));
await _redemptionManager.Execute(action, message.UserName, long.Parse(message.UserId), message.UserInput);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -36,7 +36,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
foreach (var action in actions) foreach (var action in actions)
try try
{ {
await _redemptionManager.Execute(action, message.UserName, long.Parse(message.UserId)); await _redemptionManager.Execute(action, message.UserName, long.Parse(message.UserId), string.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -38,15 +38,15 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
var actions = _redemptionManager.Get("subscription"); var actions = _redemptionManager.Get("subscription");
if (!actions.Any()) if (!actions.Any())
{ {
_logger.Debug($"No redeemable actions for this subscription was found [message: {message.Message.Text}]"); _logger.Debug($"No redeemable actions for this subscription was found [chatter id: {message.UserId}][chatter: {message.UserName}][message: {message.Message.Text}]");
return; return;
} }
_logger.Debug($"Found {actions.Count()} actions for this Twitch subscription [message: {message.Message.Text}]"); _logger.Debug($"Found {actions.Count()} actions for this Twitch subscription [chatter id: {message.UserId}][chatter: {message.UserName}][message: {message.Message.Text}]");
foreach (var action in actions) foreach (var action in actions)
try try
{ {
await _redemptionManager.Execute(action, message.UserName!, long.Parse(message.UserId!)); await _redemptionManager.Execute(action, message.UserName!, long.Parse(message.UserId!), message.Message.Text);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -0,0 +1,52 @@
using Serilog;
using TwitchChatTTS.Twitch.Redemptions;
using TwitchChatTTS.Twitch.Socket.Messages;
namespace TwitchChatTTS.Twitch.Socket.Handlers
{
public class ChannelSubscriptionEndHandler : ITwitchSocketHandler
{
public string Name => "channel.subscription.end";
private readonly IRedemptionManager _redemptionManager;
private readonly ILogger _logger;
public ChannelSubscriptionEndHandler(IRedemptionManager redemptionManager, ILogger logger)
{
_redemptionManager = redemptionManager;
_logger = logger;
}
public async Task Execute(TwitchWebsocketClient sender, object data)
{
if (data is not ChannelSubscriptionEndMessage message)
return;
_logger.Information($"Subscription ended [chatter: {message.UserLogin}][chatter id: {message.UserId}][Tier: {message.Tier}]");
try
{
var actions = _redemptionManager.Get("subscription.end");
if (!actions.Any())
{
_logger.Debug($"No redeemable actions for this subscription was found [subscriber: {message.UserLogin}][subscriber id: {message.UserId}]");
return;
}
_logger.Debug($"Found {actions.Count()} actions for this Twitch subscription [subscriber: {message.UserLogin}][subscriber id: {message.UserId}]");
foreach (var action in actions)
try
{
await _redemptionManager.Execute(action, message.UserName!, long.Parse(message.UserId!), string.Empty);
}
catch (Exception ex)
{
_logger.Error(ex, $"Failed to execute redeemable action [action: {action.Name}][action type: {action.Type}][redeem: subscription][subscriber: {message.UserLogin}][subscriber id: {message.UserId}]");
}
}
catch (Exception ex)
{
_logger.Error(ex, $"Failed to fetch the redeemable actions for subscription [subscriber: {message.UserLogin}][subscriber id: {message.UserId}]");
}
}
}
}

View File

@ -40,7 +40,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
foreach (var action in actions) foreach (var action in actions)
try try
{ {
await _redemptionManager.Execute(action, message.UserName ?? "Anonymous", message.UserId == null ? 0 : long.Parse(message.UserId)); await _redemptionManager.Execute(action, message.UserName ?? "Anonymous", message.UserId == null ? 0 : long.Parse(message.UserId), string.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -38,7 +38,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
foreach (var action in actions) foreach (var action in actions)
try try
{ {
await _redemptionManager.Execute(action, message.UserName!, long.Parse(message.UserId!)); await _redemptionManager.Execute(action, message.UserName!, long.Parse(message.UserId!), string.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -37,11 +37,12 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
_messageTypes.Add("channel.chat.clear_user_messages", typeof(ChannelChatClearUserMessage)); _messageTypes.Add("channel.chat.clear_user_messages", typeof(ChannelChatClearUserMessage));
_messageTypes.Add("channel.chat.message_delete", typeof(ChannelChatDeleteMessage)); _messageTypes.Add("channel.chat.message_delete", typeof(ChannelChatDeleteMessage));
_messageTypes.Add("channel.channel_points_custom_reward_redemption.add", typeof(ChannelCustomRedemptionMessage)); _messageTypes.Add("channel.channel_points_custom_reward_redemption.add", typeof(ChannelCustomRedemptionMessage));
_messageTypes.Add("channel.raid", typeof(ChannelRaidMessage));
_messageTypes.Add("channel.follow", typeof(ChannelFollowMessage)); _messageTypes.Add("channel.follow", typeof(ChannelFollowMessage));
_messageTypes.Add("channel.raid", typeof(ChannelRaidMessage));
_messageTypes.Add("channel.subscribe", typeof(ChannelSubscriptionMessage)); _messageTypes.Add("channel.subscribe", typeof(ChannelSubscriptionMessage));
_messageTypes.Add("channel.subscription.message", typeof(ChannelResubscriptionMessage)); _messageTypes.Add("channel.subscription.end", typeof(ChannelSubscriptionEndMessage));
_messageTypes.Add("channel.subscription.gift", typeof(ChannelSubscriptionGiftMessage)); _messageTypes.Add("channel.subscription.gift", typeof(ChannelSubscriptionGiftMessage));
_messageTypes.Add("channel.subscription.message", typeof(ChannelResubscriptionMessage));
} }
public Task Execute(TwitchWebsocketClient sender, object data) public Task Execute(TwitchWebsocketClient sender, object data)

View File

@ -61,6 +61,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
"channel.chat.clear", "channel.chat.clear",
"channel.chat.clear_user_messages", "channel.chat.clear_user_messages",
"channel.subscribe", "channel.subscribe",
"channel.subscription.end",
"channel.subscription.gift", "channel.subscription.gift",
"channel.subscription.message", "channel.subscription.message",
"channel.ad_break.begin", "channel.ad_break.begin",

View File

@ -15,6 +15,12 @@ namespace TwitchChatTTS.Twitch.Socket.Messages
public TwitchReplyInfo? Reply { get; set; } public TwitchReplyInfo? Reply { get; set; }
public string? ChannelPointsCustomRewardId { get; set; } public string? ChannelPointsCustomRewardId { get; set; }
public string? ChannelPointsAnimationId { get; set; } public string? ChannelPointsAnimationId { get; set; }
public string? SourceBroadcasterUserId { get; set; }
public string? SourceBroadcasterUserName { get; set; }
public string? SourceBroadcasterUserLogin { get; set; }
public string? SourceMessageId { get; set; }
public TwitchBadge[]? SourceBadges { get; set; }
} }
public class TwitchChatMessageInfo public class TwitchChatMessageInfo

View File

@ -9,6 +9,7 @@ namespace TwitchChatTTS.Twitch.Socket.Messages
public required string UserId { get; set; } public required string UserId { get; set; }
public required string UserLogin { get; set; } public required string UserLogin { get; set; }
public required string UserName { get; set; } public required string UserName { get; set; }
public required string UserInput { get; set; }
public required string Status { get; set; } public required string Status { get; set; }
public DateTime RedeemedAt { get; set; } public DateTime RedeemedAt { get; set; }
public required RedemptionReward Reward { get; set; } public required RedemptionReward Reward { get; set; }

View File

@ -0,0 +1,7 @@
namespace TwitchChatTTS.Twitch.Socket.Messages
{
public class ChannelSubscriptionEndMessage : ChannelSubscriptionData
{
public bool IsGift { get; set; }
}
}