Compare commits
12 Commits
2022.06.02
...
2022.06.03
Author | SHA1 | Date | |
---|---|---|---|
7ecf516278 | |||
210f1ca336 | |||
a38789adb9 | |||
e459b2fcde | |||
26853a0005 | |||
ee14306db9 | |||
169105e849 | |||
7c10e0e3de | |||
2f7eb07e39 | |||
1f13f9122c | |||
dd8c4f438f | |||
2230036d45 |
@ -24,7 +24,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2037" />
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2038" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -792,8 +792,16 @@ namespace IW4MAdmin
|
||||
/// <returns></returns>
|
||||
async Task<List<EFClient>[]> PollPlayersAsync()
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
var currentClients = GetClientsAsList();
|
||||
var statusResponse = await this.GetStatusAsync(Manager.CancellationToken);
|
||||
var statusResponse = await this.GetStatusAsync(tokenSource.Token);
|
||||
|
||||
if (statusResponse is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var polledClients = statusResponse.Clients.AsEnumerable();
|
||||
|
||||
if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
||||
@ -930,6 +938,11 @@ namespace IW4MAdmin
|
||||
|
||||
var polledClients = await PollPlayersAsync();
|
||||
|
||||
if (polledClients is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var disconnectingClient in polledClients[1]
|
||||
.Where(client => !client.IsZombieClient /* ignores "fake" zombie clients */))
|
||||
{
|
||||
|
12
Application/Misc/AsyncResult.cs
Normal file
12
Application/Misc/AsyncResult.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc;
|
||||
|
||||
public class AsyncResult : IAsyncResult
|
||||
{
|
||||
public object AsyncState { get; set; }
|
||||
public WaitHandle AsyncWaitHandle { get; set; }
|
||||
public bool CompletedSynchronously { get; set; }
|
||||
public bool IsCompleted { get; set; }
|
||||
}
|
@ -276,8 +276,8 @@ namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
_logger.LogDebug("OnLoad executing for {Name}", Name);
|
||||
_scriptEngine.SetValue("_manager", manager);
|
||||
_scriptEngine.SetValue("getDvar", GetDvarAsync);
|
||||
_scriptEngine.SetValue("setDvar", SetDvarAsync);
|
||||
_scriptEngine.SetValue("getDvar", BeginGetDvar);
|
||||
_scriptEngine.SetValue("setDvar", BeginSetDvar);
|
||||
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
|
||||
|
||||
return Task.CompletedTask;
|
||||
@ -343,7 +343,8 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// <param name="commands">commands value from jint parser</param>
|
||||
/// <param name="scriptCommandFactory">factory to create the command from</param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory)
|
||||
private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands,
|
||||
IScriptCommandFactory scriptCommandFactory)
|
||||
{
|
||||
var commandList = new List<IManagerCommand>();
|
||||
|
||||
@ -410,7 +411,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
_scriptEngine.SetValue("_event", gameEvent);
|
||||
var jsEventObject = _scriptEngine.Evaluate("_event");
|
||||
|
||||
|
||||
dynamicCommand.execute.Target.Invoke(_scriptEngine, jsEventObject);
|
||||
}
|
||||
|
||||
@ -424,7 +425,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
throw new PluginException("A runtime error occured while executing action for script plugin");
|
||||
}
|
||||
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString()))
|
||||
@ -454,83 +455,71 @@ namespace IW4MAdmin.Application.Misc
|
||||
return commandList;
|
||||
}
|
||||
|
||||
private void GetDvarAsync(Server server, string dvarName, Delegate onCompleted)
|
||||
private void BeginGetDvar(Server server, string dvarName, Delegate onCompleted)
|
||||
{
|
||||
Task.Run(() =>
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
|
||||
|
||||
server.BeginGetDvar(dvarName, result =>
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
string result = null;
|
||||
var success = true;
|
||||
var shouldRelease = false;
|
||||
try
|
||||
{
|
||||
result = server.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
_onProcessing.Wait(tokenSource.Token);
|
||||
shouldRelease = true;
|
||||
var (success, value) = (ValueTuple<bool, string>)result.AsyncState;
|
||||
|
||||
_onProcessing.Wait();
|
||||
try
|
||||
{
|
||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||
new[]
|
||||
{
|
||||
JsValue.FromObject(_scriptEngine, server),
|
||||
JsValue.FromObject(_scriptEngine, dvarName),
|
||||
JsValue.FromObject(_scriptEngine, result),
|
||||
JsValue.FromObject(_scriptEngine, success),
|
||||
});
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
{
|
||||
_onProcessing.Release();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
private void SetDvarAsync(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
var success = true;
|
||||
|
||||
try
|
||||
{
|
||||
server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
_onProcessing.Wait();
|
||||
try
|
||||
{
|
||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||
new[]
|
||||
{
|
||||
JsValue.FromObject(_scriptEngine, server),
|
||||
JsValue.FromObject(_scriptEngine, dvarName),
|
||||
JsValue.FromObject(_scriptEngine, dvarValue),
|
||||
JsValue.FromObject(_scriptEngine, dvarName),
|
||||
JsValue.FromObject(_scriptEngine, value),
|
||||
JsValue.FromObject(_scriptEngine, success)
|
||||
});
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
if (_onProcessing.CurrentCount == 0 && shouldRelease)
|
||||
{
|
||||
_onProcessing.Release();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, tokenSource.Token);
|
||||
}
|
||||
|
||||
private void BeginSetDvar(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
|
||||
|
||||
server.BeginSetDvar(dvarName, dvarValue, result =>
|
||||
{
|
||||
var shouldRelease = false;
|
||||
try
|
||||
{
|
||||
_onProcessing.Wait(tokenSource.Token);
|
||||
shouldRelease = true;
|
||||
var success = (bool)result.AsyncState;
|
||||
|
||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||
new[]
|
||||
{
|
||||
JsValue.FromObject(_scriptEngine, server),
|
||||
JsValue.FromObject(_scriptEngine, dvarName),
|
||||
JsValue.FromObject(_scriptEngine, dvarValue),
|
||||
JsValue.FromObject(_scriptEngine, success)
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0 && shouldRelease)
|
||||
{
|
||||
_onProcessing.Release();
|
||||
}
|
||||
}
|
||||
}, tokenSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static SharedLibraryCore.Server;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
@ -141,6 +142,30 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
};
|
||||
}
|
||||
|
||||
public void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default)
|
||||
{
|
||||
GetDvarAsync<string>(connection, dvarName, token: token).ContinueWith(action =>
|
||||
{
|
||||
if (action.Exception is null)
|
||||
{
|
||||
callback?.Invoke(new AsyncResult
|
||||
{
|
||||
IsCompleted = true,
|
||||
AsyncState = (true, action.Result.Value)
|
||||
});
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
callback?.Invoke(new AsyncResult
|
||||
{
|
||||
IsCompleted = true,
|
||||
AsyncState = (false, (string)null)
|
||||
});
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default)
|
||||
{
|
||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS, "status", token);
|
||||
@ -196,6 +221,31 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString, token)).Length > 0;
|
||||
}
|
||||
|
||||
public void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
SetDvarAsync(connection, dvarName, dvarValue, token).ContinueWith(action =>
|
||||
{
|
||||
if (action.Exception is null)
|
||||
{
|
||||
callback?.Invoke(new AsyncResult
|
||||
{
|
||||
IsCompleted = true,
|
||||
AsyncState = true
|
||||
});
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
callback?.Invoke(new AsyncResult
|
||||
{
|
||||
IsCompleted = true,
|
||||
AsyncState = false
|
||||
});
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
private List<EFClient> ClientsFromStatus(string[] Status)
|
||||
{
|
||||
List<EFClient> StatusPlayers = new List<EFClient>();
|
||||
|
@ -46,7 +46,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
{ColorCodes.White.ToString(), "^7"},
|
||||
{ColorCodes.Map.ToString(), "^8"},
|
||||
{ColorCodes.Grey.ToString(), "^9"},
|
||||
{ColorCodes.Wildcard.ToString(), ":^"},
|
||||
{ColorCodes.Wildcard.ToString(), "^:"}
|
||||
};
|
||||
|
||||
public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory)
|
||||
|
@ -53,6 +53,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
||||
Plugins\ScriptPlugins\GameInterface.js = Plugins\ScriptPlugins\GameInterface.js
|
||||
Plugins\ScriptPlugins\SubnetBan.js = Plugins\ScriptPlugins\SubnetBan.js
|
||||
Plugins\ScriptPlugins\BanBroadcasting.js = Plugins\ScriptPlugins\BanBroadcasting.js
|
||||
Plugins\ScriptPlugins\ParserH1MOD.js = Plugins\ScriptPlugins\ParserH1MOD.js
|
||||
Plugins\ScriptPlugins\ParserPlutoniumT5.js = Plugins\ScriptPlugins\ParserPlutoniumT5.js
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
||||
|
39
Plugins/ScriptPlugins/ParserPlutoniumT5.js
Normal file
39
Plugins/ScriptPlugins/ParserPlutoniumT5.js
Normal file
@ -0,0 +1,39 @@
|
||||
var rconParser;
|
||||
var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.1,
|
||||
name: 'Plutonium T5 Parser',
|
||||
isParser: true,
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||
|
||||
rconParser.Configuration.DefaultInstallationDirectoryHint = '{LocalAppData}/Plutonium/storage/t5';
|
||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||
rconParser.Configuration.Dvar.Pattern = '^(?:\\^7)?\\"(.+)\\" is: \\"(.+)?\\" default: \\"(.+)?\\"\\n(?:latched: \\"(.+)?\\"\\n)? *(.+)$';
|
||||
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
|
||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
||||
rconParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||
rconParser.Configuration.DefaultRConPort = 3074;
|
||||
rconParser.Configuration.CanGenerateLogPath = false;
|
||||
|
||||
rconParser.Version = 'Call of Duty Multiplayer - Ship COD_T5_S MP build 7.0.189 CL(1022875) CODPCAB-V64 CEG Wed Nov 02 18:02:23 2011 win-x86';
|
||||
rconParser.GameName = 6; // T5
|
||||
eventParser.Version = 'Call of Duty Multiplayer - Ship COD_T5_S MP build 7.0.189 CL(1022875) CODPCAB-V64 CEG Wed Nov 02 18:02:23 2011 win-x86';
|
||||
eventParser.GameName = 6; // T5
|
||||
eventParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
@ -29,7 +29,7 @@ const plugin = {
|
||||
let exempt = false;
|
||||
// prevent players that are exempt from being kicked
|
||||
vpnExceptionIds.forEach(function (id) {
|
||||
if (id === origin.ClientId) {
|
||||
if (id == origin.ClientId) { // when loaded from the config the "id" type is not the same as the ClientId type
|
||||
exempt = true;
|
||||
return false;
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
@ -7,7 +9,15 @@ namespace Stats.Config
|
||||
public class AnticheatConfiguration
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
[Obsolete]
|
||||
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; } = new Dictionary<long, DetectionType[]>();
|
||||
|
||||
public IDictionary<Game, DetectionType[]> GameDetectionTypes { get; set; } =
|
||||
new Dictionary<Game, DetectionType[]>()
|
||||
{
|
||||
{ Game.IW4, Enum.GetValues(typeof(DetectionType)).Cast<DetectionType>().ToArray() },
|
||||
{ Game.T6, new[] { DetectionType.Offset, DetectionType.Snap, DetectionType.Strain } }
|
||||
};
|
||||
public IList<long> IgnoredClientIds { get; set; } = new List<long>();
|
||||
public IDictionary<Game, IDictionary<DetectionType, string[]>> IgnoredDetectionSpecification{ get; set; } = new Dictionary<Game, IDictionary<DetectionType, string[]>>
|
||||
{
|
||||
|
@ -633,32 +633,44 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return;
|
||||
}
|
||||
|
||||
var hit = new EFClientKill()
|
||||
EFClientKill hit;
|
||||
try
|
||||
{
|
||||
Active = true,
|
||||
AttackerId = attacker.ClientId,
|
||||
VictimId = victim.ClientId,
|
||||
ServerId = serverId,
|
||||
DeathOrigin = vDeathOrigin,
|
||||
KillOrigin = vKillOrigin,
|
||||
DeathType = (int) ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)),
|
||||
Damage = int.Parse(damage),
|
||||
HitLoc = (int) ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
|
||||
WeaponReference = weapon,
|
||||
ViewAngles = vViewAngles,
|
||||
TimeOffset = long.Parse(offset),
|
||||
When = time,
|
||||
IsKillstreakKill = isKillstreakKill[0] != '0',
|
||||
AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture),
|
||||
Fraction = double.Parse(fraction, System.Globalization.CultureInfo.InvariantCulture),
|
||||
VisibilityPercentage = double.Parse(visibilityPercentage,
|
||||
System.Globalization.CultureInfo.InvariantCulture),
|
||||
IsKill = !isDamage,
|
||||
AnglesList = snapshotAngles,
|
||||
IsAlive = isAlive == "1",
|
||||
TimeSinceLastAttack = long.Parse(lastAttackTime),
|
||||
GameName = (int) attacker.CurrentServer.GameName
|
||||
};
|
||||
hit = new EFClientKill
|
||||
{
|
||||
Active = true,
|
||||
AttackerId = attacker.ClientId,
|
||||
VictimId = victim.ClientId,
|
||||
ServerId = serverId,
|
||||
DeathOrigin = vDeathOrigin,
|
||||
KillOrigin = vKillOrigin,
|
||||
DeathType = (int) ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)),
|
||||
Damage = int.Parse(damage),
|
||||
HitLoc = (int) ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
|
||||
WeaponReference = weapon,
|
||||
ViewAngles = vViewAngles,
|
||||
TimeOffset = long.Parse(offset),
|
||||
When = time,
|
||||
IsKillstreakKill = isKillstreakKill[0] != '0',
|
||||
AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture),
|
||||
Fraction = double.Parse(fraction, System.Globalization.CultureInfo.InvariantCulture),
|
||||
VisibilityPercentage = double.Parse(visibilityPercentage,
|
||||
System.Globalization.CultureInfo.InvariantCulture),
|
||||
IsKill = !isDamage,
|
||||
AnglesList = snapshotAngles,
|
||||
IsAlive = isAlive == "1",
|
||||
TimeSinceLastAttack = long.Parse(lastAttackTime),
|
||||
GameName = (int) attacker.CurrentServer.GameName
|
||||
};
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "Could not parse script hit data. Damage={Damage}, TimeOffset={Offset}, TimeSinceLastAttack={LastAttackTime}",
|
||||
damage, offset, lastAttackTime);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
hit.SetAdditionalProperty("HitLocationReference", hitLoc);
|
||||
|
||||
@ -769,7 +781,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "Could not save hit or anti-cheat info {@attacker} {@victim} {server}", attacker,
|
||||
_log.LogError(ex, "Could not save hit or anti-cheat info {Attacker} {Victim} {Server}", attacker,
|
||||
victim, serverId);
|
||||
}
|
||||
|
||||
@ -806,7 +818,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
||||
{
|
||||
var detectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
||||
var serverDetectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
||||
var gameDetectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.GameDetectionTypes;
|
||||
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
||||
|
||||
if (ignoredClients.Contains(clientId))
|
||||
@ -814,10 +827,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (!detectionTypes[server.EndPoint].Contains(detectionType))
|
||||
if (!serverDetectionTypes[server.EndPoint].Contains(detectionType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -826,6 +838,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!gameDetectionTypes[server.GameName].Contains(detectionType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace SharedLibraryCore.Dtos
|
||||
public int Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// how many itesm to take
|
||||
/// how many items to take
|
||||
/// </summary>
|
||||
public int Count { get; set; } = 100;
|
||||
|
||||
@ -35,4 +35,4 @@ namespace SharedLibraryCore.Dtos
|
||||
Ascending,
|
||||
Descending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
|
||||
|
||||
void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// set value of DVAR by name
|
||||
@ -65,6 +67,8 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
|
||||
|
||||
void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// executes a console command on the server
|
||||
|
@ -773,6 +773,11 @@ namespace SharedLibraryCore
|
||||
{
|
||||
return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token);
|
||||
}
|
||||
|
||||
public static void BeginGetDvar(this Server server, string dvarName, AsyncCallback callback, CancellationToken token = default)
|
||||
{
|
||||
server.RconParser.BeginGetDvar(server.RemoteConnection, dvarName, callback, token);
|
||||
}
|
||||
|
||||
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
|
||||
T fallbackValue = default)
|
||||
@ -808,6 +813,12 @@ namespace SharedLibraryCore
|
||||
{
|
||||
await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token);
|
||||
}
|
||||
|
||||
public static void BeginSetDvar(this Server server, string dvarName, object dvarValue,
|
||||
AsyncCallback callback, CancellationToken token = default)
|
||||
{
|
||||
server.RconParser.BeginSetDvar(server.RemoteConnection, dvarName, dvarValue, callback, token);
|
||||
}
|
||||
|
||||
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
||||
{
|
||||
@ -824,9 +835,17 @@ namespace SharedLibraryCore
|
||||
return await ExecuteCommandAsync(server, commandName, default);
|
||||
}
|
||||
|
||||
public static Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
|
||||
public static async Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
|
||||
{
|
||||
return server.RconParser.GetStatusAsync(server.RemoteConnection, token);
|
||||
try
|
||||
{
|
||||
return await server.RconParser.GetStatusAsync(server.RemoteConnection, token);
|
||||
}
|
||||
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
25
WebfrontCore/Controllers/API/PenaltyController.cs
Normal file
25
WebfrontCore/Controllers/API/PenaltyController.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using WebfrontCore.QueryHelpers.Models;
|
||||
|
||||
namespace WebfrontCore.Controllers.API;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
public class PenaltyController : BaseController
|
||||
{
|
||||
private readonly IResourceQueryHelper<BanInfoRequest, BanInfo> _banInfoQueryHelper;
|
||||
|
||||
public PenaltyController(IManager manager, IResourceQueryHelper<BanInfoRequest, BanInfo> banInfoQueryHelper) : base(manager)
|
||||
{
|
||||
_banInfoQueryHelper = banInfoQueryHelper;
|
||||
}
|
||||
|
||||
[HttpGet("BanInfo/{clientName}")]
|
||||
public async Task<IActionResult> BanInfo(BanInfoRequest request)
|
||||
{
|
||||
var result = await _banInfoQueryHelper.QueryResource(request);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
96
WebfrontCore/QueryHelpers/BanInfoResourceQueryHelper.cs
Normal file
96
WebfrontCore/QueryHelpers/BanInfoResourceQueryHelper.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models.Client;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using WebfrontCore.QueryHelpers.Models;
|
||||
|
||||
namespace WebfrontCore.QueryHelpers;
|
||||
|
||||
public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, BanInfo>
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
|
||||
public BanInfoResourceQueryHelper(IDatabaseContextFactory contextFactory)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
public async Task<ResourceQueryHelperResult<BanInfo>> QueryResource(BanInfoRequest query)
|
||||
{
|
||||
if (query.Count > 30)
|
||||
{
|
||||
query.Count = 30;
|
||||
}
|
||||
|
||||
await using var context = _contextFactory.CreateContext(false);
|
||||
|
||||
var matchingClients = await context.Clients.Where(client =>
|
||||
EF.Functions.ILike(client.CurrentAlias.SearchableName ?? client.CurrentAlias.Name, $"%{query.ClientName.Trim()}%"))
|
||||
.Where(client => client.Level == EFClient.Permission.Banned)
|
||||
.OrderByDescending(client => client.LastConnection)
|
||||
.Skip(query.Offset)
|
||||
.Take(query.Count)
|
||||
.Select(client => new
|
||||
{
|
||||
client.CurrentAlias.Name,
|
||||
client.NetworkId,
|
||||
client.AliasLinkId,
|
||||
client.ClientId
|
||||
}).ToListAsync();
|
||||
|
||||
var usedIps = await context.Aliases
|
||||
.Where(alias => matchingClients.Select(client => client.AliasLinkId).Contains(alias.LinkId))
|
||||
.Where(alias => alias.IPAddress != null)
|
||||
.Select(alias => new { alias.IPAddress, alias.LinkId })
|
||||
.ToListAsync();
|
||||
|
||||
var usedIpsGrouped = usedIps
|
||||
.GroupBy(alias => alias.LinkId)
|
||||
.ToDictionary(key => key.Key, value => value.Select(alias => alias.IPAddress).Distinct());
|
||||
|
||||
var searchingNetworkIds = matchingClients.Select(client => client.NetworkId);
|
||||
var searchingIps = usedIpsGrouped.SelectMany(group => group.Value);
|
||||
|
||||
var matchedPenalties = await context.PenaltyIdentifiers.Where(identifier =>
|
||||
searchingNetworkIds.Contains(identifier.NetworkId) ||
|
||||
searchingIps.Contains(identifier.IPv4Address))
|
||||
.Select(penalty => new
|
||||
{
|
||||
penalty.CreatedDateTime,
|
||||
PunisherName = penalty.Penalty.Punisher.CurrentAlias.Name,
|
||||
Offense = string.IsNullOrEmpty(penalty.Penalty.AutomatedOffense) ? penalty.Penalty.Offense : "Anticheat Detection",
|
||||
LinkId = penalty.Penalty.Offender.AliasLinkId,
|
||||
penalty.Penalty.PunisherId
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var groupedPenalties = matchedPenalties.GroupBy(penalty => penalty.LinkId)
|
||||
.ToDictionary(key => key.Key, value => value.FirstOrDefault());
|
||||
|
||||
var results = matchingClients.Select(client =>
|
||||
{
|
||||
var matchedPenalty =
|
||||
groupedPenalties.ContainsKey(client.AliasLinkId) ? groupedPenalties[client.AliasLinkId] : null;
|
||||
return new BanInfo
|
||||
{
|
||||
DateTime = matchedPenalty?.CreatedDateTime,
|
||||
OffenderName = client.Name.StripColors(),
|
||||
OffenderId = client.ClientId,
|
||||
PunisherName = matchedPenalty?.PunisherName.StripColors(),
|
||||
PunisherId = matchedPenalty?.PunisherId,
|
||||
Offense = matchedPenalty?.Offense
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return new ResourceQueryHelperResult<BanInfo>
|
||||
{
|
||||
RetrievedResultCount = results.Count,
|
||||
TotalResultCount = results.Count,
|
||||
Results = results
|
||||
};
|
||||
}
|
||||
}
|
12
WebfrontCore/QueryHelpers/Models/BanInfo.cs
Normal file
12
WebfrontCore/QueryHelpers/Models/BanInfo.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
public class BanInfo
|
||||
{
|
||||
public string OffenderName { get; set; }
|
||||
public int OffenderId { get; set; }
|
||||
public string PunisherName { get; set; }
|
||||
public int? PunisherId { get; set; }
|
||||
public string Offense { get; set; }
|
||||
public DateTime? DateTime { get; set; }
|
||||
public long? TimeStamp => DateTime.HasValue ? new DateTimeOffset(DateTime.Value, TimeSpan.Zero).ToUnixTimeSeconds() : null;
|
||||
}
|
8
WebfrontCore/QueryHelpers/Models/BanInfoRequest.cs
Normal file
8
WebfrontCore/QueryHelpers/Models/BanInfoRequest.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using SharedLibraryCore.Dtos;
|
||||
|
||||
namespace WebfrontCore.QueryHelpers.Models;
|
||||
|
||||
public class BanInfoRequest : PaginationRequest
|
||||
{
|
||||
public string ClientName { get; set; }
|
||||
}
|
@ -24,11 +24,12 @@ using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using Stats.Client.Abstractions;
|
||||
using Stats.Config;
|
||||
using WebfrontCore.Controllers.API.Validation;
|
||||
using WebfrontCore.Middleware;
|
||||
using WebfrontCore.QueryHelpers;
|
||||
using WebfrontCore.QueryHelpers.Models;
|
||||
|
||||
namespace WebfrontCore
|
||||
{
|
||||
@ -127,6 +128,7 @@ namespace WebfrontCore
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IMetaServiceV2>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ApplicationConfiguration>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ClientService>());
|
||||
services.AddSingleton<IResourceQueryHelper<BanInfoRequest, BanInfo>, BanInfoResourceQueryHelper>();
|
||||
services.AddSingleton(
|
||||
Program.ApplicationServiceProvider.GetRequiredService<IServerDistributionCalculator>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider
|
||||
|
@ -19,7 +19,7 @@
|
||||
<partial name="_ListAuditLog"/>
|
||||
</tbody>
|
||||
</table>
|
||||
<i id="loaderLoad" class="loader-load-more oi oi-chevron-bottom text-center text-primary d-none d-lg-block mt-10"></i>
|
||||
<i id="loaderLoad" class="loader-load-more oi oi-chevron-bottom text-center w-full text-primary mt-10"></i>
|
||||
</div>
|
||||
|
||||
@section scripts {
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="col-12 col-lg-9 col-xl-10 mt-0">
|
||||
<h2 class="content-title mb-0">Top Players</h2>
|
||||
<span class="text-muted">
|
||||
<color-code value="@(ViewBag.SelectedServerName ?? ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"])"></color-code>
|
||||
<color-code value="@(Model.FirstOrDefault(m => m.Endpoint == ViewBag.SelectedServerId)?.Name ?? ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"])"></color-code>
|
||||
</span>
|
||||
|
||||
<div id="topPlayersContainer">
|
||||
|
@ -22,10 +22,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
<div class="pt-15 pl-15 pr-15 d-flex flex-wrap flex-column flex-md-row justify-content-between w-full w-auto-lg">
|
||||
<div class="pt-15 pl-15 pr-15 d-flex flex-wrap flex-column flex-md-row w-full w-auto-lg">
|
||||
@if (groupedClients.Count > 0)
|
||||
{
|
||||
<div class="flex-fill flex-lg-grow-0 w-full w-md-half pr-md-10 pb-md-10">
|
||||
<div class="flex-fill flex-lg-grow-0 w-full w-md-half">
|
||||
@foreach (var chat in Model.ChatHistory)
|
||||
{
|
||||
var message = chat.IsHidden && !ViewBag.Authorized ? chat.HiddenMessage : chat.Message;
|
||||
@ -33,7 +33,6 @@
|
||||
|
||||
<div class="text-truncate">
|
||||
<i class="oi @stateIcon"></i>
|
||||
|
||||
<span>
|
||||
<color-code value="@chat.Name"></color-code>
|
||||
</span>
|
||||
@ -47,36 +46,34 @@
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
<hr class="d-block d-md-none"/>
|
||||
<hr class="d-block d-md-none"/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex flex-row w-full w-md-half pl-md-10 pb-md-10">
|
||||
|
||||
<div class="d-flex flex-row w-full w-md-half">
|
||||
@foreach (var clientIndex in groupedClients)
|
||||
{
|
||||
<div class="@(clientIndex.index == 1 ? "pl-md-10" : "pr-md-10") w-half w-xl-full">
|
||||
<div class="w-half">
|
||||
@foreach (var client in clientIndex.group)
|
||||
{
|
||||
var levelColorClass = !ViewBag.Authorized || client.client.LevelInt == 0 ? "text-light-dm text-dark-lm" : $"level-color-{client.client.LevelInt}";
|
||||
<div class="d-flex @(clientIndex.index == 1 ? "justify-content-start ml-auto flex-row-reverse" : "ml-auto") w-full w-xl-200">
|
||||
<div class="d-flex @(clientIndex.index == 1 ? "justify-content-start flex-row-reverse" : "")">
|
||||
<has-permission entity="AdminMenu" required-permission="Update">
|
||||
<a href="#actionModal" class="profile-action" data-action="kick" data-action-id="@client.client.ClientId" aria-hidden="true">
|
||||
<i class="oi oi-circle-x font-size-12 @levelColorClass @(clientIndex.index == 1 ? "ml-5" : "mr-5")"></i>
|
||||
<i class="oi oi-circle-x font-size-12 @levelColorClass"></i>
|
||||
</a>
|
||||
</has-permission>
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.client.ClientId" class="@levelColorClass no-decoration text-truncate">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.client.ClientId" class="@levelColorClass no-decoration text-truncate ml-5 mr-5">
|
||||
<color-code value="@client.client.Name"></color-code>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (groupedClients.Count > 0)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
@if (groupedClients.Count > 0)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,7 +56,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ref" />
|
||||
<Folder Include="wwwroot\lib\canvas.js\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
Reference in New Issue
Block a user