Raid Spam Prevention is now applied to joined chats.

This commit is contained in:
Tom 2024-08-07 23:21:56 +00:00
parent ca5b1c0733
commit cc81999abe
5 changed files with 73 additions and 41 deletions

View File

@ -214,27 +214,32 @@ namespace TwitchChatTTS.Chat.Commands
return; return;
} }
string targetUserId = fragment.Mention!.UserId!; string targetUserId = fragment.Mention!.UserId;
if (targetUserId == _user.TwitchUserId.ToString()) if (targetUserId == _user.TwitchUserId.ToString())
{ {
_logger.Warning("Cannot join yourself."); _logger.Warning("Cannot join yourself.");
return; return;
} }
string targetUserLogin = fragment.Mention!.UserLogin;
string[] subscriptions = ["channel.chat.message", "channel.chat.message_delete", "channel.chat.clear_user_messages"]; string[] subscriptions = ["channel.chat.message", "channel.chat.message_delete", "channel.chat.clear_user_messages"];
foreach (var subscription in subscriptions) foreach (var subscription in subscriptions)
await Subscribe(subscription, targetUserId, targetUserLogin, async () => await _client.CreateEventSubscription(subscription, "1", _twitch.SessionId, _user.TwitchUserId.ToString(), targetUserId));
await Subscribe("channel.raid", targetUserId, targetUserLogin, async () => await _client.CreateChannelRaidEventSubscription("1", _twitch.SessionId, targetUserId));
_logger.Information($"Joined chat room [channel: {fragment.Mention.UserLogin}][channel: {targetUserLogin}][channel id: {targetUserId}][invoker: {message.ChatterUserLogin}][id: {message.ChatterUserId}]");
}
private async Task Subscribe(string subscription, string targetId, string targetName, Func<Task<EventResponse<NotificationInfo>?>> subscribe)
{
_logger.Debug($"Attempting to subscribe to Twitch events [subscription: {subscription}][target channel: {targetName}][target channel id: {targetId}]");
var data = await subscribe.Invoke();
var info = data?.Data?.FirstOrDefault();
if (info == null)
{ {
_logger.Debug($"Attempting to subscribe to Twitch events [subscription: {subscription}]"); _logger.Warning("Could not find the subscription id.");
var data = await _client.CreateEventSubscription(subscription, "1", _twitch.SessionId, _user.TwitchUserId.ToString(), targetUserId); return;
var info = data?.Data?.FirstOrDefault();
if (info == null)
{
_logger.Warning("Could not find the subscription id.");
continue;
}
_twitch.AddSubscription(targetUserId, subscription, info.Id);
} }
_logger.Information($"Joined chat room [channel: {fragment.Mention.UserLogin}][channel id: {targetUserId}][invoker: {message.ChatterUserLogin}][id: {message.ChatterUserId}]"); _twitch.AddSubscription(targetId, subscription, info.Id);
} }
} }
@ -277,7 +282,7 @@ namespace TwitchChatTTS.Chat.Commands
return; return;
} }
string[] subscriptions = ["channel.chat.message", "channel.chat.message_delete", "channel.chat.clear_user_messages"]; string[] subscriptions = ["channel.chat.message", "channel.chat.message_delete", "channel.chat.clear_user_messages", "channel.raid"];
foreach (var subscription in subscriptions) foreach (var subscription in subscriptions)
{ {
var subscriptionId = _twitch.GetSubscriptionId(targetUserId, subscription); var subscriptionId = _twitch.GetSubscriptionId(targetUserId, subscription);

View File

@ -98,12 +98,6 @@ 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);
@ -166,6 +160,12 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
} }
} }
if (_user.Raids.TryGetValue(message.BroadcasterUserId, out var raid) && !raid.Chatters.Contains(chatterId))
{
_logger.Information($"Potential chat message from raider ignored due to potential raid message spam [chatter: {message.ChatterUserLogin}][chatter id: {message.ChatterUserId}]");
return;
}
// Replace filtered words. // Replace filtered words.
if (_user.RegexFilters != null) if (_user.RegexFilters != null)
{ {

View File

@ -26,40 +26,67 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
return; return;
_logger.Information($"A raid has started. Starting raid spam prevention. [from: {message.FromBroadcasterUserLogin}][from id: {message.FromBroadcasterUserId}]."); _logger.Information($"A raid has started. Starting raid spam prevention. [from: {message.FromBroadcasterUserLogin}][from id: {message.FromBroadcasterUserId}].");
var chatters = await _api.GetChatters(_user.TwitchUserId.ToString(), _user.TwitchUserId.ToString()); EventResponse<ChatterMessage>? chatters = null;
if (chatters?.Data == null)
{
_logger.Error("Could not fetch the list of chatters in chat.");
return;
}
var date = DateTime.Now; if (!_user.Raids.ContainsKey(message.ToBroadcasterUserId))
lock (_lock)
{ {
_user.RaidStart = date; await _api.GetChatters(_user.TwitchUserId.ToString(), _user.TwitchUserId.ToString());
if (_user.AllowedChatters == null) if (chatters?.Data == null)
{ {
var chatterIds = chatters.Data.Select(c => long.Parse(c.UserId)); var extraErrorInfo = _user.TwitchUserId.ToString() != message.ToBroadcasterUserId ? " Ensure you have moderator status in your joined channel(s) to prevent raid spam." : string.Empty;
_user.AllowedChatters = new HashSet<long>(chatterIds); _logger.Error("Could not fetch the list of chatters in chat." + extraErrorInfo);
return;
} }
} }
Task.Run(async () => await EndOfRaidSpamProtection(date)); var startDate = DateTime.Now;
var endDate = startDate + TimeSpan.FromSeconds(30);
lock (_lock)
{
if (_user.Raids.TryGetValue(message.ToBroadcasterUserId, out var raid))
raid.RaidSpamPreventionEndDate = endDate;
else
{
var chatterIds = chatters!.Data!.Select(c => long.Parse(c.UserId));
_user.Raids.Add(message.ToBroadcasterUserId, new RaidInfo(endDate, new HashSet<long>(chatterIds)));
}
}
Task.Run(async () => await EndOfRaidSpamProtection(message.ToBroadcasterUserId, endDate));
} }
private async Task EndOfRaidSpamProtection(DateTime date) private async Task EndOfRaidSpamProtection(string raidedId, DateTime endDate)
{ {
await Task.Delay(TimeSpan.FromSeconds(30)); await Task.Delay(endDate - DateTime.Now);
lock (_lock) lock (_lock)
{ {
if (_user.RaidStart == date) if (_user.Raids.TryGetValue(raidedId, out var raid))
{ {
_logger.Information("Raid message spam prevention ended."); if (raid.RaidSpamPreventionEndDate == endDate)
_user.RaidStart = null; {
_user.AllowedChatters = null; _logger.Information("Raid message spam prevention ended.");
_user.Raids.Remove(raidedId);
}
else
_logger.Debug("Raid spam prevention would have stopped now if it wasn't for the consecutive raid.");
} }
else
_logger.Error("Something went wrong ending a raid spam prevention.");
} }
} }
} }
public sealed class RaidInfo
{
public DateTime RaidSpamPreventionEndDate { get; set; }
public HashSet<long> Chatters { get; set; }
public RaidInfo(DateTime raidEnd, HashSet<long> chatters)
{
RaidSpamPreventionEndDate = raidEnd;
Chatters = chatters;
}
}
} }

View File

@ -28,7 +28,7 @@ public class TwitchApiClient
}); });
} }
public async Task<EventResponse<NotificationInfo>?> CreateEventSubscription(string type, string version, string sessionId, IDictionary<string, string> conditions) private async Task<EventResponse<NotificationInfo>?> CreateEventSubscription(string type, string version, string sessionId, IDictionary<string, string> conditions)
{ {
var subscriptionData = new EventSubscriptionMessage(type, version, sessionId, conditions); var subscriptionData = new EventSubscriptionMessage(type, version, sessionId, conditions);
var base_url = _configuration.Environment == "PROD" || string.IsNullOrWhiteSpace(_configuration.Twitch?.ApiUrl) var base_url = _configuration.Environment == "PROD" || string.IsNullOrWhiteSpace(_configuration.Twitch?.ApiUrl)
@ -58,7 +58,7 @@ public class TwitchApiClient
conditions.Add("from_broadcaster_user_id", from); conditions.Add("from_broadcaster_user_id", from);
if (to != null) if (to != null)
conditions.Add("to_broadcaster_user_id", to); conditions.Add("to_broadcaster_user_id", to);
return await CreateEventSubscription("channel.raid", version, sessionId, conditions); return await CreateEventSubscription("channel.raid", version, sessionId, conditions);
} }

View File

@ -1,6 +1,7 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using TwitchChatTTS.Twitch.Socket.Handlers;
namespace TwitchChatTTS namespace TwitchChatTTS
{ {
@ -22,8 +23,7 @@ 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 IDictionary<string, RaidInfo> Raids { get; set; } = new Dictionary<string, RaidInfo>();
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]