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)
|
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))
|
if (await Plugin.MuteManager.Mute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, null, gameEvent.Data))
|
||||||
{
|
{
|
||||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_MUTED"]
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_MUTED"]
|
||||||
|
@ -42,6 +42,12 @@ public class TempMuteCommand : Command
|
|||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
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);
|
var match = Regex.Match(gameEvent.Data, TempBanRegex);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
|
@ -34,6 +34,12 @@ public class UnmuteCommand : Command
|
|||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
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))
|
if (await Plugin.MuteManager.Unmute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, gameEvent.Data))
|
||||||
{
|
{
|
||||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_UNMUTED"]
|
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 Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
@ -13,13 +16,15 @@ public class MuteManager
|
|||||||
private readonly IMetaServiceV2 _metaService;
|
private readonly IMetaServiceV2 _metaService;
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IDatabaseContextFactory _databaseContextFactory;
|
||||||
private readonly SemaphoreSlim _onMuteAction = new(1, 1);
|
private readonly SemaphoreSlim _onMuteAction = new(1, 1);
|
||||||
|
|
||||||
public MuteManager(IMetaServiceV2 metaService, ITranslationLookup translationLookup, ILogger logger)
|
public MuteManager(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_metaService = metaService;
|
_metaService = serviceProvider.GetRequiredService<IMetaServiceV2>();
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
|
||||||
_logger = logger;
|
_logger = serviceProvider.GetRequiredService<ILogger<MuteManager>>();
|
||||||
|
_databaseContextFactory = serviceProvider.GetRequiredService<IDatabaseContextFactory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsExpiredMute(MuteStateMeta muteStateMeta) =>
|
public static bool IsExpiredMute(MuteStateMeta muteStateMeta) =>
|
||||||
@ -98,11 +103,13 @@ public class MuteManager
|
|||||||
if (clientMuteMeta.MuteState is MuteState.Unmuted && clientMuteMeta.CommandExecuted) return false;
|
if (clientMuteMeta.MuteState is MuteState.Unmuted && clientMuteMeta.CommandExecuted) return false;
|
||||||
if (!target.IsIngame && clientMuteMeta.MuteState is MuteState.Unmuting) 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 CreatePenalty(MuteState.Unmuted, origin, target, DateTime.UtcNow, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ExpireMutePenalties(target);
|
||||||
|
|
||||||
clientMuteMeta = new MuteStateMeta
|
clientMuteMeta = new MuteStateMeta
|
||||||
{
|
{
|
||||||
Expiration = DateTime.UtcNow,
|
Expiration = DateTime.UtcNow,
|
||||||
@ -130,7 +137,6 @@ public class MuteManager
|
|||||||
Offender = target,
|
Offender = target,
|
||||||
Offense = reason,
|
Offense = reason,
|
||||||
Punisher = origin,
|
Punisher = origin,
|
||||||
Active = true,
|
|
||||||
Link = target.AliasLink
|
Link = target.AliasLink
|
||||||
};
|
};
|
||||||
_logger.LogDebug("Creating new {MuteState} Penalty for {Target} with reason {Reason}",
|
_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);
|
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)
|
public static async Task PerformGameCommand(Server server, EFClient? client, MuteStateMeta muteStateMeta)
|
||||||
{
|
{
|
||||||
if (client is null || !client.IsIngame) return;
|
if (client is null || !client.IsIngame) return;
|
||||||
|
@ -4,7 +4,7 @@ namespace Mute;
|
|||||||
|
|
||||||
public class MuteStateMeta
|
public class MuteStateMeta
|
||||||
{
|
{
|
||||||
public string Reason { get; set; } = string.Empty;
|
public string? Reason { get; set; }
|
||||||
public DateTime? Expiration { get; set; }
|
public DateTime? Expiration { get; set; }
|
||||||
public MuteState MuteState { get; set; }
|
public MuteState MuteState { get; set; }
|
||||||
[JsonIgnore] public bool CommandExecuted { 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 Mute.Commands;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Commands;
|
using SharedLibraryCore.Commands;
|
||||||
@ -12,7 +13,7 @@ namespace Mute;
|
|||||||
public class Plugin : IPlugin
|
public class Plugin : IPlugin
|
||||||
{
|
{
|
||||||
public string Name => "Mute";
|
public string Name => "Mute";
|
||||||
public float Version => (float) Utilities.GetVersionAsDouble();
|
public float Version => (float)Utilities.GetVersionAsDouble();
|
||||||
public string Author => "Amos";
|
public string Author => "Amos";
|
||||||
|
|
||||||
public const string MuteKey = "IW4MMute";
|
public const string MuteKey = "IW4MMute";
|
||||||
@ -24,12 +25,12 @@ public class Plugin : IPlugin
|
|||||||
private readonly IRemoteCommandService _remoteCommandService;
|
private readonly IRemoteCommandService _remoteCommandService;
|
||||||
private static readonly string MuteInteraction = "Webfront::Profile::Mute";
|
private static readonly string MuteInteraction = "Webfront::Profile::Mute";
|
||||||
|
|
||||||
public Plugin(ILogger<Plugin> logger, IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration,
|
public Plugin(ILogger<Plugin> logger, IInteractionRegistration interactionRegistration,
|
||||||
ITranslationLookup translationLookup, IRemoteCommandService remoteCommandService)
|
IRemoteCommandService remoteCommandService, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_interactionRegistration = interactionRegistration;
|
_interactionRegistration = interactionRegistration;
|
||||||
_remoteCommandService = remoteCommandService;
|
_remoteCommandService = remoteCommandService;
|
||||||
MuteManager = new MuteManager(metaService, translationLookup, logger);
|
MuteManager = new MuteManager(serviceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnEventAsync(GameEvent gameEvent, Server server)
|
public async Task OnEventAsync(GameEvent gameEvent, Server server)
|
||||||
@ -56,7 +57,7 @@ public class Plugin : IPlugin
|
|||||||
case MuteState.Unmuting:
|
case MuteState.Unmuting:
|
||||||
// Handle unmute of unmuted players.
|
// Handle unmute of unmuted players.
|
||||||
await MuteManager.Unmute(server, Utilities.IW4MAdminClient(), gameEvent.Origin,
|
await MuteManager.Unmute(server, Utilities.IW4MAdminClient(), gameEvent.Origin,
|
||||||
muteMetaJoin.Reason);
|
muteMetaJoin.Reason ?? string.Empty);
|
||||||
gameEvent.Origin.Tell(Utilities.CurrentLocalization
|
gameEvent.Origin.Tell(Utilities.CurrentLocalization
|
||||||
.LocalizationIndex["PLUGINS_MUTE_COMMANDS_UNMUTE_TARGET_UNMUTED"]
|
.LocalizationIndex["PLUGINS_MUTE_COMMANDS_UNMUTE_TARGET_UNMUTED"]
|
||||||
.FormatExt(muteMetaJoin.Reason));
|
.FormatExt(muteMetaJoin.Reason));
|
||||||
@ -134,7 +135,7 @@ public class Plugin : IPlugin
|
|||||||
|
|
||||||
_interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, token) =>
|
_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;
|
return null;
|
||||||
}
|
}
|
||||||
@ -162,7 +163,7 @@ public class Plugin : IPlugin
|
|||||||
Name = "Reason",
|
Name = "Reason",
|
||||||
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_REASON"],
|
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_REASON"],
|
||||||
Type = "text",
|
Type = "text",
|
||||||
Values = (Dictionary<string, string>?) null
|
Values = (Dictionary<string, string>?)null
|
||||||
};
|
};
|
||||||
|
|
||||||
var durationInput = new
|
var durationInput = new
|
||||||
@ -170,7 +171,7 @@ public class Plugin : IPlugin
|
|||||||
Name = "Duration",
|
Name = "Duration",
|
||||||
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_DURATION"],
|
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_DURATION"],
|
||||||
Type = "select",
|
Type = "select",
|
||||||
Values = (Dictionary<string, string>?) new Dictionary<string, string>
|
Values = (Dictionary<string, string>?)new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{"5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture()},
|
{"5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture()},
|
||||||
{"30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture()},
|
{"30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture()},
|
||||||
|
@ -131,7 +131,7 @@ namespace SharedLibraryCore.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static readonly EFPenalty.PenaltyType[] LinkedPenalties =
|
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 =>
|
private static readonly Expression<Func<EFPenalty, bool>> Filter = p =>
|
||||||
LinkedPenalties.Contains(p.Type) && p.Active && (p.Expires == null || p.Expires > DateTime.UtcNow);
|
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));
|
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive));
|
||||||
|
|
||||||
var strippedName = clientDto.Name.StripColors();
|
var strippedName = clientDto.Name.StripColors();
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
EFPenalty.PenaltyType.Ban => "alert-danger",
|
EFPenalty.PenaltyType.Ban => "alert-danger",
|
||||||
EFPenalty.PenaltyType.Flag => "alert-secondary",
|
EFPenalty.PenaltyType.Flag => "alert-secondary",
|
||||||
EFPenalty.PenaltyType.TempBan => "alert-secondary",
|
EFPenalty.PenaltyType.TempBan => "alert-secondary",
|
||||||
|
EFPenalty.PenaltyType.TempMute => "alert-secondary",
|
||||||
|
EFPenalty.PenaltyType.Mute => "alert-secondary",
|
||||||
_ => "alert"
|
_ => "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
|
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
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user