Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mute Banner for Profile & Prevent Self-Target & Correctly Expire Early Unmutes #272

Merged
merged 8 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Plugins/Mute/Commands/MuteCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
6 changes: 6 additions & 0 deletions Plugins/Mute/Commands/TempMuteCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
6 changes: 6 additions & 0 deletions Plugins/Mute/Commands/UnmuteCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
38 changes: 31 additions & 7 deletions Plugins/Mute/MuteManager.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) =>
Expand Down Expand Up @@ -98,11 +103,13 @@ public async Task<bool> 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,
Expand Down Expand Up @@ -130,14 +137,31 @@ 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}",
nameof(muteState), target.Name, reason);
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;
Expand Down
2 changes: 1 addition & 1 deletion Plugins/Mute/MuteStateMeta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
19 changes: 10 additions & 9 deletions Plugins/Mute/Plugin.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using Data.Abstractions;
using Microsoft.Extensions.Logging;
using Mute.Commands;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
Expand All @@ -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";
Expand All @@ -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)
Expand All @@ -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));
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -162,15 +163,15 @@ private InteractionData CreateMuteInteraction(int targetClientId, Server server,
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
{
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()},
Expand Down
2 changes: 1 addition & 1 deletion SharedLibraryCore/Services/PenaltyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public async Task<IList<PenaltyInfo>> 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<Func<EFPenalty, bool>> Filter = p =>
LinkedPenalties.Contains(p.Type) && p.Active && (p.Expires == null || p.Expires > DateTime.UtcNow);
Expand Down
8 changes: 7 additions & 1 deletion WebfrontCore/Controllers/Client/ClientController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,13 @@ public async Task<IActionResult> 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
Ayymoss marked this conversation as resolved.
Show resolved Hide resolved
{
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();
Expand Down
6 changes: 4 additions & 2 deletions WebfrontCore/Views/Client/Profile/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
};
}
Expand Down Expand Up @@ -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
{
Expand All @@ -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
{
Expand Down