Added more information to logs when receiving subscriptions. Added raid message spam prevention. Added bit message detection - requires tts.chat.bits.read permission for TTS."

This commit is contained in:
Tom 2024-08-07 20:30:03 +00:00
parent d6b66b3446
commit e4a11382ef
11 changed files with 124 additions and 18 deletions

View File

@ -77,7 +77,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
return; // new MessageResult(MessageStatus.NotReady, -1, -1); return; // new MessageResult(MessageStatus.NotReady, -1, -1);
} }
var msg = message.Message.Text; var msg = string.Join(string.Empty, message.Message.Fragments.Where(f => f.Type != "cheermote").Select(f => f.Text)).Trim();
var chatterId = long.Parse(message.ChatterUserId); var chatterId = long.Parse(message.ChatterUserId);
var tasks = new List<Task>(); var tasks = new List<Task>();
@ -98,12 +98,23 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
return; return;
} }
if (_user.AllowedChatters != null && !_user.AllowedChatters.Contains(chatterId))
{
_logger.Information("Potential chat message from raider ignored due to potential raid message spam.");
return;
}
if (message.Reply != null) if (message.Reply != null)
msg = msg.Substring(message.Reply.ParentUserLogin.Length + 2); msg = msg.Substring(message.Reply.ParentUserLogin.Length + 2);
var bits = message.Message.Fragments.Where(f => f.Type == "cheermote" && f.Cheermote != null)
.Select(f => f.Cheermote!.Bits)
.Sum();
var permissionPath = "tts.chat.messages.read"; var permissionPath = "tts.chat.messages.read";
if (!string.IsNullOrWhiteSpace(message.ChannelPointsCustomRewardId)) if (!string.IsNullOrWhiteSpace(message.ChannelPointsCustomRewardId))
permissionPath = "tts.chat.redemptions.read"; permissionPath = "tts.chat.redemptions.read";
else if (bits > 0)
permissionPath = "tts.chat.bits.read";
var permission = chatterId == _user.OwnerId ? true : _permissionManager.CheckIfAllowed(groups, permissionPath); var permission = chatterId == _user.OwnerId ? true : _permissionManager.CheckIfAllowed(groups, permissionPath);
if (permission != true) if (permission != true)

View File

@ -0,0 +1,59 @@
using Serilog;
using TwitchChatTTS.Twitch.Socket.Messages;
namespace TwitchChatTTS.Twitch.Socket.Handlers
{
public class ChannelRaidHandler : ITwitchSocketHandler
{
public string Name => "channel.raid";
private readonly TwitchApiClient _api;
private readonly User _user;
private readonly ILogger _logger;
private readonly object _lock;
public ChannelRaidHandler(TwitchApiClient api, User user, ILogger logger)
{
_api = api;
_user = user;
_logger = logger;
_lock = new object();
}
public async Task Execute(TwitchWebsocketClient sender, object data)
{
if (data is not ChannelRaidMessage message)
return;
var chatters = await _api.GetChatters(message.ToBroadcasterUserId, message.ToBroadcasterUserLogin);
if (chatters?.Data == null)
{
_logger.Error("Could not fetch the list of chatters in chat.");
return;
}
var date = DateTime.Now;
lock (_lock)
{
_user.RaidStart = date;
if (_user.AllowedChatters == null)
{
var chatterIds = chatters.Data.Select(c => long.Parse(c.UserId));
_user.AllowedChatters = new HashSet<long>(chatterIds);
}
}
await Task.Delay(TimeSpan.FromSeconds(30));
lock (_lock)
{
if (_user.RaidStart == date)
{
_logger.Information("Raid message spam prevention ended.");
_user.RaidStart = null;
_user.AllowedChatters = null;
}
}
}
}
}

View File

@ -22,7 +22,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
if (data is not ChannelResubscriptionMessage message) if (data is not ChannelResubscriptionMessage message)
return; return;
_logger.Debug("Resubscription occured."); _logger.Debug($"Resubscription occured [chatter: {message.UserLogin}][chatter id: {message.UserId}][Tier: {message.Tier}][Streak: {message.StreakMonths}][Cumulative: {message.CumulativeMonths}][Duration: {message.DurationMonths}]");
try try
{ {
var actions = _redemptionManager.Get("subscription"); var actions = _redemptionManager.Get("subscription");
@ -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!));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -22,7 +22,11 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
if (data is not ChannelSubscriptionGiftMessage message) if (data is not ChannelSubscriptionGiftMessage message)
return; return;
_logger.Debug("Gifted subscription occured."); if (message.IsAnonymous)
_logger.Debug($"Gifted subscription occured [chatter: Anonymous][Tier: {message.Tier}][Count: {message.Total}]");
else
_logger.Debug($"Gifted subscription occured [chatter: {message.UserLogin}][chatter id: {message.UserId}][Tier: {message.Tier}][Count: {message.Total}][Cumulative Count: {message.CumulativeTotal}]");
try try
{ {
var actions = _redemptionManager.Get("subscription.gift"); var actions = _redemptionManager.Get("subscription.gift");
@ -36,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, long.Parse(message.UserId)); await _redemptionManager.Execute(action, message.UserName ?? "Anonymous", message.UserId == null ? 0 : long.Parse(message.UserId));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -24,7 +24,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
if (message.IsGifted) if (message.IsGifted)
return; return;
_logger.Debug("Subscription occured."); _logger.Debug($"Subscription occured [chatter: {message.UserLogin}][chatter id: {message.UserId}][Tier: {message.Tier}]");
try try
{ {
var actions = _redemptionManager.Get("subscription"); var actions = _redemptionManager.Get("subscription");
@ -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!));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -22,12 +22,8 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
public async Task Execute(TwitchWebsocketClient sender, object data) public async Task Execute(TwitchWebsocketClient sender, object data)
{ {
if (sender == null)
return;
if (data is not SessionWelcomeMessage message) if (data is not SessionWelcomeMessage message)
return; return;
if (_api == null)
return;
if (string.IsNullOrEmpty(message.Session.Id)) if (string.IsNullOrEmpty(message.Session.Id))
{ {
@ -43,15 +39,15 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
string[] subscriptionsv1 = [ string[] subscriptionsv1 = [
"channel.chat.message", "channel.chat.message",
"channel.chat.message_delete", "channel.chat.message_delete",
"channel.chat.notification",
"channel.chat.clear", "channel.chat.clear",
"channel.chat.clear_user_messages", "channel.chat.clear_user_messages",
"channel.ad_break.begin",
"channel.subscribe", "channel.subscribe",
"channel.subscription.gift", "channel.subscription.gift",
"channel.subscription.message", "channel.subscription.message",
"channel.ad_break.begin",
"channel.ban", "channel.ban",
"channel.channel_points_custom_reward_redemption.add" "channel.channel_points_custom_reward_redemption.add",
"channel.raid"
]; ];
string[] subscriptionsv2 = [ string[] subscriptionsv2 = [
"channel.follow", "channel.follow",
@ -92,7 +88,6 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
var response = await _api.CreateEventSubscription(subscriptionName, version, sessionId, broadcasterId); var response = await _api.CreateEventSubscription(subscriptionName, version, sessionId, broadcasterId);
if (response == null) if (response == null)
{ {
_logger.Error($"Failed to create an event subscription [subscription type: {subscriptionName}][reason: response is null]");
return; return;
} }
if (response.Data == null) if (response.Data == null)

View File

@ -0,0 +1,13 @@
namespace TwitchChatTTS.Twitch.Socket.Messages
{
public class ChannelRaidMessage
{
public string FromBroadcasterUserId { get; set; }
public string FromBroadcasterUserLogin { get; set; }
public string FromBroadcasterUserName { get; set; }
public string ToBroadcasterUserId { get; set; }
public string ToBroadcasterUserLogin { get; set; }
public string ToBroadcasterUserName { get; set; }
public int Viewers { get; set; }
}
}

View File

@ -2,9 +2,9 @@ namespace TwitchChatTTS.Twitch.Socket.Messages
{ {
public class ChannelSubscriptionData public class ChannelSubscriptionData
{ {
public string UserId { get; set; } public string? UserId { get; set; }
public string UserLogin { get; set; } public string? UserLogin { get; set; }
public string UserName { get; set; } public string? UserName { get; set; }
public string BroadcasterUserId { get; set; } public string BroadcasterUserId { get; set; }
public string BroadcasterUserLogin { get; set; } public string BroadcasterUserLogin { get; set; }
public string BroadcasterUserName { get; set; } public string BroadcasterUserName { get; set; }

View File

@ -0,0 +1,9 @@
namespace TwitchChatTTS.Twitch.Socket.Messages
{
public class ChatterMessage
{
public string UserId { get; set; }
public string UserLogin { get; set; }
public string UserName { get; set; }
}
}

View File

@ -51,6 +51,19 @@ public class TwitchApiClient
await _web.Delete($"{base_url}/eventsub/subscriptions?id=" + subscriptionId); await _web.Delete($"{base_url}/eventsub/subscriptions?id=" + subscriptionId);
} }
public async Task<EventResponse<ChatterMessage>?> GetChatters(string broadcasterId, string? moderatorId = null)
{
moderatorId ??= broadcasterId;
var response = await _web.Get($"https://api.twitch.tv/helix/chat/chatters?broadcaster_id={broadcasterId}&moderator_id={moderatorId}");
if (response.StatusCode == HttpStatusCode.Accepted)
{
_logger.Debug($"Twitch API call [type: get chatters][response: {await response.Content.ReadAsStringAsync()}]");
return await response.Content.ReadFromJsonAsync(typeof(EventResponse<ChatterMessage>)) as EventResponse<ChatterMessage>;
}
_logger.Error($"Twitch API call failed [type: get chatters][response: {await response.Content.ReadAsStringAsync()}]");
return null;
}
public async Task<EventResponse<NotificationInfo>?> GetSubscriptions(string? status = null, string? broadcasterId = null, string? after = null) public async Task<EventResponse<NotificationInfo>?> GetSubscriptions(string? status = null, string? broadcasterId = null, string? after = null)
{ {
List<string> queryParams = new List<string>(); List<string> queryParams = new List<string>();

View File

@ -22,6 +22,8 @@ namespace TwitchChatTTS
// voice names // voice names
public HashSet<string> VoicesEnabled { get => _voicesEnabled; set { _voicesEnabled = value; VoiceNameRegex = GenerateEnabledVoicesRegex(); } } public HashSet<string> VoicesEnabled { get => _voicesEnabled; set { _voicesEnabled = value; VoiceNameRegex = GenerateEnabledVoicesRegex(); } }
public DateTime? RaidStart { get; set; }
public HashSet<long>? AllowedChatters { get; set; }
public HashSet<long> Chatters { get; set; } public HashSet<long> Chatters { get; set; }
public TTSWordFilter[] RegexFilters { get; set; } public TTSWordFilter[] RegexFilters { get; set; }
[JsonIgnore] [JsonIgnore]