Added !unmute, !tempmute, !listmutes
Quick fix for PowerShell IE use Makes date readable for target player Resolved translation string inconsistencies Minor code cleanups Initial commit from review Cleaned up code & amended a few checks Comment typo Fix infinite unmuting Removed unnecessary checks (Unmuting an already unmuted player will not trigger MuteStateMeta creation (if already doesn't exist)) Resolved !listmutes showing expired mutes Committing before refactor Refactor from review Removed reference to AdditionalProperty Fix check for meta state when unmuting Continued request solves main problem Handle potential failed command execution Missed CommandExecuted onJoin Fix another PS Reference to Invoke-WebRequest Fixes review issues & Cleaned up code Adds support for Intercepting Commands via Plugin (Credit: @RaidMax) Comparing Revert formatting changes Removing MuteList for Penalty Added Mute, TempMute & Unmute Penalty Fixed reference in Mute.csproj & Removed ListMutesCommand.cs
This commit is contained in:
parent
a15da15d3e
commit
cf3209e1d0
@ -7,6 +7,6 @@ foreach($localization in $localizations)
|
|||||||
{
|
{
|
||||||
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
||||||
$filePath = "{0}Localization\IW4MAdmin.{1}.json" -f $OutputDir, $localization
|
$filePath = "{0}Localization\IW4MAdmin.{1}.json" -f $OutputDir, $localization
|
||||||
$response = Invoke-WebRequest $url
|
$response = Invoke-WebRequest $url -UseBasicParsing
|
||||||
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,9 @@ namespace Data.Models
|
|||||||
Unban,
|
Unban,
|
||||||
Any,
|
Any,
|
||||||
Unflag,
|
Unflag,
|
||||||
|
Mute,
|
||||||
|
TempMute,
|
||||||
|
Unmute,
|
||||||
Other = 100
|
Other = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ foreach($localization in $localizations)
|
|||||||
{
|
{
|
||||||
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
||||||
$filePath = "{0}\Localization\IW4MAdmin.{1}.json" -f $PublishDir, $localization
|
$filePath = "{0}\Localization\IW4MAdmin.{1}.json" -f $PublishDir, $localization
|
||||||
$response = Invoke-WebRequest $url
|
$response = Invoke-WebRequest $url -UseBasicParsing
|
||||||
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,4 +20,4 @@ Minor = $versionInfo.ProductMinorPart
|
|||||||
Build = $versionInfo.ProductBuildPart
|
Build = $versionInfo.ProductBuildPart
|
||||||
Revision = $versionInfo.ProductPrivatePart
|
Revision = $versionInfo.ProductPrivatePart
|
||||||
}
|
}
|
||||||
$json | ConvertTo-Json | Out-File -FilePath ("{0}\VersionInformation.json" -f $PublishDir) -Encoding ASCII
|
$json | ConvertTo-Json | Out-File -FilePath ("{0}\VersionInformation.json" -f $PublishDir) -Encoding ASCII
|
||||||
|
@ -39,7 +39,7 @@ else
|
|||||||
|
|
||||||
Write-Output "Retrieving latest version info..."
|
Write-Output "Retrieving latest version info..."
|
||||||
|
|
||||||
$releaseInfo = (Invoke-WebRequest $releasesUri | ConvertFrom-Json) | Select -First 1
|
$releaseInfo = (Invoke-WebRequest $releasesUri -UseBasicParsing | ConvertFrom-Json) | Select -First 1
|
||||||
$asset = $releaseInfo.assets | Where-Object name -like $assetPattern | Select -First 1
|
$asset = $releaseInfo.assets | Where-Object name -like $assetPattern | Select -First 1
|
||||||
$downloadUri = $asset.browser_download_url
|
$downloadUri = $asset.browser_download_url
|
||||||
$filename = Split-Path $downloadUri -leaf
|
$filename = Split-Path $downloadUri -leaf
|
||||||
@ -55,7 +55,7 @@ if (!$Silent)
|
|||||||
|
|
||||||
Write-Output "Downloading update. This might take a moment..."
|
Write-Output "Downloading update. This might take a moment..."
|
||||||
|
|
||||||
$fileDownload = Invoke-WebRequest -Uri $downloadUri
|
$fileDownload = Invoke-WebRequest -Uri $downloadUri -UseBasicParsing
|
||||||
if ($fileDownload.StatusDescription -ne "OK")
|
if ($fileDownload.StatusDescription -ne "OK")
|
||||||
{
|
{
|
||||||
throw "Could not update IW4MAdmin. ($fileDownload.StatusDescription)"
|
throw "Could not update IW4MAdmin. ($fileDownload.StatusDescription)"
|
||||||
|
@ -23,20 +23,27 @@ public class MuteCommand : Command
|
|||||||
{
|
{
|
||||||
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||||
Required = true
|
Required = true
|
||||||
|
},
|
||||||
|
new CommandArgument
|
||||||
|
{
|
||||||
|
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||||
|
Required = true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly MuteManager _muteManager = new();
|
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
if (await _muteManager.Mute(gameEvent))
|
if (await Plugin.MuteManager.Mute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, null, gameEvent.Data))
|
||||||
{
|
{
|
||||||
gameEvent.Origin.Tell($"{_translationLookup["PLUGINS_MUTE_MUTED"]} {gameEvent.Target.Name}");
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_MUTED"]
|
||||||
|
.FormatExt(gameEvent.Target.CleanedName));
|
||||||
|
gameEvent.Target.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_TARGET_MUTED"]
|
||||||
|
.FormatExt(gameEvent.Data));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gameEvent.Origin.Tell($"{_translationLookup["PLUGINS_MUTE_UNMUTED"]} {gameEvent.Target.Name}");
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_NOT_UNMUTED"]
|
||||||
|
.FormatExt(gameEvent.Target.CleanedName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
67
Plugins/Mute/Commands/TempMuteCommand.cs
Normal file
67
Plugins/Mute/Commands/TempMuteCommand.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Data.Models.Client;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Commands;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
namespace Mute.Commands;
|
||||||
|
|
||||||
|
public class TempMuteCommand : Command
|
||||||
|
{
|
||||||
|
private const string TempBanRegex = @"([0-9]+\w+)\ (.+)";
|
||||||
|
|
||||||
|
public TempMuteCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||||
|
translationLookup)
|
||||||
|
{
|
||||||
|
Name = "tempmute";
|
||||||
|
Description = translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_DESC"];
|
||||||
|
Alias = "tm";
|
||||||
|
Permission = EFClient.Permission.Moderator;
|
||||||
|
RequiresTarget = true;
|
||||||
|
SupportedGames = Plugin.SupportedGames;
|
||||||
|
Arguments = new[]
|
||||||
|
{
|
||||||
|
new CommandArgument
|
||||||
|
{
|
||||||
|
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||||
|
Required = true
|
||||||
|
},
|
||||||
|
new CommandArgument
|
||||||
|
{
|
||||||
|
Name = translationLookup["COMMANDS_ARGS_DURATION"],
|
||||||
|
Required = true
|
||||||
|
},
|
||||||
|
new CommandArgument
|
||||||
|
{
|
||||||
|
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||||
|
Required = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
var match = Regex.Match(gameEvent.Data, TempBanRegex);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
var expiration = DateTime.UtcNow + match.Groups[1].ToString().ParseTimespan();
|
||||||
|
var reason = match.Groups[2].ToString();
|
||||||
|
|
||||||
|
if (await Plugin.MuteManager.Mute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, expiration, reason))
|
||||||
|
{
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_TEMPMUTED"]
|
||||||
|
.FormatExt(gameEvent.Target.CleanedName));
|
||||||
|
gameEvent.Target.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_TARGET_TEMPMUTED"]
|
||||||
|
.FormatExt(expiration.HumanizeForCurrentCulture(), reason));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_NOT_UNMUTED"]
|
||||||
|
.FormatExt(gameEvent.Target.CleanedName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_BAD_FORMAT"]);
|
||||||
|
}
|
||||||
|
}
|
49
Plugins/Mute/Commands/UnmuteCommand.cs
Normal file
49
Plugins/Mute/Commands/UnmuteCommand.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Data.Models.Client;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Commands;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
namespace Mute.Commands;
|
||||||
|
|
||||||
|
public class UnmuteCommand : Command
|
||||||
|
{
|
||||||
|
public UnmuteCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||||
|
translationLookup)
|
||||||
|
{
|
||||||
|
Name = "unmute";
|
||||||
|
Description = translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_DESC"];
|
||||||
|
Alias = "um";
|
||||||
|
Permission = EFClient.Permission.Moderator;
|
||||||
|
RequiresTarget = true;
|
||||||
|
SupportedGames = Plugin.SupportedGames;
|
||||||
|
Arguments = new[]
|
||||||
|
{
|
||||||
|
new CommandArgument
|
||||||
|
{
|
||||||
|
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||||
|
Required = true
|
||||||
|
},
|
||||||
|
new CommandArgument
|
||||||
|
{
|
||||||
|
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||||
|
Required = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
if (await Plugin.MuteManager.Unmute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, gameEvent.Data))
|
||||||
|
{
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_UNMUTED"]
|
||||||
|
.FormatExt(gameEvent.Target.CleanedName));
|
||||||
|
gameEvent.Target.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_TARGET_UNMUTED"]
|
||||||
|
.FormatExt(gameEvent.Data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_NOT_MUTED"]
|
||||||
|
.FormatExt(gameEvent.Target.CleanedName));
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
using Data.Models.Client;
|
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
using static System.Enum;
|
|
||||||
|
|
||||||
namespace Mute;
|
|
||||||
|
|
||||||
public class DataManager
|
|
||||||
{
|
|
||||||
public DataManager(IMetaServiceV2 metaService)
|
|
||||||
{
|
|
||||||
_metaService = metaService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly IMetaServiceV2 _metaService;
|
|
||||||
|
|
||||||
public async Task<MuteState> ReadPersistentData(EFClient client)
|
|
||||||
{
|
|
||||||
var clientMuteState = client.GetAdditionalProperty<MuteState?>(Plugin.MuteKey) ??
|
|
||||||
Parse<MuteState>((await _metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?
|
|
||||||
.Value ?? nameof(MuteState.Unmuted));
|
|
||||||
|
|
||||||
client.SetAdditionalProperty(Plugin.MuteKey, clientMuteState);
|
|
||||||
return clientMuteState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WritePersistentData(EFClient client, MuteState state)
|
|
||||||
{
|
|
||||||
await _metaService.SetPersistentMeta(Plugin.MuteKey, state.ToString(), client.ClientId);
|
|
||||||
client.SetAdditionalProperty(Plugin.MuteKey, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum MuteState
|
|
||||||
{
|
|
||||||
Muted,
|
|
||||||
Unmuting,
|
|
||||||
Unmuted
|
|
||||||
}
|
|
@ -11,9 +11,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.12.1" PrivateAssets="All"/>
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.12.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||||
</Target>
|
</Target>
|
||||||
|
@ -1,21 +1,175 @@
|
|||||||
using SharedLibraryCore;
|
using Data.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using static System.Enum;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace Mute;
|
namespace Mute;
|
||||||
|
|
||||||
public class MuteManager
|
public class MuteManager
|
||||||
{
|
{
|
||||||
public async Task<bool> Mute(GameEvent gameEvent)
|
private readonly IMetaServiceV2 _metaService;
|
||||||
|
private readonly ITranslationLookup _translationLookup;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public MuteManager(IMetaServiceV2 metaService, ITranslationLookup translationLookup, ILogger logger)
|
||||||
{
|
{
|
||||||
if (await Plugin.DataManager.ReadPersistentData(gameEvent.Target) == MuteState.Muted)
|
_metaService = metaService;
|
||||||
|
_translationLookup = translationLookup;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsExpiredMute(MuteStateMeta muteStateMeta) =>
|
||||||
|
muteStateMeta.Expiration is not null && muteStateMeta.Expiration < DateTime.UtcNow;
|
||||||
|
|
||||||
|
public async Task<MuteStateMeta> GetCurrentMuteState(EFClient client)
|
||||||
|
{
|
||||||
|
var clientMuteMeta = await ReadPersistentDataV2(client);
|
||||||
|
if (clientMuteMeta is not null) return clientMuteMeta;
|
||||||
|
|
||||||
|
// Return null if the client doesn't have old or new meta.
|
||||||
|
var muteState = await ReadPersistentDataV1(client);
|
||||||
|
clientMuteMeta = new MuteStateMeta
|
||||||
{
|
{
|
||||||
await gameEvent.Owner.ExecuteCommandAsync($"unmute {gameEvent.Target.ClientNumber}");
|
ClientId = client.ClientId,
|
||||||
await Plugin.DataManager.WritePersistentData(gameEvent.Target,
|
Reason = muteState is null ? string.Empty : _translationLookup["PLUGINS_MUTE_MIGRATED"],
|
||||||
gameEvent.Target.IsIngame ? MuteState.Unmuted : MuteState.Unmuting);
|
Expiration = muteState switch
|
||||||
return false;
|
{
|
||||||
|
null => DateTime.UtcNow,
|
||||||
|
MuteState.Muted => null,
|
||||||
|
_ => DateTime.UtcNow
|
||||||
|
},
|
||||||
|
AdminId = Utilities.IW4MAdminClient().ClientId,
|
||||||
|
MuteState = muteState ?? MuteState.Unmuted,
|
||||||
|
CommandExecuted = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Migrate old mute meta, else, client has no state, so set a generic one, but don't write it to database.
|
||||||
|
if (muteState is not null)
|
||||||
|
{
|
||||||
|
await WritePersistentData(client, clientMuteMeta);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
await gameEvent.Owner.ExecuteCommandAsync($"muteClient {gameEvent.Target.ClientNumber}");
|
return clientMuteMeta;
|
||||||
await Plugin.DataManager.WritePersistentData(gameEvent.Target, MuteState.Muted);
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Mute(Server server, EFClient origin, EFClient target, DateTime? dateTime, string reason)
|
||||||
|
{
|
||||||
|
var clientMuteMeta = await GetCurrentMuteState(target);
|
||||||
|
if (clientMuteMeta.MuteState is MuteState.Muted && clientMuteMeta.CommandExecuted) return false;
|
||||||
|
|
||||||
|
var newPenalty = new EFPenalty
|
||||||
|
{
|
||||||
|
Type = dateTime is null ? EFPenalty.PenaltyType.Mute : EFPenalty.PenaltyType.TempMute,
|
||||||
|
Expires = dateTime,
|
||||||
|
Offender = target,
|
||||||
|
Offense = reason,
|
||||||
|
Punisher = origin,
|
||||||
|
Link = target.AliasLink
|
||||||
|
};
|
||||||
|
_logger.LogDebug("Creating new mute for {Target} with reason {Reason}", target.Name, reason);
|
||||||
|
await newPenalty.TryCreatePenalty(Plugin.Manager.GetPenaltyService(), _logger);
|
||||||
|
|
||||||
|
clientMuteMeta = new MuteStateMeta
|
||||||
|
{
|
||||||
|
ClientId = target.ClientId,
|
||||||
|
Expiration = dateTime,
|
||||||
|
MuteState = MuteState.Muted,
|
||||||
|
Reason = reason,
|
||||||
|
AdminId = origin.ClientId,
|
||||||
|
CommandExecuted = false
|
||||||
|
};
|
||||||
|
await WritePersistentData(target, clientMuteMeta);
|
||||||
|
|
||||||
|
// Handle game command
|
||||||
|
var client = server.GetClientsAsList().FirstOrDefault(client => client.NetworkId == target.NetworkId);
|
||||||
|
await PerformGameCommand(server, client, clientMuteMeta);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Unmute(Server server, EFClient origin, EFClient target, string reason)
|
||||||
|
{
|
||||||
|
var clientMuteMeta = await GetCurrentMuteState(target);
|
||||||
|
if (clientMuteMeta.MuteState is MuteState.Unmuted && clientMuteMeta.CommandExecuted) return false;
|
||||||
|
if (!target.IsIngame && clientMuteMeta.MuteState is MuteState.Unmuting) return false;
|
||||||
|
|
||||||
|
var newPenalty = new EFPenalty
|
||||||
|
{
|
||||||
|
Type = EFPenalty.PenaltyType.Unmute,
|
||||||
|
Expires = DateTime.Now,
|
||||||
|
Offender = target,
|
||||||
|
Offense = reason,
|
||||||
|
Punisher = origin,
|
||||||
|
Active = true,
|
||||||
|
Link = target.AliasLink
|
||||||
|
};
|
||||||
|
_logger.LogDebug("Creating new unmute for {Target} with reason {Reason}", target.Name, reason);
|
||||||
|
await newPenalty.TryCreatePenalty(Plugin.Manager.GetPenaltyService(), _logger);
|
||||||
|
|
||||||
|
clientMuteMeta = new MuteStateMeta
|
||||||
|
{
|
||||||
|
ClientId = target.ClientId,
|
||||||
|
Expiration = DateTime.UtcNow,
|
||||||
|
MuteState = target.IsIngame ? MuteState.Unmuted : MuteState.Unmuting,
|
||||||
|
Reason = reason,
|
||||||
|
AdminId = origin.ClientId,
|
||||||
|
CommandExecuted = false
|
||||||
|
};
|
||||||
|
await WritePersistentData(target, clientMuteMeta);
|
||||||
|
|
||||||
|
// Handle game command
|
||||||
|
var client = server.GetClientsAsList().FirstOrDefault(client => client.NetworkId == target.NetworkId);
|
||||||
|
await PerformGameCommand(server, client, clientMuteMeta);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task PerformGameCommand(Server server, EFClient? client, MuteStateMeta muteStateMeta)
|
||||||
|
{
|
||||||
|
if (client is null || !client.IsIngame) return;
|
||||||
|
|
||||||
|
switch (muteStateMeta.MuteState)
|
||||||
|
{
|
||||||
|
case MuteState.Muted:
|
||||||
|
await server.ExecuteCommandAsync($"muteClient {client.ClientNumber}");
|
||||||
|
muteStateMeta.CommandExecuted = true;
|
||||||
|
break;
|
||||||
|
case MuteState.Unmuted:
|
||||||
|
await server.ExecuteCommandAsync($"unmute {client.ClientNumber}");
|
||||||
|
muteStateMeta.CommandExecuted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<MuteState?> ReadPersistentDataV1(EFClient client) => TryParse<MuteState>(
|
||||||
|
(await _metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?.Value,
|
||||||
|
out var muteState)
|
||||||
|
? muteState
|
||||||
|
: null;
|
||||||
|
|
||||||
|
private async Task<MuteStateMeta?> ReadPersistentDataV2(EFClient client)
|
||||||
|
{
|
||||||
|
// Get meta from client
|
||||||
|
var clientMuteMeta = client.GetAdditionalProperty<MuteStateMeta>(Plugin.MuteKey);
|
||||||
|
if (clientMuteMeta is not null) return clientMuteMeta;
|
||||||
|
|
||||||
|
// Get meta from database and store in client if exists
|
||||||
|
clientMuteMeta = await _metaService.GetPersistentMetaValue<MuteStateMeta>(Plugin.MuteKey, client.ClientId);
|
||||||
|
if (clientMuteMeta is not null) client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta);
|
||||||
|
|
||||||
|
return clientMuteMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WritePersistentData(EFClient client, MuteStateMeta clientMuteMeta)
|
||||||
|
{
|
||||||
|
client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta);
|
||||||
|
await _metaService.SetPersistentMetaValue(Plugin.MuteKey, clientMuteMeta, client.ClientId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
21
Plugins/Mute/MuteStateMeta.cs
Normal file
21
Plugins/Mute/MuteStateMeta.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Mute;
|
||||||
|
|
||||||
|
public class MuteStateMeta
|
||||||
|
{
|
||||||
|
public int ClientId { get; set; }
|
||||||
|
public string Reason { get; set; } = string.Empty;
|
||||||
|
public DateTime? Expiration { get; set; }
|
||||||
|
public int AdminId { get; set; }
|
||||||
|
public MuteState MuteState { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool CommandExecuted { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MuteState
|
||||||
|
{
|
||||||
|
Muted,
|
||||||
|
Unmuting,
|
||||||
|
Unmuted
|
||||||
|
}
|
@ -1,47 +1,111 @@
|
|||||||
using SharedLibraryCore;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Commands;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
namespace Mute;
|
namespace Mute;
|
||||||
|
|
||||||
public class Plugin : IPlugin
|
public class Plugin : IPlugin
|
||||||
{
|
{
|
||||||
private readonly IInteractionRegistration _interactionRegistration;
|
|
||||||
private static readonly string MuteInteraction = nameof(MuteInteraction);
|
|
||||||
|
|
||||||
public Plugin(IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration)
|
|
||||||
{
|
|
||||||
_interactionRegistration = interactionRegistration;
|
|
||||||
DataManager = new DataManager(metaService);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 static string MuteKey = "IW4MMute";
|
public const string MuteKey = "IW4MMute";
|
||||||
|
public static MuteManager MuteManager { get; private set; } = null!;
|
||||||
public static DataManager DataManager;
|
public static IManager Manager { get; private set; } = null!;
|
||||||
public static readonly Server.Game[] SupportedGames = {Server.Game.IW4};
|
public static readonly Server.Game[] SupportedGames = {Server.Game.IW4};
|
||||||
|
private static readonly string[] DisabledCommands = {nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"};
|
||||||
|
private readonly IInteractionRegistration _interactionRegistration;
|
||||||
|
private static readonly string MuteInteraction = nameof(MuteInteraction);
|
||||||
|
|
||||||
|
public Plugin(IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration,
|
||||||
|
ITranslationLookup translationLookup, ILogger<Plugin> logger)
|
||||||
|
{
|
||||||
|
_interactionRegistration = interactionRegistration;
|
||||||
|
MuteManager = new MuteManager(metaService, translationLookup, logger);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task OnEventAsync(GameEvent gameEvent, Server server)
|
public async Task OnEventAsync(GameEvent gameEvent, Server server)
|
||||||
{
|
{
|
||||||
if (!SupportedGames.Contains(server.GameName)) return;
|
if (!SupportedGames.Contains(server.GameName)) return;
|
||||||
|
|
||||||
|
|
||||||
switch (gameEvent.Type)
|
switch (gameEvent.Type)
|
||||||
{
|
{
|
||||||
|
case GameEvent.EventType.Command:
|
||||||
|
|
||||||
|
break;
|
||||||
case GameEvent.EventType.Join:
|
case GameEvent.EventType.Join:
|
||||||
switch (await DataManager.ReadPersistentData(gameEvent.Origin))
|
// Check if user has any meta set, else ignore (unmuted)
|
||||||
|
var muteMetaJoin = await MuteManager.GetCurrentMuteState(gameEvent.Origin);
|
||||||
|
|
||||||
|
switch (muteMetaJoin.MuteState)
|
||||||
{
|
{
|
||||||
case MuteState.Muted:
|
case MuteState.Muted:
|
||||||
await server.ExecuteCommandAsync($"muteClient {gameEvent.Origin.ClientNumber}");
|
// Let the client know when their mute expires.
|
||||||
|
gameEvent.Origin.Tell(Utilities.CurrentLocalization
|
||||||
|
.LocalizationIndex["PLUGINS_MUTE_REMAINING_TIME"].FormatExt(
|
||||||
|
muteMetaJoin.Expiration is not null
|
||||||
|
? muteMetaJoin.Expiration.Value.HumanizeForCurrentCulture()
|
||||||
|
: Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_NEVER"],
|
||||||
|
muteMetaJoin.Reason));
|
||||||
break;
|
break;
|
||||||
case MuteState.Unmuting:
|
case MuteState.Unmuting:
|
||||||
await server.ExecuteCommandAsync($"unmute {gameEvent.Origin.ClientNumber}");
|
// Handle unmute of unmuted players.
|
||||||
await DataManager.WritePersistentData(gameEvent.Origin, MuteState.Unmuted);
|
await MuteManager.Unmute(server, Utilities.IW4MAdminClient(), gameEvent.Origin,
|
||||||
|
muteMetaJoin.Reason);
|
||||||
|
gameEvent.Origin.Tell(Utilities.CurrentLocalization
|
||||||
|
.LocalizationIndex["PLUGINS_MUTE_COMMANDS_UNMUTE_TARGET_UNMUTED"]
|
||||||
|
.FormatExt(muteMetaJoin.Reason));
|
||||||
break;
|
break;
|
||||||
case MuteState.Unmuted:
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case GameEvent.EventType.Say:
|
||||||
|
var muteMetaSay = await MuteManager.GetCurrentMuteState(gameEvent.Origin);
|
||||||
|
|
||||||
|
switch (muteMetaSay.MuteState)
|
||||||
|
{
|
||||||
|
case MuteState.Muted:
|
||||||
|
// Let the client know when their mute expires.
|
||||||
|
gameEvent.Origin.Tell(Utilities.CurrentLocalization
|
||||||
|
.LocalizationIndex["PLUGINS_MUTE_REMAINING_TIME"].FormatExt(
|
||||||
|
muteMetaSay.Expiration is not null
|
||||||
|
? muteMetaSay.Expiration.Value.HumanizeForCurrentCulture()
|
||||||
|
: Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_NEVER"],
|
||||||
|
muteMetaSay.Reason));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case GameEvent.EventType.Update:
|
||||||
|
// Get correct EFClient object
|
||||||
|
var client = server.GetClientsAsList()
|
||||||
|
.FirstOrDefault(client => client.NetworkId == gameEvent.Origin.NetworkId);
|
||||||
|
if (client == null) break;
|
||||||
|
|
||||||
|
var muteMetaUpdate = await MuteManager.GetCurrentMuteState(client);
|
||||||
|
if (!muteMetaUpdate.CommandExecuted)
|
||||||
|
{
|
||||||
|
await MuteManager.PerformGameCommand(server, client, muteMetaUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (muteMetaUpdate.MuteState)
|
||||||
|
{
|
||||||
|
case MuteState.Muted:
|
||||||
|
// Handle unmute if expired.
|
||||||
|
if (MuteManager.IsExpiredMute(muteMetaUpdate))
|
||||||
|
{
|
||||||
|
await MuteManager.Unmute(server, Utilities.IW4MAdminClient(), client,
|
||||||
|
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_EXPIRED"]);
|
||||||
|
client.Tell(
|
||||||
|
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_TARGET_EXPIRED"]);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,16 +115,30 @@ public class Plugin : IPlugin
|
|||||||
|
|
||||||
public Task OnLoadAsync(IManager manager)
|
public Task OnLoadAsync(IManager manager)
|
||||||
{
|
{
|
||||||
|
Manager = manager;
|
||||||
|
|
||||||
|
manager.CommandInterceptors.Add(gameEvent =>
|
||||||
|
{
|
||||||
|
if (gameEvent.Extra is not Command command) return true;
|
||||||
|
return !DisabledCommands.Contains(command.GetType().Name) && !command.IsBroadcast;
|
||||||
|
});
|
||||||
|
|
||||||
_interactionRegistration.RegisterInteraction(MuteInteraction, async (clientId, game, token) =>
|
_interactionRegistration.RegisterInteraction(MuteInteraction, async (clientId, game, token) =>
|
||||||
{
|
{
|
||||||
if (!clientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game)game.Value))
|
if (!clientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game) game.Value))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var muteState = await DataManager.ReadPersistentData(new EFClient { ClientId = clientId.Value });
|
var reasonInput = new {Name = "Reason", Placeholder = "Reason", TargetId = clientId};
|
||||||
|
var durationInput = new {Name = "Length", Placeholder = "Length", TargetId = clientId};
|
||||||
|
var inputs = new[] {reasonInput, durationInput};
|
||||||
|
var inputsJson = JsonSerializer.Serialize(inputs);
|
||||||
|
|
||||||
return muteState is MuteState.Unmuted or MuteState.Unmuting
|
var clientMuteMetaState = (await MuteManager.GetCurrentMuteState(new EFClient {ClientId = clientId.Value}))
|
||||||
|
.MuteState;
|
||||||
|
|
||||||
|
return clientMuteMetaState is MuteState.Unmuted or MuteState.Unmuting
|
||||||
? new InteractionData
|
? new InteractionData
|
||||||
{
|
{
|
||||||
EntityId = clientId,
|
EntityId = clientId,
|
||||||
@ -69,11 +147,19 @@ public class Plugin : IPlugin
|
|||||||
ActionPath = "DynamicAction",
|
ActionPath = "DynamicAction",
|
||||||
ActionMeta = new()
|
ActionMeta = new()
|
||||||
{
|
{
|
||||||
{ "InteractionId", "command" },
|
{"InteractionId", "command"},
|
||||||
{ "Data", $"mute @{clientId.Value}" },
|
{"Data", $"mute @{clientId.Value}"},
|
||||||
{ "ActionButtonLabel", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"] },
|
{"Outputs", $"{reasonInput.Name},{durationInput.Name}"},
|
||||||
{ "Name", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"] },
|
{"Inputs", inputsJson},
|
||||||
{ "ShouldRefresh", true.ToString() }
|
{
|
||||||
|
"ActionButtonLabel",
|
||||||
|
Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name",
|
||||||
|
Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"]
|
||||||
|
},
|
||||||
|
{"ShouldRefresh", true.ToString()}
|
||||||
},
|
},
|
||||||
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
||||||
Source = Name
|
Source = Name
|
||||||
@ -81,22 +167,32 @@ public class Plugin : IPlugin
|
|||||||
: new InteractionData
|
: new InteractionData
|
||||||
{
|
{
|
||||||
EntityId = clientId,
|
EntityId = clientId,
|
||||||
Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"],
|
Name = Utilities.CurrentLocalization.LocalizationIndex[
|
||||||
|
"WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"],
|
||||||
DisplayMeta = "oi-volume-high",
|
DisplayMeta = "oi-volume-high",
|
||||||
ActionPath = "DynamicAction",
|
ActionPath = "DynamicAction",
|
||||||
ActionMeta = new()
|
ActionMeta = new()
|
||||||
{
|
{
|
||||||
{ "InteractionId", "command" },
|
{"InteractionId", "command"},
|
||||||
{ "Data", $"mute @{clientId.Value}" },
|
{"Data", $"mute @{clientId.Value}"},
|
||||||
{ "ActionButtonLabel", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"] },
|
{"Outputs", $"{reasonInput.Name},{durationInput.Name}"},
|
||||||
{ "Name", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"] },
|
{"Inputs", inputsJson},
|
||||||
{ "ShouldRefresh", true.ToString() }
|
{
|
||||||
|
"ActionButtonLabel",
|
||||||
|
Utilities.CurrentLocalization.LocalizationIndex[
|
||||||
|
"WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name",
|
||||||
|
Utilities.CurrentLocalization.LocalizationIndex[
|
||||||
|
"WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"]
|
||||||
|
},
|
||||||
|
{"ShouldRefresh", true.ToString()}
|
||||||
},
|
},
|
||||||
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
||||||
Source = Name
|
Source = Name
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// event executed when event has finished executing
|
/// event executed when event has finished executing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<GameEvent> OnGameEventExecuted;
|
event EventHandler<GameEvent> OnGameEventExecuted;
|
||||||
|
|
||||||
IAlertManager AlertManager { get; }
|
IAlertManager AlertManager { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,21 @@
|
|||||||
color: rgba(116, 147, 99, 1);
|
color: rgba(116, 147, 99, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.penalties-color-unmute {
|
||||||
|
color: #749363;
|
||||||
|
color: rgba(180, 222, 12, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.penalties-color-mute {
|
||||||
|
color: #749363;
|
||||||
|
color: rgba(222, 222, 12, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.penalties-color-tempmute {
|
||||||
|
color: #749363;
|
||||||
|
color: rgba(222, 145, 12, 1);
|
||||||
|
}
|
||||||
|
|
||||||
.penalties-color-unflag {
|
.penalties-color-unflag {
|
||||||
color: rgb(140, 154, 132);
|
color: rgb(140, 154, 132);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user