From 04a95aa58a204c731c738eda203e27143b142c97 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 31 Jul 2020 20:40:03 -0500 Subject: [PATCH] add configurable command and broadcast command prefix for issue #149 --- Application/ApplicationManager.cs | 9 +- Application/EventParsers/BaseEventParser.cs | 7 +- .../EventParsers/DynamicEventParser.cs | 5 +- Application/IW4MServer.cs | 2 +- SharedLibraryCore/Command.cs | 4 +- .../Commands/CommandProcessing.cs | 11 +- SharedLibraryCore/Commands/NativeCommands.cs | 14 +- .../Configuration/ApplicationConfiguration.cs | 6 + .../Configuration/CommandConfiguration.cs | 13 ++ .../ApplicationConfigurationValidator.cs | 14 +- .../Interfaces/IManagerCommand.cs | 5 + SharedLibraryCore/PartialEntities/EFClient.cs | 2 +- SharedLibraryCore/Utilities.cs | 9 +- .../ApplicationTests/ApplicationTests.csproj | 3 + .../ApplicationTests/BaseEventParserTests.cs | 60 +++++-- Tests/ApplicationTests/CommandTests.cs | 20 ++- .../Files/GameEvent.Command.CustomPrefix.json | 32 ++++ Tests/ApplicationTests/Files/GameEvents.json | 159 ++++++++++++++++++ .../Fixtures/EventGenerators.cs | 26 +++ Tests/ApplicationTests/Mocks/Commands.cs | 14 +- Tests/ApplicationTests/ServerTests.cs | 5 +- Tests/ApplicationTests/StatsTests.cs | 3 +- WebfrontCore/Controllers/ActionController.cs | 21 ++- WebfrontCore/Controllers/ConsoleController.cs | 11 +- WebfrontCore/Controllers/HomeController.cs | 1 + WebfrontCore/Views/Home/Help.cshtml | 4 +- WebfrontCore/wwwroot/js/console.js | 4 - 27 files changed, 403 insertions(+), 61 deletions(-) create mode 100644 Tests/ApplicationTests/Files/GameEvent.Command.CustomPrefix.json create mode 100644 Tests/ApplicationTests/Files/GameEvents.json create mode 100644 Tests/ApplicationTests/Fixtures/EventGenerators.cs diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 1d059d37c..a1980aad5 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -81,7 +81,7 @@ namespace IW4MAdmin.Application ConfigHandler = appConfigHandler; StartTime = DateTime.UtcNow; PageList = new PageList(); - AdditionalEventParsers = new List() { new BaseEventParser(parserRegexFactory, logger) }; + AdditionalEventParsers = new List() { new BaseEventParser(parserRegexFactory, logger, appConfigHandler.Configuration()) }; AdditionalRConParsers = new List() { new BaseRConParser(parserRegexFactory) }; TokenAuthenticator = new TokenAuthentication(); _logger = logger; @@ -432,6 +432,11 @@ namespace IW4MAdmin.Application commandsToAddToConfig.AddRange(unsavedCommands); } + // this is because I want to store the command prefix in IW4MAdminSettings, but can't easily + // inject it to all the places that need it + cmdConfig.CommandPrefix = config.CommandPrefix; + cmdConfig.BroadcastCommandPrefix = config.BroadcastCommandPrefix; + foreach (var cmd in commandsToAddToConfig) { cmdConfig.Commands.Add(cmd.CommandConfigNameForType(), @@ -729,7 +734,7 @@ namespace IW4MAdmin.Application public IEventParser GenerateDynamicEventParser(string name) { - return new DynamicEventParser(_parserRegexFactory, _logger) + return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration()) { Name = name }; diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index b7d8e6948..65c5756e1 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -1,4 +1,5 @@ using SharedLibraryCore; +using SharedLibraryCore.Configuration; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Interfaces; using System; @@ -12,11 +13,13 @@ namespace IW4MAdmin.Application.EventParsers { private readonly Dictionary)> _customEventRegistrations; private readonly ILogger _logger; + private readonly ApplicationConfiguration _appConfig; - public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger) + public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, ApplicationConfiguration appConfig) { _customEventRegistrations = new Dictionary)>(); _logger = logger; + _appConfig = appConfig; Configuration = new DynamicEventParserConfiguration(parserRegexFactory) { @@ -128,7 +131,7 @@ namespace IW4MAdmin.Application.EventParsers int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]); // todo: these need to defined outside of here - if (message[0] == '!' || message[0] == '@') + if (message.StartsWith(_appConfig.CommandPrefix) || message.StartsWith(_appConfig.BroadcastCommandPrefix)) { return new GameEvent() { diff --git a/Application/EventParsers/DynamicEventParser.cs b/Application/EventParsers/DynamicEventParser.cs index fff3ee561..17022320a 100644 --- a/Application/EventParsers/DynamicEventParser.cs +++ b/Application/EventParsers/DynamicEventParser.cs @@ -1,4 +1,5 @@ -using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Interfaces; namespace IW4MAdmin.Application.EventParsers { @@ -8,7 +9,7 @@ namespace IW4MAdmin.Application.EventParsers /// sealed internal class DynamicEventParser : BaseEventParser { - public DynamicEventParser(IParserRegexFactory parserRegexFactory, ILogger logger) : base(parserRegexFactory, logger) + public DynamicEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, ApplicationConfiguration appConfig) : base(parserRegexFactory, logger, appConfig) { } } diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index 702590445..2c06f07f7 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -139,7 +139,7 @@ namespace IW4MAdmin { try { - C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E); + C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E, Manager.GetApplicationSettings().Configuration()); } catch (CommandException e) diff --git a/SharedLibraryCore/Command.cs b/SharedLibraryCore/Command.cs index ec8bd4a72..fbe2cf300 100644 --- a/SharedLibraryCore/Command.cs +++ b/SharedLibraryCore/Command.cs @@ -59,7 +59,7 @@ namespace SharedLibraryCore /// /// Helper property to provide the syntax of the command /// - public string Syntax => $"{_translationLookup["COMMAND_HELP_SYNTAX"]} !{Alias} {string.Join(" ", Arguments.Select(a => $"<{(a.Required ? "" : _translationLookup["COMMAND_HELP_OPTIONAL"] + " ")}{a.Name}>"))}"; + public string Syntax => $"{_translationLookup["COMMAND_HELP_SYNTAX"]} {_config.CommandPrefix}{Alias} {string.Join(" ", Arguments.Select(a => $"<{(a.Required ? "" : _translationLookup["COMMAND_HELP_OPTIONAL"] + " ")}{a.Name}>"))}"; /// /// Alternate name for this command to be executed by @@ -123,5 +123,7 @@ namespace SharedLibraryCore /// indicates if this command allows impersonation (run as) /// public bool AllowImpersonation { get; set; } + + public bool IsBroadcast { get; set; } } } diff --git a/SharedLibraryCore/Commands/CommandProcessing.cs b/SharedLibraryCore/Commands/CommandProcessing.cs index 5f62331d1..0e021162e 100644 --- a/SharedLibraryCore/Commands/CommandProcessing.cs +++ b/SharedLibraryCore/Commands/CommandProcessing.cs @@ -1,4 +1,5 @@ -using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Database.Models; using SharedLibraryCore.Exceptions; using System; using System.Collections.Generic; @@ -10,12 +11,14 @@ namespace SharedLibraryCore.Commands { public class CommandProcessing { - public static async Task ValidateCommand(GameEvent E) + public static async Task ValidateCommand(GameEvent E, ApplicationConfiguration appConfig) { var loc = Utilities.CurrentLocalization.LocalizationIndex; var Manager = E.Owner.Manager; + bool isBroadcast = E.Data.StartsWith(appConfig.BroadcastCommandPrefix); + int prefixLength = isBroadcast ? appConfig.BroadcastCommandPrefix.Length : appConfig.CommandPrefix.Length; - string CommandString = E.Data.Substring(1, E.Data.Length - 1).Split(' ')[0]; + string CommandString = E.Data.Substring(prefixLength, E.Data.Length - prefixLength).Split(' ')[0]; E.Message = E.Data; Command C = null; @@ -34,6 +37,8 @@ namespace SharedLibraryCore.Commands throw new CommandException($"{E.Origin} entered unknown command \"{CommandString}\""); } + C.IsBroadcast = isBroadcast; + if (!C.AllowImpersonation && E.ImpersonationOrigin != null) { E.ImpersonationOrigin.Tell(loc["COMMANDS_RUN_AS_FAIL"]); diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index 333260e47..e8cc81271 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -773,6 +773,8 @@ namespace SharedLibraryCore.Commands /// public class ListAdminsCommand : Command { + private readonly CommandConfiguration _config; + public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup) { Name = "admins"; @@ -780,6 +782,8 @@ namespace SharedLibraryCore.Commands Alias = "a"; Permission = Permission.User; RequiresTarget = false; + + _config = config; } public static string OnlineAdmins(Server S, ITranslationLookup lookup) @@ -798,7 +802,7 @@ namespace SharedLibraryCore.Commands { foreach (string line in OnlineAdmins(E.Owner, _translationLookup).Split(Environment.NewLine)) { - var _ = E.Message.IsBroadcastCommand() ? E.Owner.Broadcast(line) : E.Origin.Tell(line); + var _ = E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix) ? E.Owner.Broadcast(line) : E.Origin.Tell(line); } return Task.CompletedTask; @@ -903,6 +907,8 @@ namespace SharedLibraryCore.Commands /// public class ListRulesCommands : Command { + private readonly CommandConfiguration _config; + public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup) { Name = "rules"; @@ -910,6 +916,8 @@ namespace SharedLibraryCore.Commands Alias = "r"; Permission = Permission.User; RequiresTarget = false; + + _config = config; } public override Task ExecuteAsync(GameEvent E) @@ -917,7 +925,7 @@ namespace SharedLibraryCore.Commands if (E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules?.Length < 1 && E.Owner.ServerConfig.Rules?.Length < 1) { - var _ = E.Message.IsBroadcastCommand() ? + var _ = E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix) ? E.Owner.Broadcast(_translationLookup["COMMANDS_RULES_NONE"]) : E.Origin.Tell(_translationLookup["COMMANDS_RULES_NONE"]); } @@ -933,7 +941,7 @@ namespace SharedLibraryCore.Commands foreach (string r in rules) { - var _ = E.Message.IsBroadcastCommand() ? E.Owner.Broadcast($"- {r}") : E.Origin.Tell($"- {r}"); + var _ = E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix) ? E.Owner.Broadcast($"- {r}") : E.Origin.Tell($"- {r}"); } } diff --git a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs index 283777d4d..debf7c1f2 100644 --- a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs +++ b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs @@ -66,6 +66,12 @@ namespace SharedLibraryCore.Configuration [LocalizedDisplayName("WEBFRONT_CONFIGURATION_CUSTOM_LOCALE")] public string CustomLocale { get; set; } + [LocalizedDisplayName("WEBFRONT_CONFIGURATION_COMMAND_PREFIX")] + public string CommandPrefix { get; set; } = "!"; + + [LocalizedDisplayName("WEBFRONT_CONFIGURATION_BROADCAST_COMMAND_PREFIX")] + public string BroadcastCommandPrefix { get; set; } = "@"; + [LocalizedDisplayName("WEBFRONT_CONFIGURATION_DB_PROVIDER")] public string DatabaseProvider { get; set; } = "sqlite"; [ConfigurationOptional] diff --git a/SharedLibraryCore/Configuration/CommandConfiguration.cs b/SharedLibraryCore/Configuration/CommandConfiguration.cs index f8c5fa5e1..508c66224 100644 --- a/SharedLibraryCore/Configuration/CommandConfiguration.cs +++ b/SharedLibraryCore/Configuration/CommandConfiguration.cs @@ -1,6 +1,7 @@ using SharedLibraryCore.Interfaces; using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace SharedLibraryCore.Configuration { @@ -14,6 +15,18 @@ namespace SharedLibraryCore.Configuration /// public Dictionary Commands { get; set; } = new Dictionary(); + /// + /// prefix indicated the chat message is a command + /// + [JsonIgnore] + public string CommandPrefix { get; set; } + + /// + /// prefix indicating that the chat message is a broadcast command + /// + [JsonIgnore] + public string BroadcastCommandPrefix { get; set; } + public IBaseConfiguration Generate() { throw new NotImplementedException(); diff --git a/SharedLibraryCore/Configuration/Validation/ApplicationConfigurationValidator.cs b/SharedLibraryCore/Configuration/Validation/ApplicationConfigurationValidator.cs index 5238035d0..69f7e46c4 100644 --- a/SharedLibraryCore/Configuration/Validation/ApplicationConfigurationValidator.cs +++ b/SharedLibraryCore/Configuration/Validation/ApplicationConfigurationValidator.cs @@ -64,13 +64,19 @@ namespace SharedLibraryCore.Configuration.Validation RuleFor(_app => _app.GlobalRules) .NotNull(); - RuleForEach(_app => _app.Servers) - .NotEmpty() - .SetValidator(new ServerConfigurationValidator()); - RuleFor(_app => _app.MasterUrl) .NotNull() .Must(_url => _url != null && _url.Scheme == Uri.UriSchemeHttp); + + RuleFor(_app => _app.CommandPrefix) + .NotEmpty(); + + RuleFor(_app => _app.BroadcastCommandPrefix) + .NotEmpty(); + + RuleForEach(_app => _app.Servers) + .NotEmpty() + .SetValidator(new ServerConfigurationValidator()); } } } diff --git a/SharedLibraryCore/Interfaces/IManagerCommand.cs b/SharedLibraryCore/Interfaces/IManagerCommand.cs index e0d35fbea..88743e36c 100644 --- a/SharedLibraryCore/Interfaces/IManagerCommand.cs +++ b/SharedLibraryCore/Interfaces/IManagerCommand.cs @@ -49,5 +49,10 @@ namespace SharedLibraryCore.Interfaces /// Indicates if the commands can be run as another client /// bool AllowImpersonation { get; } + + /// + /// Indicates if the command result should be broadcasted to all clients + /// + bool IsBroadcast { get; set; } } } diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs index c39656a82..1990160f5 100644 --- a/SharedLibraryCore/PartialEntities/EFClient.cs +++ b/SharedLibraryCore/PartialEntities/EFClient.cs @@ -486,7 +486,7 @@ namespace SharedLibraryCore.Database.Models .DisallowedClientNames ?.Any(_name => Regex.IsMatch(Name, _name)) ?? false) { - CurrentServer.Logger.WriteDebug($"Kicking {this} because their name is generic"); + CurrentServer.Logger.WriteDebug($"Kicking {this} because their name is not allowed"); Kick(loc["SERVER_KICK_GENERICNAME"], Utilities.IW4MAdminClient(CurrentServer)); return false; } diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 534b82bf6..427a4836c 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -235,9 +235,14 @@ namespace SharedLibraryCore return str; } - public static bool IsBroadcastCommand(this string str) + public static bool IsBroadcastCommand(this string str, string broadcastCommandPrefix) { - return str[0] == '@'; + return str.StartsWith(broadcastCommandPrefix); + } + + public static IManagerCommand AsCommand(this GameEvent gameEvent) + { + return gameEvent.Extra as IManagerCommand; } /// diff --git a/Tests/ApplicationTests/ApplicationTests.csproj b/Tests/ApplicationTests/ApplicationTests.csproj index 343f58ecd..13eae0ddf 100644 --- a/Tests/ApplicationTests/ApplicationTests.csproj +++ b/Tests/ApplicationTests/ApplicationTests.csproj @@ -22,6 +22,9 @@ + + PreserveNewest + PreserveNewest diff --git a/Tests/ApplicationTests/BaseEventParserTests.cs b/Tests/ApplicationTests/BaseEventParserTests.cs index fd8eb35bc..37f8f06e9 100644 --- a/Tests/ApplicationTests/BaseEventParserTests.cs +++ b/Tests/ApplicationTests/BaseEventParserTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using NUnit.Framework; using SharedLibraryCore; +using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; using System; using static SharedLibraryCore.GameEvent; @@ -18,17 +19,20 @@ namespace ApplicationTests private EventLogTest eventLogData; private IServiceProvider serviceProvider; private ILogger fakeLogger; + private ApplicationConfiguration appConfig; [SetUp] public void Setup() { eventLogData = JsonConvert.DeserializeObject(System.IO.File.ReadAllText("Files/GameEvents.json")); + appConfig = ConfigurationGenerators.CreateApplicationConfiguration(); fakeLogger = A.Fake(); serviceProvider = new ServiceCollection() .AddSingleton() .AddTransient() .AddSingleton() + .AddSingleton(appConfig) .AddSingleton(fakeLogger) .BuildServiceProvider(); } @@ -38,22 +42,6 @@ namespace ApplicationTests { var eventParser = serviceProvider.GetService(); - void AssertMatch(GameEvent src, LogEvent expected) - { - Assert.AreEqual(expected.ExpectedEventType, src.Type); - Assert.AreEqual(expected.ExpectedData, src.Data); - Assert.AreEqual(expected.ExpectedMessage, src.Message); - Assert.AreEqual(expected.ExpectedTime, src.GameTime); - - //Assert.AreEqual(expected.ExpectedOriginClientName, src.Origin?.Name); - Assert.AreEqual(expected.ExpectedOriginClientNumber, src.Origin?.ClientNumber); - Assert.AreEqual(expected.ExpectedOriginNetworkId, src.Origin?.NetworkId.ToString("X")); - - //Assert.AreEqual(expected.ExpectedTargetClientName, src.Target?.Name); - Assert.AreEqual(expected.ExpectedTargetClientNumber, src.Target?.ClientNumber); - Assert.AreEqual(expected.ExpectedTargetNetworkId, src.Target?.NetworkId.ToString("X")); - } - foreach (var e in eventLogData.Events) { var parsedEvent = eventParser.GenerateGameEvent(e.EventLine); @@ -109,5 +97,45 @@ namespace ApplicationTests A.CallTo(() => fakeLogger.WriteWarning(A.Ignored)) .MustHaveHappenedOnceExactly(); } + + [Test] + public void Test_CustomCommandPrefix_Parses() + { + var eventParser = serviceProvider.GetService(); + var commandData = JsonConvert.DeserializeObject(System.IO.File.ReadAllText("Files/GameEvent.Command.CustomPrefix.json")); + appConfig.CommandPrefix = "^^"; + + var e = commandData.Events[0]; + var parsedEvent = eventParser.GenerateGameEvent(e.EventLine); + AssertMatch(parsedEvent, e); + } + + [Test] + public void Test_CustomBroadcastCommandPrefix_Parses() + { + var eventParser = serviceProvider.GetService(); + var commandData = JsonConvert.DeserializeObject(System.IO.File.ReadAllText("Files/GameEvent.Command.CustomPrefix.json")); + appConfig.BroadcastCommandPrefix = "@@"; + + var e = commandData.Events[1]; + var parsedEvent = eventParser.GenerateGameEvent(e.EventLine); + AssertMatch(parsedEvent, e); + } + + private static void AssertMatch(GameEvent src, LogEvent expected) + { + Assert.AreEqual(expected.ExpectedEventType, src.Type); + Assert.AreEqual(expected.ExpectedData, src.Data); + Assert.AreEqual(expected.ExpectedMessage, src.Message); + Assert.AreEqual(expected.ExpectedTime, src.GameTime); + + //Assert.AreEqual(expected.ExpectedOriginClientName, src.Origin?.Name); + Assert.AreEqual(expected.ExpectedOriginClientNumber, src.Origin?.ClientNumber); + Assert.AreEqual(expected.ExpectedOriginNetworkId, src.Origin?.NetworkId.ToString("X")); + + //Assert.AreEqual(expected.ExpectedTargetClientName, src.Target?.Name); + Assert.AreEqual(expected.ExpectedTargetClientNumber, src.Target?.ClientNumber); + Assert.AreEqual(expected.ExpectedTargetNetworkId, src.Target?.NetworkId.ToString("X")); + } } } diff --git a/Tests/ApplicationTests/CommandTests.cs b/Tests/ApplicationTests/CommandTests.cs index f766eb292..d9c9fccbb 100644 --- a/Tests/ApplicationTests/CommandTests.cs +++ b/Tests/ApplicationTests/CommandTests.cs @@ -57,7 +57,8 @@ namespace ApplicationTests .Returns(new Command[] { new ImpersonatableCommand(cmdConfig, transLookup), - new NonImpersonatableCommand(cmdConfig, transLookup) + new NonImpersonatableCommand(cmdConfig, transLookup), + new MockCommand(cmdConfig, transLookup) }); A.CallTo(() => manager.AddEvent(A.Ignored)) @@ -519,5 +520,22 @@ namespace ApplicationTests Assert.IsNotEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.ChangePermission && !_event.Failed)); } #endregion + + #region PREFIX_PROCESSING + [Test] + public async Task Test_CommandProcessing_IsBroadcastCommand() + { + string broadcastPrefix = "@@"; + var config = ConfigurationGenerators.CreateApplicationConfiguration(); + config.BroadcastCommandPrefix = broadcastPrefix; + var server = serviceProvider.GetRequiredService(); + + var cmd = EventGenerators.GenerateEvent(GameEvent.EventType.Command, $"{broadcastPrefix}{nameof(MockCommand)}", server); + + var result = await CommandProcessing.ValidateCommand(cmd, config); + Assert.AreEqual(nameof(MockCommand), result.Name); + Assert.IsTrue(result.IsBroadcast); + } + #endregion } } diff --git a/Tests/ApplicationTests/Files/GameEvent.Command.CustomPrefix.json b/Tests/ApplicationTests/Files/GameEvent.Command.CustomPrefix.json new file mode 100644 index 000000000..585f20bc0 --- /dev/null +++ b/Tests/ApplicationTests/Files/GameEvent.Command.CustomPrefix.json @@ -0,0 +1,32 @@ +{ + "Events": [ + { + "Game": "IW4", + "EventLine": " 12:34 say;AAA;1;Player;^^help", + "ExpectedEventType": "Command", + "ExpectedData": "^^help", + "ExpectedMessage": "^^help", + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 1, + "ExpectedOriginClientName": "Player", + "ExpectedTargetNetworkId": null, + "ExpectedTargetClientNumber": null, + "ExpectedTargetClientName": null, + "ExpectedTime": 754 + }, + { + "Game": "IW4", + "EventLine": " 12:34 say;AAA;1;Player;@@help", + "ExpectedEventType": "Command", + "ExpectedData": "@@help", + "ExpectedMessage": "@@help", + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 1, + "ExpectedOriginClientName": "Player", + "ExpectedTargetNetworkId": null, + "ExpectedTargetClientNumber": null, + "ExpectedTargetClientName": null, + "ExpectedTime": 754 + } + ] +} \ No newline at end of file diff --git a/Tests/ApplicationTests/Files/GameEvents.json b/Tests/ApplicationTests/Files/GameEvents.json new file mode 100644 index 000000000..ba3cfdfd2 --- /dev/null +++ b/Tests/ApplicationTests/Files/GameEvents.json @@ -0,0 +1,159 @@ +{ + "Events": [ + { + "Game": "IW4", + "EventLine": " 12:34 say;AAA;1;Player;this is a test message", + "ExpectedEventType": "Say", + "ExpectedData": "this is a test message", + "ExpectedMessage": "this is a test message", + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 1, + "ExpectedOriginClientName": "Player", + "ExpectedTargetNetworkId": null, + "ExpectedTargetClientNumber": null, + "ExpectedTargetClientName": null, + "ExpectedTime": 754 + }, + { + "Game": "IW4", + "EventLine": " 12:34 sayteam;AAA;1;Player;this is a test team message", + "ExpectedEventType": "Say", + "ExpectedData": "this is a test team message", + "ExpectedMessage": "this is a test team message", + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 1, + "ExpectedOriginClientName": "Player", + "ExpectedTargetNetworkId": null, + "ExpectedTargetClientNumber": null, + "ExpectedTargetClientName": null, + "ExpectedTime": 754 + }, + { + "Game": "IW4", + "EventLine": " 12:34 say;AAA;1;Player;!help", + "ExpectedEventType": "Command", + "ExpectedData": "!help", + "ExpectedMessage": "!help", + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 1, + "ExpectedOriginClientName": "Player", + "ExpectedTargetNetworkId": null, + "ExpectedTargetClientNumber": null, + "ExpectedTargetClientName": null, + "ExpectedTime": 754 + }, + { + "Game": "IW4", + "EventLine": "2824:03 K;BBB;6;axis;Snake;AAA;11;allies;Doctor;sa80_reflex_xmags_mp;55;MOD_HEAD_SHOT;head", + "ExpectedEventType": "Kill", + "ExpectedData": "K;BBB;6;axis;Snake;AAA;11;allies;Doctor;sa80_reflex_xmags_mp;55;MOD_HEAD_SHOT;head", + "ExpectedMessage": null, + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 11, + "ExpectedOriginClientName": "Doctor", + "ExpectedTargetNetworkId": "BBB", + "ExpectedTargetClientNumber": 6, + "ExpectedTargetClientName": "Snake", + "ExpectedTime": 169443 + }, + { + "Game": "IW4", + "EventLine": "6:44 K;AAA;0;allies;Player;;-1;world;;none;100;MOD_FALLING;none", + "ExpectedEventType": "Kill", + "ExpectedData": "K;AAA;0;allies;Player;;-1;world;;none;100;MOD_FALLING;none", + "ExpectedMessage": null, + "ExpectedOriginNetworkId": "FFFFFFFFFFFFFFFF", + "ExpectedOriginClientNumber": -1, + "ExpectedOriginClientName": "", + "ExpectedTargetNetworkId": "AAA", + "ExpectedTargetClientNumber": 0, + "ExpectedTargetClientName": "Player", + "ExpectedTime": 404 + }, + { + "Game": "IW4", + "EventLine": "2824:03 D;BBB;6;axis;Snake;AAA;11;allies;Doctor;sa80_reflex_xmags_mp;55;MOD_HEAD_SHOT;head", + "ExpectedEventType": "Damage", + "ExpectedData": "D;BBB;6;axis;Snake;AAA;11;allies;Doctor;sa80_reflex_xmags_mp;55;MOD_HEAD_SHOT;head", + "ExpectedMessage": null, + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 11, + "ExpectedOriginClientName": "Doctor", + "ExpectedTargetNetworkId": "BBB", + "ExpectedTargetClientNumber": 6, + "ExpectedTargetClientName": "Snake", + "ExpectedTime": 169443 + }, + { + "Game": "IW4", + "EventLine": "1:43 J;AAA;1;Doctor", + "ExpectedEventType": "Preconnect", + "ExpectedData": "J;AAA;1;Doctor", + "ExpectedMessage": null, + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 1, + "ExpectedOriginClientName": "Doctor", + "ExpectedTargetNetworkId": null, + "ExpectedTargetClientNumber": null, + "ExpectedTargetClientName": null, + "ExpectedTime": 103 + }, + { + "Game": "IW4", + "EventLine": "1344:54 Q;AAA;1;Cosmic Riptide", + "ExpectedEventType": "Predisconnect", + "ExpectedData": "Q;AAA;1;Cosmic Riptide", + "ExpectedMessage": null, + "ExpectedOriginNetworkId": "AAA", + "ExpectedOriginClientNumber": 1, + "ExpectedOriginClientName": "Cosmic Riptide", + "ExpectedTargetNetworkId": null, + "ExpectedTargetClientNumber": null, + "ExpectedTargetClientName": null, + "ExpectedTime": 80694 + }, + { + "Game": "IW4", + "EventLine": "75:44 ExitLevel: executed", + "ExpectedEventType": "MapEnd", + "ExpectedData": "ExitLevel: executed", + "ExpectedMessage": null, + "ExpectedOriginNetworkId": 0, + "ExpectedOriginClientNumber": -1, + "ExpectedOriginClientName": null, + "ExpectedTargetNetworkId": 0, + "ExpectedTargetClientNumber": -1, + "ExpectedTargetClientName": null, + "ExpectedTime": 4544 + }, + { + "Game": "IW4", + "EventLine": " 75:44 InitGame", + "ExpectedEventType": "MapChange", + "ExpectedData": "InitGame", + "ExpectedMessage": null, + "ExpectedOriginNetworkId": 0, + "ExpectedOriginClientNumber": -1, + "ExpectedOriginClientName": null, + "ExpectedTargetNetworkId": 0, + "ExpectedTargetClientNumber": -1, + "ExpectedTargetClientName": null, + "ExpectedTime": 4544 + }, + { + "Game": "IW4", + "EventLine": " 0:00 CustomLogLine", + "ExpectedEventType": "Unknown", + "ExpectedData": "CustomLogLine", + "ExpectedMessage": null, + "ExpectedOriginNetworkId": 0, + "ExpectedOriginClientNumber": -1, + "ExpectedOriginClientName": null, + "ExpectedTargetNetworkId": 0, + "ExpectedTargetClientNumber": -1, + "ExpectedTargetClientName": null, + "ExpectedTime": 0 + } + ] +} + \ No newline at end of file diff --git a/Tests/ApplicationTests/Fixtures/EventGenerators.cs b/Tests/ApplicationTests/Fixtures/EventGenerators.cs new file mode 100644 index 000000000..6ae09a004 --- /dev/null +++ b/Tests/ApplicationTests/Fixtures/EventGenerators.cs @@ -0,0 +1,26 @@ +using SharedLibraryCore; +using System; + + +namespace ApplicationTests.Fixtures +{ + static class EventGenerators + { + public static GameEvent GenerateEvent(GameEvent.EventType type, string data, Server owner) + { + switch (type) + { + case GameEvent.EventType.Command: + return new GameEvent + { + Origin = ClientGenerators.CreateDatabaseClient(), + Data = data, + Message = data, + Owner = owner + }; + default: + throw new NotImplementedException(); + } + } + } +} diff --git a/Tests/ApplicationTests/Mocks/Commands.cs b/Tests/ApplicationTests/Mocks/Commands.cs index be6d0e2ba..1ac1d58c4 100644 --- a/Tests/ApplicationTests/Mocks/Commands.cs +++ b/Tests/ApplicationTests/Mocks/Commands.cs @@ -1,7 +1,6 @@ using SharedLibraryCore; using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; -using System; using System.Threading.Tasks; namespace ApplicationTests.Mocks @@ -33,4 +32,17 @@ namespace ApplicationTests.Mocks return Task.CompletedTask; } } + + class MockCommand : Command + { + public MockCommand(CommandConfiguration config, ITranslationLookup lookup) : base(config, lookup) + { + Name = nameof(MockCommand); + } + + public override Task ExecuteAsync(GameEvent E) + { + return Task.CompletedTask; + } + } } diff --git a/Tests/ApplicationTests/ServerTests.cs b/Tests/ApplicationTests/ServerTests.cs index 70dfb0c23..dff1ae17c 100644 --- a/Tests/ApplicationTests/ServerTests.cs +++ b/Tests/ApplicationTests/ServerTests.cs @@ -3,6 +3,7 @@ using IW4MAdmin; using IW4MAdmin.Application; using IW4MAdmin.Application.EventParsers; using NUnit.Framework; +using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; using System; using System.Diagnostics; @@ -35,7 +36,7 @@ namespace ApplicationTests new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, A.Fake(), A.Fake(), A.Fake()); - var parser = new BaseEventParser(A.Fake(), A.Fake()); + var parser = new BaseEventParser(A.Fake(), A.Fake(), A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; var log = System.IO.File.ReadAllLines("Files\\T6MapRotation.log"); @@ -61,7 +62,7 @@ namespace ApplicationTests new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, A.Fake(), A.Fake(), A.Fake()); - var parser = new BaseEventParser(A.Fake(), A.Fake()); + var parser = new BaseEventParser(A.Fake(), A.Fake(), A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; var log = System.IO.File.ReadAllLines("Files\\T6Game.log"); diff --git a/Tests/ApplicationTests/StatsTests.cs b/Tests/ApplicationTests/StatsTests.cs index f37584e8b..5f611c21e 100644 --- a/Tests/ApplicationTests/StatsTests.cs +++ b/Tests/ApplicationTests/StatsTests.cs @@ -16,6 +16,7 @@ using ApplicationTests.Fixtures; using System.Threading.Tasks; using Stats.Helpers; using Stats.Dtos; +using SharedLibraryCore.Configuration; namespace ApplicationTests { @@ -73,7 +74,7 @@ namespace ApplicationTests A.Fake(), A.Fake(), A.Fake()); - var parser = new BaseEventParser(A.Fake(), A.Fake()); + var parser = new BaseEventParser(A.Fake(), A.Fake(), A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; var log = System.IO.File.ReadAllLines("Files\\T6GameStats.log"); diff --git a/WebfrontCore/Controllers/ActionController.cs b/WebfrontCore/Controllers/ActionController.cs index 9d70d3e2a..b9127dc54 100644 --- a/WebfrontCore/Controllers/ActionController.cs +++ b/WebfrontCore/Controllers/ActionController.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; +using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; using WebfrontCore.ViewModels; using static SharedLibraryCore.Database.Models.EFClient; @@ -13,9 +14,11 @@ namespace WebfrontCore.Controllers { public class ActionController : BaseController { + private readonly ApplicationConfiguration _appConfig; + public ActionController(IManager manager) : base(manager) { - + _appConfig = manager.GetApplicationSettings().Configuration(); } public IActionResult BanForm() @@ -80,8 +83,8 @@ namespace WebfrontCore.Controllers } string command = Duration == 6 ? - $"!ban @{targetId} {Reason}" : - $"!tempban @{targetId} {duration} {Reason}"; + $"{_appConfig.CommandPrefix}ban @{targetId} {Reason}" : + $"{_appConfig.CommandPrefix}tempban @{targetId} {duration} {Reason}"; var server = Manager.GetServers().First(); @@ -120,7 +123,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { serverId = server.EndPoint, - command = $"!unban @{targetId} {Reason}" + command = $"{_appConfig.CommandPrefix}unban @{targetId} {Reason}" })); } @@ -189,7 +192,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { serverId = server.EndPoint, - command = $"!setlevel @{targetId} {level}" + command = $"{_appConfig.CommandPrefix}setlevel @{targetId} {level}" })); } @@ -256,7 +259,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { serverId = server.EndPoint, - command = $"!say {message}" + command = $"{_appConfig.CommandPrefix}say {message}" })); } @@ -294,7 +297,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { serverId = server.EndPoint, - command = $"!flag @{targetId} {reason}" + command = $"{_appConfig.CommandPrefix}flag @{targetId} {reason}" })); } @@ -326,7 +329,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { serverId = server.EndPoint, - command = $"!unflag @{targetId} {reason}" + command = $"{_appConfig.CommandPrefix}unflag @{targetId} {reason}" })); } @@ -369,7 +372,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { serverId = client.CurrentServer.EndPoint, - command = $"!kick {client.ClientNumber} {reason}" + command = $"{_appConfig.CommandPrefix}kick {client.ClientNumber} {reason}" })); } } diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index 50b6c08eb..e3e8f5b99 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; +using SharedLibraryCore.Configuration; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos; using SharedLibraryCore.Interfaces; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -11,9 +11,11 @@ namespace WebfrontCore.Controllers { public class ConsoleController : BaseController { + private readonly ApplicationConfiguration _appconfig; + public ConsoleController(IManager manager) : base(manager) { - + _appconfig = manager.GetApplicationSettings().Configuration(); } public IActionResult Index() @@ -50,7 +52,8 @@ namespace WebfrontCore.Controllers var remoteEvent = new GameEvent() { Type = GameEvent.EventType.Command, - Data = command, + Data = command.StartsWith(_appconfig.CommandPrefix) || command.StartsWith(_appconfig.BroadcastCommandPrefix) ? + command : $"{_appconfig.CommandPrefix}{command}", Origin = client, Owner = server, IsRemote = true @@ -63,7 +66,7 @@ namespace WebfrontCore.Controllers { // wait for the event to process var completedEvent = await remoteEvent.WaitAsync(Utilities.DefaultCommandTimeout, server.Manager.CancellationToken); - + if (completedEvent.FailReason == GameEvent.EventFailReason.Timeout) { response = new[] diff --git a/WebfrontCore/Controllers/HomeController.cs b/WebfrontCore/Controllers/HomeController.cs index d26cbdfff..77aa71ecb 100644 --- a/WebfrontCore/Controllers/HomeController.cs +++ b/WebfrontCore/Controllers/HomeController.cs @@ -61,6 +61,7 @@ namespace WebfrontCore.Controllers { ViewBag.IsFluid = true; ViewBag.Title = Localization["WEBFRONT_NAV_HELP"]; + ViewBag.CommandPrefix = Manager.GetApplicationSettings().Configuration().CommandPrefix; // we don't need to the name of the shared library assembly var excludedAssembly = typeof(BaseController).Assembly; diff --git a/WebfrontCore/Views/Home/Help.cshtml b/WebfrontCore/Views/Home/Help.cshtml index 4e8cfef59..0d91dd4dc 100644 --- a/WebfrontCore/Views/Home/Help.cshtml +++ b/WebfrontCore/Views/Home/Help.cshtml @@ -26,7 +26,7 @@ @command.Alias @command.Description @command.RequiresTarget - !@command.Syntax.Split('!')[1] + @ViewBag.CommandPrefix@command.Syntax.Split(@ViewBag.CommandPrefix)[1] @command.Permission.ToLocalizedLevelName() } @@ -62,7 +62,7 @@ @loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"] - !@command.Syntax.Split('!')[1] + @ViewBag.CommandPrefix@command.Syntax.Split(@ViewBag.CommandPrefix)[1] @loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"] diff --git a/WebfrontCore/wwwroot/js/console.js b/WebfrontCore/wwwroot/js/console.js index 5b13e9c08..79c7808bc 100644 --- a/WebfrontCore/wwwroot/js/console.js +++ b/WebfrontCore/wwwroot/js/console.js @@ -6,10 +6,6 @@ return false; } - if (command[0] !== '!') { - $('#console_command_response').text(_localization['WEBFRONT_CONSOLE_COMMAND']).addClass('text-danger'); - return false; - } showLoader(); $.get('/Console/ExecuteAsync', { serverId: serverId, command: command }) .done(function (response) {