Mute Banner for Profile & Prevent Self-Target & Correctly Expire Early Unmutes (#272)
* Fix self-targeting Remove creation of penalty on mute expiration * Display mute penalties on profile Expire mute penalties on unmute * Resolves issues in code review Added comment in ClientController.cs Fixed order of operations in MuteManager.cs Fixed condition in MuteManager.cs * Fix self-targeting Remove creation of penalty on mute expiration * Display mute penalties on profile Expire mute penalties on unmute * Resolves issues in code review Added comment in ClientController.cs Fixed order of operations in MuteManager.cs Fixed condition in MuteManager.cs * Changed localisation value to be more generic Fix null reference warning (it should never be null) (34da216)
This commit is contained in:
parent
dbca3675ba
commit
a16986f7a3
@ -34,6 +34,12 @@ public class MuteCommand : Command
|
||||
|
||||
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"]
|
||||
|
@ -42,6 +42,12 @@ public class TempMuteCommand : Command
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -34,6 +34,12 @@ public class UnmuteCommand : Command
|
||||
|
||||
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"]
|
||||
|
@ -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<IMetaServiceV2>();
|
||||
_translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
|
||||
_logger = serviceProvider.GetRequiredService<ILogger<MuteManager>>();
|
||||
_databaseContextFactory = serviceProvider.GetRequiredService<IDatabaseContextFactory>();
|
||||
}
|
||||
|
||||
public static bool IsExpiredMute(MuteStateMeta muteStateMeta) =>
|
||||
@ -98,11 +103,13 @@ public class MuteManager
|
||||
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 @@ public class MuteManager
|
||||
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 @@ public class MuteManager
|
||||
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;
|
||||
|
@ -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; }
|
||||
|
@ -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<Plugin> logger, IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration,
|
||||
ITranslationLookup translationLookup, IRemoteCommandService remoteCommandService)
|
||||
public Plugin(ILogger<Plugin> 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 @@ public class Plugin : IPlugin
|
||||
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 class Plugin : IPlugin
|
||||
|
||||
_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 @@ public class Plugin : IPlugin
|
||||
Name = "Reason",
|
||||
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_REASON"],
|
||||
Type = "text",
|
||||
Values = (Dictionary<string, string>?) null
|
||||
Values = (Dictionary<string, string>?)null
|
||||
};
|
||||
|
||||
var durationInput = new
|
||||
@ -170,7 +171,7 @@ public class Plugin : IPlugin
|
||||
Name = "Duration",
|
||||
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_DURATION"],
|
||||
Type = "select",
|
||||
Values = (Dictionary<string, string>?) new Dictionary<string, string>
|
||||
Values = (Dictionary<string, string>?)new Dictionary<string, string>
|
||||
{
|
||||
{"5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture()},
|
||||
{"30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture()},
|
||||
|
@ -131,7 +131,7 @@ namespace SharedLibraryCore.Services
|
||||
}
|
||||
|
||||
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<Func<EFPenalty, bool>> Filter = p =>
|
||||
LinkedPenalties.Contains(p.Type) && p.Active && (p.Expires == null || p.Expires > DateTime.UtcNow);
|
||||
|
@ -162,7 +162,13 @@ namespace WebfrontCore.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -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
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user