fish-station/Content.Server/_Sunrise/Chat/AntiSpamSystem.cs
2025-06-16 01:26:09 +03:00

99 lines
4.2 KiB
C#

using System.Linq;
using Content.Shared.Popups;
using Robust.Shared.Network;
using Content.Server.Chat.Systems;
using Robust.Shared.Timing;
using Content.Shared.Speech.Muting;
using Content.Shared.StatusEffect;
using Content.Shared.Mobs.Components;
using Robust.Shared.Player;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared._Sunrise.SunriseCCVars;
using Robust.Shared.Configuration;
namespace Content.Server._Sunrise.AntiSpam;
public sealed class AntiSpamSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly IConfigurationManager _configuration = default!;
private static readonly Dictionary<NetUserId, List<(string Message, float Time)>> MessageHistory = new();
private EntityQuery<HumanoidAppearanceComponent> _humanoidQuery;
private bool _enabled; //eneble-disable mute system
private int _counterShort; // allowed number of messages for TimeShort duration
private int _counterLong; // allowed number of messages for TimeLong duration
private float _muteDuration; // mute time
private float _timeShort; // minimum check time
private float _timeLong; // maximum check time
public override void Initialize()
{
SubscribeLocalEvent<MobStateComponent, TrySendICMessageEvent>(SpamICCheck);
SubscribeNetworkEvent<RoundRestartCleanupEvent>(RoundRestartHistoryCleanup);
_humanoidQuery = GetEntityQuery<HumanoidAppearanceComponent>();
_configuration.OnValueChanged(SunriseCCVars.AntiSpamEnable, enabled => _enabled = enabled, true);
_configuration.OnValueChanged(SunriseCCVars.AntiSpamCounterShort, val => _counterShort = val, true);
_configuration.OnValueChanged(SunriseCCVars.AntiSpamCounterLong, val => _counterLong = val, true);
_configuration.OnValueChanged(SunriseCCVars.AntiSpamMuteDuration, val => _muteDuration = val, true);
_configuration.OnValueChanged(SunriseCCVars.AntiSpamTimeShort, val => _timeShort = val, true);
_configuration.OnValueChanged(SunriseCCVars.AntiSpamTimeLong, val => _timeLong = val, true);
}
private void SpamICCheck(Entity<MobStateComponent> ent, ref TrySendICMessageEvent args)
{
if (!_enabled)
return;
if (args.Player == null)
return;
if (!_humanoidQuery.HasComp(ent.Owner))
return;
if (args.DesiredType == InGameICChatType.Emote) // ignore emote chat
return;
var now = (float)_timing.CurTime.TotalSeconds;
if (!MessageHistory.TryGetValue(args.Player.UserId, out var history))
{
history = new List<(string Message, float Time)>();
MessageHistory[args.Player.UserId] = history;
}
// Cleaning up old records (older than 5 seconds)
history.RemoveAll(m => now - m.Time > _timeLong);
var currentMessage = args.Message;
// Add current message
history.Add((currentMessage, now));
// Count repetitions for the last 1.5 and 5 seconds
var repeatsInShort = history.Count(m => m.Message == currentMessage && now - m.Time <= _timeShort);
var repeatsInLong = history.Count(m => m.Message == currentMessage);
if (repeatsInShort > _counterShort || repeatsInLong > _counterLong)
{
history.Clear(); // reset spam history
args.Cancel();
var selfMessage = Loc.GetString("spam-mute-text-self");
_popup.PopupEntity(selfMessage, ent, PopupType.Large);
_statusEffects.TryAddStatusEffect<MutedComponent>(ent, "Muted", TimeSpan.FromSeconds(_muteDuration), true);
}
}
private void RoundRestartHistoryCleanup(RoundRestartCleanupEvent ev)
{
MessageHistory.Clear();
}
}
public sealed class TrySendICMessageEvent(string message, InGameICChatType desiredType, ICommonSession? player = null) : CancellableEntityEventArgs
{
public readonly string Message = message;
public readonly InGameICChatType DesiredType = desiredType;
public readonly ICommonSession? Player = player;
}