diff --git a/Plugins/Mute/Commands/MuteCommand.cs b/Plugins/Mute/Commands/MuteCommand.cs index fc0c28218..5d6dbabd6 100644 --- a/Plugins/Mute/Commands/MuteCommand.cs +++ b/Plugins/Mute/Commands/MuteCommand.cs @@ -34,6 +34,12 @@ public MuteCommand(CommandConfiguration config, ITranslationLookup translationLo public override async Task ExecuteAsync(GameEvent gameEvent) { + if (gameEvent.Origin.ClientId == gameEvent.Target.ClientId) + { + gameEvent.Origin.Tell(_translationLookup["COMMANDS_DENY_SELF_TARGET"]); + return; + } + if (await Plugin.MuteManager.Mute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, null, gameEvent.Data)) { gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_MUTED"] diff --git a/Plugins/Mute/Commands/TempMuteCommand.cs b/Plugins/Mute/Commands/TempMuteCommand.cs index 6914ad77d..3263c1dcd 100644 --- a/Plugins/Mute/Commands/TempMuteCommand.cs +++ b/Plugins/Mute/Commands/TempMuteCommand.cs @@ -42,6 +42,12 @@ public TempMuteCommand(CommandConfiguration config, ITranslationLookup translati public override async Task ExecuteAsync(GameEvent gameEvent) { + if (gameEvent.Origin.ClientId == gameEvent.Target.ClientId) + { + gameEvent.Origin.Tell(_translationLookup["COMMANDS_DENY_SELF_TARGET"]); + return; + } + var match = Regex.Match(gameEvent.Data, TempBanRegex); if (match.Success) { diff --git a/Plugins/Mute/Commands/UnmuteCommand.cs b/Plugins/Mute/Commands/UnmuteCommand.cs index fd0aead25..00ad125bc 100644 --- a/Plugins/Mute/Commands/UnmuteCommand.cs +++ b/Plugins/Mute/Commands/UnmuteCommand.cs @@ -34,6 +34,12 @@ public UnmuteCommand(CommandConfiguration config, ITranslationLookup translation public override async Task ExecuteAsync(GameEvent gameEvent) { + if (gameEvent.Origin.ClientId == gameEvent.Target.ClientId) + { + gameEvent.Origin.Tell(_translationLookup["COMMANDS_DENY_SELF_TARGET"]); + return; + } + if (await Plugin.MuteManager.Unmute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, gameEvent.Data)) { gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_UNMUTED"] diff --git a/Plugins/Mute/MuteManager.cs b/Plugins/Mute/MuteManager.cs index b3ce355a2..d9239aa03 100644 --- a/Plugins/Mute/MuteManager.cs +++ b/Plugins/Mute/MuteManager.cs @@ -1,4 +1,7 @@ -using Data.Models; +using Data.Abstractions; +using Data.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SharedLibraryCore; using SharedLibraryCore.Database.Models; @@ -13,13 +16,15 @@ public class MuteManager private readonly IMetaServiceV2 _metaService; private readonly ITranslationLookup _translationLookup; private readonly ILogger _logger; + private readonly IDatabaseContextFactory _databaseContextFactory; private readonly SemaphoreSlim _onMuteAction = new(1, 1); - public MuteManager(IMetaServiceV2 metaService, ITranslationLookup translationLookup, ILogger logger) + public MuteManager(IServiceProvider serviceProvider) { - _metaService = metaService; - _translationLookup = translationLookup; - _logger = logger; + _metaService = serviceProvider.GetRequiredService(); + _translationLookup = serviceProvider.GetRequiredService(); + _logger = serviceProvider.GetRequiredService>(); + _databaseContextFactory = serviceProvider.GetRequiredService(); } public static bool IsExpiredMute(MuteStateMeta muteStateMeta) => @@ -98,11 +103,13 @@ public async Task Unmute(Server server, EFClient origin, EFClient target, if (clientMuteMeta.MuteState is MuteState.Unmuted && clientMuteMeta.CommandExecuted) return false; if (!target.IsIngame && clientMuteMeta.MuteState is MuteState.Unmuting) return false; - if (clientMuteMeta.MuteState is not MuteState.Unmuting) + if (clientMuteMeta.MuteState is not MuteState.Unmuting && origin.ClientId != 1) { await CreatePenalty(MuteState.Unmuted, origin, target, DateTime.UtcNow, reason); } + await ExpireMutePenalties(target); + clientMuteMeta = new MuteStateMeta { Expiration = DateTime.UtcNow, @@ -130,7 +137,6 @@ private async Task CreatePenalty(MuteState muteState, EFClient origin, EFClient Offender = target, Offense = reason, Punisher = origin, - Active = true, Link = target.AliasLink }; _logger.LogDebug("Creating new {MuteState} Penalty for {Target} with reason {Reason}", @@ -138,6 +144,24 @@ private async Task CreatePenalty(MuteState muteState, EFClient origin, EFClient await newPenalty.TryCreatePenalty(Plugin.Manager.GetPenaltyService(), _logger); } + private async Task ExpireMutePenalties(EFClient client) + { + await using var context = _databaseContextFactory.CreateContext(); + var mutePenalties = await context.Penalties + .Where(penalty => penalty.OffenderId == client.ClientId && + (penalty.Type == EFPenalty.PenaltyType.Mute || + penalty.Type == EFPenalty.PenaltyType.TempMute) && + (penalty.Expires == null || penalty.Expires > DateTime.UtcNow)) + .ToListAsync(); + + foreach (var mutePenalty in mutePenalties) + { + mutePenalty.Expires = DateTime.UtcNow; + } + + await context.SaveChangesAsync(); + } + public static async Task PerformGameCommand(Server server, EFClient? client, MuteStateMeta muteStateMeta) { if (client is null || !client.IsIngame) return; diff --git a/Plugins/Mute/MuteStateMeta.cs b/Plugins/Mute/MuteStateMeta.cs index b6951649c..d37fb81f9 100644 --- a/Plugins/Mute/MuteStateMeta.cs +++ b/Plugins/Mute/MuteStateMeta.cs @@ -4,7 +4,7 @@ namespace Mute; public class MuteStateMeta { - public string Reason { get; set; } = string.Empty; + public string? Reason { get; set; } public DateTime? Expiration { get; set; } public MuteState MuteState { get; set; } [JsonIgnore] public bool CommandExecuted { get; set; } diff --git a/Plugins/Mute/Plugin.cs b/Plugins/Mute/Plugin.cs index fe5852c86..6df57b0e6 100644 --- a/Plugins/Mute/Plugin.cs +++ b/Plugins/Mute/Plugin.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Data.Abstractions; +using Microsoft.Extensions.Logging; using Mute.Commands; using SharedLibraryCore; using SharedLibraryCore.Commands; @@ -12,7 +13,7 @@ namespace Mute; public class Plugin : IPlugin { public string Name => "Mute"; - public float Version => (float) Utilities.GetVersionAsDouble(); + public float Version => (float)Utilities.GetVersionAsDouble(); public string Author => "Amos"; public const string MuteKey = "IW4MMute"; @@ -24,12 +25,12 @@ public class Plugin : IPlugin private readonly IRemoteCommandService _remoteCommandService; private static readonly string MuteInteraction = "Webfront::Profile::Mute"; - public Plugin(ILogger logger, IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration, - ITranslationLookup translationLookup, IRemoteCommandService remoteCommandService) + public Plugin(ILogger logger, IInteractionRegistration interactionRegistration, + IRemoteCommandService remoteCommandService, IServiceProvider serviceProvider) { _interactionRegistration = interactionRegistration; _remoteCommandService = remoteCommandService; - MuteManager = new MuteManager(metaService, translationLookup, logger); + MuteManager = new MuteManager(serviceProvider); } public async Task OnEventAsync(GameEvent gameEvent, Server server) @@ -56,7 +57,7 @@ muteMetaJoin.Expiration is not null case MuteState.Unmuting: // Handle unmute of unmuted players. await MuteManager.Unmute(server, Utilities.IW4MAdminClient(), gameEvent.Origin, - muteMetaJoin.Reason); + muteMetaJoin.Reason ?? string.Empty); gameEvent.Origin.Tell(Utilities.CurrentLocalization .LocalizationIndex["PLUGINS_MUTE_COMMANDS_UNMUTE_TARGET_UNMUTED"] .FormatExt(muteMetaJoin.Reason)); @@ -134,7 +135,7 @@ public Task OnLoadAsync(IManager manager) _interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, token) => { - if (!targetClientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game) game.Value)) + if (!targetClientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game)game.Value)) { return null; } @@ -162,7 +163,7 @@ private InteractionData CreateMuteInteraction(int targetClientId, Server server, Name = "Reason", Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_REASON"], Type = "text", - Values = (Dictionary?) null + Values = (Dictionary?)null }; var durationInput = new @@ -170,7 +171,7 @@ private InteractionData CreateMuteInteraction(int targetClientId, Server server, Name = "Duration", Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_DURATION"], Type = "select", - Values = (Dictionary?) new Dictionary + Values = (Dictionary?)new Dictionary { {"5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture()}, {"30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture()}, diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index ec642c817..a226fb784 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -131,7 +131,7 @@ public async Task> GetRecentPenalties(int count, int offset, } private static readonly EFPenalty.PenaltyType[] LinkedPenalties = - { EFPenalty.PenaltyType.Ban, EFPenalty.PenaltyType.Flag, EFPenalty.PenaltyType.TempBan }; + { EFPenalty.PenaltyType.Ban, EFPenalty.PenaltyType.Flag, EFPenalty.PenaltyType.TempBan, EFPenalty.PenaltyType.TempMute, EFPenalty.PenaltyType.Mute }; private static readonly Expression> Filter = p => LinkedPenalties.Contains(p.Type) && p.Active && (p.Expires == null || p.Expires > DateTime.UtcNow); diff --git a/WebfrontCore/Controllers/Client/ClientController.cs b/WebfrontCore/Controllers/Client/ClientController.cs index 9466bb598..7590b386a 100644 --- a/WebfrontCore/Controllers/Client/ClientController.cs +++ b/WebfrontCore/Controllers/Client/ClientController.cs @@ -162,7 +162,13 @@ public async Task Profile(int id, MetaType? metaFilterType, Cance }); } - clientDto.ActivePenalty = activePenalties.OrderByDescending(_penalty => _penalty.Type).FirstOrDefault(); + // Reducing the enum value for Temp/Mute so bans appear in client banner first + clientDto.ActivePenalty = activePenalties.MaxBy(penalty => penalty.Type switch + { + EFPenalty.PenaltyType.TempMute => 0, + EFPenalty.PenaltyType.Mute => 1, + _ => (int)penalty.Type + }); clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive)); var strippedName = clientDto.Name.StripColors(); diff --git a/WebfrontCore/Views/Client/Profile/Index.cshtml b/WebfrontCore/Views/Client/Profile/Index.cshtml index fd1f0fb83..693d453df 100644 --- a/WebfrontCore/Views/Client/Profile/Index.cshtml +++ b/WebfrontCore/Views/Client/Profile/Index.cshtml @@ -21,6 +21,8 @@ EFPenalty.PenaltyType.Ban => "alert-danger", EFPenalty.PenaltyType.Flag => "alert-secondary", EFPenalty.PenaltyType.TempBan => "alert-secondary", + EFPenalty.PenaltyType.TempMute => "alert-secondary", + EFPenalty.PenaltyType.Mute => "alert-secondary", _ => "alert" }; } @@ -367,7 +369,7 @@ }); } - if ((Model.LevelInt < (int)ViewBag.User.Level && !Model.HasActivePenalty || isTempBanned) && ViewBag.Authorized) + if ((Model.LevelInt < (int)ViewBag.User.Level && !isPermBanned || isTempBanned) && ViewBag.Authorized) { menuItems.Items.Add(new SideContextMenuItem { @@ -379,7 +381,7 @@ }); } - if ((Model.LevelInt < (int)ViewBag.User.Level && Model.HasActivePenalty || isTempBanned) && ViewBag.Authorized) + if ((Model.LevelInt < (int)ViewBag.User.Level && isPermBanned || isTempBanned) && ViewBag.Authorized) { menuItems.Items.Add(new SideContextMenuItem {