diff --git a/Plugins/Mute/Commands/MuteCommand.cs b/Plugins/Mute/Commands/MuteCommand.cs index 82c4cd7e2..0e894f542 100644 --- a/Plugins/Mute/Commands/MuteCommand.cs +++ b/Plugins/Mute/Commands/MuteCommand.cs @@ -20,8 +20,8 @@ public MuteCommand(CommandConfiguration config, ITranslationLookup translationLo Permission = EFClient.Permission.Moderator; RequiresTarget = true; SupportedGames = Plugin.SupportedGames; - Arguments = new[] - { + Arguments = + [ new CommandArgument { Name = translationLookup["COMMANDS_ARGS_PLAYER"], @@ -32,7 +32,7 @@ public MuteCommand(CommandConfiguration config, ITranslationLookup translationLo Name = translationLookup["COMMANDS_ARGS_REASON"], Required = true } - }; + ]; } public override async Task ExecuteAsync(GameEvent gameEvent) diff --git a/Plugins/Mute/Commands/MuteInfoCommand.cs b/Plugins/Mute/Commands/MuteInfoCommand.cs index 4622a0ce9..bba01a9e7 100644 --- a/Plugins/Mute/Commands/MuteInfoCommand.cs +++ b/Plugins/Mute/Commands/MuteInfoCommand.cs @@ -21,14 +21,14 @@ public MuteInfoCommand(CommandConfiguration config, ITranslationLookup translati Permission = EFClient.Permission.Moderator; RequiresTarget = true; SupportedGames = Plugin.SupportedGames; - Arguments = new[] - { + Arguments = + [ new CommandArgument { Name = translationLookup["COMMANDS_ARGS_PLAYER"], Required = true } - }; + ]; } public override async Task ExecuteAsync(GameEvent gameEvent) diff --git a/Plugins/Mute/Commands/TempMuteCommand.cs b/Plugins/Mute/Commands/TempMuteCommand.cs index 69a2999c9..9275b1075 100644 --- a/Plugins/Mute/Commands/TempMuteCommand.cs +++ b/Plugins/Mute/Commands/TempMuteCommand.cs @@ -7,10 +7,9 @@ namespace IW4MAdmin.Plugins.Mute.Commands; -public class TempMuteCommand : Command +public partial class TempMuteCommand : Command { private readonly MuteManager _muteManager; - private const string TempBanRegex = @"([0-9]+\w+)\ (.+)"; public TempMuteCommand(CommandConfiguration config, ITranslationLookup translationLookup, MuteManager muteManager) : base(config, translationLookup) @@ -22,8 +21,8 @@ public TempMuteCommand(CommandConfiguration config, ITranslationLookup translati Permission = EFClient.Permission.Moderator; RequiresTarget = true; SupportedGames = Plugin.SupportedGames; - Arguments = new[] - { + Arguments = + [ new CommandArgument { Name = translationLookup["COMMANDS_ARGS_PLAYER"], @@ -39,7 +38,7 @@ public TempMuteCommand(CommandConfiguration config, ITranslationLookup translati Name = translationLookup["COMMANDS_ARGS_REASON"], Required = true } - }; + ]; } public override async Task ExecuteAsync(GameEvent gameEvent) @@ -49,8 +48,8 @@ public override async Task ExecuteAsync(GameEvent gameEvent) gameEvent.Origin.Tell(_translationLookup["COMMANDS_DENY_SELF_TARGET"]); return; } - - var match = Regex.Match(gameEvent.Data, TempBanRegex); + + var match = TempBanRegex().Match(gameEvent.Data); if (match.Success) { var expiration = DateTime.UtcNow + match.Groups[1].ToString().ParseTimespan(); @@ -72,4 +71,7 @@ public override async Task ExecuteAsync(GameEvent gameEvent) gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_BAD_FORMAT"]); } + + [GeneratedRegex(@"([0-9]+\w+)\ (.+)")] + private static partial Regex TempBanRegex(); } diff --git a/Plugins/Mute/Commands/UnmuteCommand.cs b/Plugins/Mute/Commands/UnmuteCommand.cs index e59c4334a..043611442 100644 --- a/Plugins/Mute/Commands/UnmuteCommand.cs +++ b/Plugins/Mute/Commands/UnmuteCommand.cs @@ -20,8 +20,8 @@ public UnmuteCommand(CommandConfiguration config, ITranslationLookup translation Permission = EFClient.Permission.Moderator; RequiresTarget = true; SupportedGames = Plugin.SupportedGames; - Arguments = new[] - { + Arguments = + [ new CommandArgument { Name = translationLookup["COMMANDS_ARGS_PLAYER"], @@ -32,7 +32,7 @@ public UnmuteCommand(CommandConfiguration config, ITranslationLookup translation Name = translationLookup["COMMANDS_ARGS_REASON"], Required = true } - }; + ]; } public override async Task ExecuteAsync(GameEvent gameEvent) diff --git a/Plugins/Mute/Mute.csproj b/Plugins/Mute/Mute.csproj index a227b7fed..3341c5258 100644 --- a/Plugins/Mute/Mute.csproj +++ b/Plugins/Mute/Mute.csproj @@ -8,6 +8,7 @@ Library Debug;Release;Prerelease AnyCPU + IW4MAdmin.Plugins.Mute diff --git a/Plugins/Mute/MuteManager.cs b/Plugins/Mute/MuteManager.cs index 537465b12..e758d5eb9 100644 --- a/Plugins/Mute/MuteManager.cs +++ b/Plugins/Mute/MuteManager.cs @@ -10,23 +10,15 @@ namespace IW4MAdmin.Plugins.Mute; -public class MuteManager +public class MuteManager( + ILogger logger, + IDatabaseContextFactory databaseContextFactory, + IMetaServiceV2 metaService, + ITranslationLookup translationLookup) { - private readonly IMetaServiceV2 _metaService; - private readonly ITranslationLookup _translationLookup; - private readonly ILogger _logger; - private readonly IDatabaseContextFactory _databaseContextFactory; + private readonly ILogger _logger = logger; private readonly SemaphoreSlim _onMuteAction = new(1, 1); - public MuteManager(ILogger logger, IDatabaseContextFactory databaseContextFactory, - IMetaServiceV2 metaService, ITranslationLookup translationLookup) - { - _logger = logger; - _databaseContextFactory = databaseContextFactory; - _metaService = metaService; - _translationLookup = translationLookup; - } - public static bool IsExpiredMute(MuteStateMeta muteStateMeta) => muteStateMeta.Expiration is not null && muteStateMeta.Expiration < DateTime.UtcNow; @@ -42,7 +34,7 @@ public async Task GetCurrentMuteState(EFClient client) var muteState = await ReadPersistentDataV1(client); clientMuteMeta = new MuteStateMeta { - Reason = muteState is null ? string.Empty : _translationLookup["PLUGINS_MUTE_MIGRATED"], + Reason = muteState is null ? string.Empty : translationLookup["PLUGINS_MUTE_MIGRATED"], Expiration = muteState switch { null => DateTime.UtcNow, @@ -149,7 +141,7 @@ private async Task CreatePenalty(MuteState muteState, EFClient origin, EFClient private async Task ExpireMutePenalties(EFClient client) { - await using var context = _databaseContextFactory.CreateContext(); + await using var context = databaseContextFactory.CreateContext(); var mutePenalties = await context.Penalties .Where(penalty => penalty.OffenderId == client.ClientId) .Where(penalty => penalty.Type == EFPenalty.PenaltyType.Mute || penalty.Type == EFPenalty.PenaltyType.TempMute) @@ -184,7 +176,7 @@ public static async Task PerformGameCommand(Server server, EFClient? client, Mut } private async Task ReadPersistentDataV1(EFClient client) => TryParse( - (await _metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?.Value, out var muteState) + (await metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?.Value, out var muteState) ? muteState : null; @@ -195,7 +187,7 @@ public static async Task PerformGameCommand(Server server, EFClient? client, Mut if (clientMuteMeta is not null) return clientMuteMeta; // Get meta from database and store in client if exists - clientMuteMeta = await _metaService.GetPersistentMetaValue(Plugin.MuteKey, client.ClientId); + clientMuteMeta = await metaService.GetPersistentMetaValue(Plugin.MuteKey, client.ClientId); if (clientMuteMeta is not null) client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta); return clientMuteMeta; @@ -204,6 +196,6 @@ public static async Task PerformGameCommand(Server server, EFClient? client, Mut private async Task WritePersistentData(EFClient client, MuteStateMeta clientMuteMeta) { client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta); - await _metaService.SetPersistentMetaValue(Plugin.MuteKey, clientMuteMeta, client.ClientId); + await metaService.SetPersistentMetaValue(Plugin.MuteKey, clientMuteMeta, client.ClientId); } } diff --git a/Plugins/Mute/Plugin.cs b/Plugins/Mute/Plugin.cs index d1c36815a..44ca8b2cd 100644 --- a/Plugins/Mute/Plugin.cs +++ b/Plugins/Mute/Plugin.cs @@ -21,15 +21,14 @@ public class Plugin : IPluginV2 public const string MuteKey = "IW4MMute"; public static IManager Manager { get; private set; } = null!; - public static Server.Game[] SupportedGames { get; private set; } = Array.Empty(); + public static Server.Game[] SupportedGames { get; private set; } = []; private static readonly string[] DisabledCommands = [nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"]; private readonly IInteractionRegistration _interactionRegistration; private readonly IRemoteCommandService _remoteCommandService; private readonly MuteManager _muteManager; private const string MuteInteraction = "Webfront::Profile::Mute"; - public Plugin(IInteractionRegistration interactionRegistration, - IRemoteCommandService remoteCommandService, MuteManager muteManager) + public Plugin(IInteractionRegistration interactionRegistration, IRemoteCommandService remoteCommandService, MuteManager muteManager) { _interactionRegistration = interactionRegistration; _remoteCommandService = remoteCommandService; @@ -37,7 +36,6 @@ public Plugin(IInteractionRegistration interactionRegistration, IManagementEventSubscriptions.Load += OnLoad; IManagementEventSubscriptions.Unload += OnUnload; - IManagementEventSubscriptions.ClientStateInitialized += OnClientStateInitialized; IGameServerEventSubscriptions.ClientDataUpdated += OnClientDataUpdated; @@ -73,7 +71,7 @@ private Task OnLoad(IManager manager, CancellationToken cancellationToken) return !DisabledCommands.Contains(command.GetType().Name) && !command.IsBroadcast; }); - _interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, token) => + _interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, _) => { if (!targetClientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game)game.Value)) { @@ -81,16 +79,16 @@ private Task OnLoad(IManager manager, CancellationToken cancellationToken) } var clientMuteMetaState = - (await _muteManager.GetCurrentMuteState(new EFClient {ClientId = targetClientId.Value})) + (await _muteManager.GetCurrentMuteState(new EFClient { ClientId = targetClientId.Value })) .MuteState; var server = manager.GetServers().First(); - string GetCommandName(Type commandType) => - manager.Commands.FirstOrDefault(command => command.GetType() == commandType)?.Name ?? ""; - return clientMuteMetaState is MuteState.Unmuted or MuteState.Unmuting ? CreateMuteInteraction(targetClientId.Value, server, GetCommandName) : CreateUnmuteInteraction(targetClientId.Value, server, GetCommandName); + + string GetCommandName(Type commandType) => + manager.Commands.FirstOrDefault(command => command.GetType() == commandType)?.Name ?? string.Empty; }); return Task.CompletedTask; } @@ -109,9 +107,9 @@ private async Task OnClientDataUpdated(ClientDataUpdateEvent updateEvent, Cancel } var networkIds = updateEvent.Clients.Select(client => client.NetworkId).ToList(); - var ingameClients = updateEvent.Server.ConnectedClients.Where(client => networkIds.Contains(client.NetworkId)); + var inGameClients = updateEvent.Server.ConnectedClients.Where(client => networkIds.Contains(client.NetworkId)); - await Task.WhenAll(ingameClients.Select(async client => + await Task.WhenAll(inGameClients.Select(async client => { var muteMetaUpdate = await _muteManager.GetCurrentMuteState(client); if (!muteMetaUpdate.CommandExecuted) @@ -137,7 +135,7 @@ private async Task OnClientMessaged(ClientMessageEvent messageEvent, Cancellatio { var muteMetaSay = await _muteManager.GetCurrentMuteState(messageEvent.Origin); - if (muteMetaSay.MuteState == MuteState.Muted) + if (muteMetaSay.MuteState is MuteState.Muted) { // Let the client know when their mute expires. messageEvent.Origin.Tell(Utilities.CurrentLocalization @@ -160,16 +158,16 @@ private async Task OnClientStateInitialized(ClientStateInitializeEvent state, Ca switch (muteMetaJoin) { - case {MuteState: MuteState.Muted}: + case { MuteState: MuteState.Muted }: // Let the client know when their mute expires. state.Client.Tell(Utilities.CurrentLocalization .LocalizationIndex["PLUGINS_MUTE_REMAINING_TIME"].FormatExt( - muteMetaJoin is {Expiration: not null} + muteMetaJoin is { Expiration: not null } ? muteMetaJoin.Expiration.Value.HumanizeForCurrentCulture() : Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_NEVER"], muteMetaJoin.Reason)); break; - case {MuteState: MuteState.Unmuting}: + case { MuteState: MuteState.Unmuting }: // Handle unmute of unmuted players. await _muteManager.Unmute(state.Client.CurrentServer, Utilities.IW4MAdminClient(), state.Client, muteMetaJoin.Reason ?? string.Empty); @@ -191,6 +189,29 @@ private InteractionData CreateMuteInteraction(int targetClientId, Server server, Values = (Dictionary?)null }; + var presetReasonInput = new + { + Name = "PresetReason", + Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_PRESET_REASON"], + Type = "select", + Values = (Dictionary?)new Dictionary + { + { string.Empty, string.Empty }, + { + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_ABUSIVE"], + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_ABUSIVE"] + }, + { + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_SPAMMING"], + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_SPAMMING"] + }, + { + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_OTHER"], + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_OTHER"] + } + } + }; + var durationInput = new { Name = "Duration", @@ -198,16 +219,16 @@ private InteractionData CreateMuteInteraction(int targetClientId, Server server, Type = "select", Values = (Dictionary?)new Dictionary { - {"5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture()}, - {"30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture()}, - {"1h", TimeSpan.FromHours(1).HumanizeForCurrentCulture()}, - {"6h", TimeSpan.FromHours(6).HumanizeForCurrentCulture()}, - {"1d", TimeSpan.FromDays(1).HumanizeForCurrentCulture()}, - {"p", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_SELECTION_PERMANENT"]} + { "5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture() }, + { "30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture() }, + { "1h", TimeSpan.FromHours(1).HumanizeForCurrentCulture() }, + { "6h", TimeSpan.FromHours(6).HumanizeForCurrentCulture() }, + { "1d", TimeSpan.FromDays(1).HumanizeForCurrentCulture() }, + { "p", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_SELECTION_PERMANENT"] } } }; - var inputs = new[] {reasonInput, durationInput}; + var inputs = new[] { reasonInput, presetReasonInput, durationInput }; var inputsJson = JsonSerializer.Serialize(inputs); return new InteractionData @@ -216,10 +237,10 @@ private InteractionData CreateMuteInteraction(int targetClientId, Server server, Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"], DisplayMeta = "oi-volume-off", ActionPath = "DynamicAction", - ActionMeta = new() + ActionMeta = new Dictionary { - {"InteractionId", MuteInteraction}, - {"Inputs", inputsJson}, + { "InteractionId", MuteInteraction }, + { "Inputs", inputsJson }, { "ActionButtonLabel", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"] @@ -228,7 +249,7 @@ private InteractionData CreateMuteInteraction(int targetClientId, Server server, "Name", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"] }, - {"ShouldRefresh", true.ToString()} + { "ShouldRefresh", true.ToString() } }, MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator, Source = Name, @@ -250,11 +271,14 @@ private InteractionData CreateMuteInteraction(int targetClientId, Server server, args.Add(duration); } - if (meta.TryGetValue(reasonInput.Name, out var reason)) + var definedReason = meta.TryGetValue(reasonInput.Name, out var reason) ? reason : string.Empty; + if (meta.TryGetValue(presetReasonInput.Name, out var presetReason) && string.IsNullOrWhiteSpace(definedReason)) { - args.Add(reason); + definedReason = presetReason; } + args.Add(definedReason); + var commandResponse = await _remoteCommandService.Execute(originId, targetId, muteCommand, args, server); return string.Join(".", commandResponse.Select(result => result.Response)); @@ -272,21 +296,20 @@ private InteractionData CreateUnmuteInteraction(int targetClientId, Server serve Type = "text", }; - var inputs = new[] {reasonInput}; + var inputs = new[] { reasonInput }; var inputsJson = JsonSerializer.Serialize(inputs); return new InteractionData { EntityId = targetClientId, - Name = Utilities.CurrentLocalization.LocalizationIndex[ - "WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"], + Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"], DisplayMeta = "oi-volume-high", ActionPath = "DynamicAction", - ActionMeta = new() + ActionMeta = new Dictionary { - {"InteractionId", MuteInteraction}, - {"Outputs", reasonInput.Name}, - {"Inputs", inputsJson}, + { "InteractionId", MuteInteraction }, + { "Outputs", reasonInput.Name }, + { "Inputs", inputsJson }, { "ActionButtonLabel", Utilities.CurrentLocalization.LocalizationIndex[ @@ -297,7 +320,7 @@ private InteractionData CreateUnmuteInteraction(int targetClientId, Server serve Utilities.CurrentLocalization.LocalizationIndex[ "WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"] }, - {"ShouldRefresh", true.ToString()} + { "ShouldRefresh", true.ToString() } }, MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator, Source = Name,