diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index bad4832a9..135e02562 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -67,13 +67,14 @@ namespace IW4MAdmin.Application private readonly IEventHandler _eventHandler; private readonly IScriptCommandFactory _scriptCommandFactory; private readonly IMetaRegistration _metaRegistration; + private readonly IScriptPluginServiceResolver _scriptPluginServiceResolver; public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable commands, ITranslationLookup translationLookup, IConfigurationHandler commandConfiguration, IConfigurationHandler appConfigHandler, IGameServerInstanceFactory serverInstanceFactory, IEnumerable plugins, IParserRegexFactory parserRegexFactory, IEnumerable customParserEvents, IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService, - IMetaRegistration metaRegistration) + IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver) { MiddlewareActionHandler = actionHandler; _servers = new ConcurrentBag(); @@ -100,6 +101,7 @@ namespace IW4MAdmin.Application _eventHandler = eventHandler; _scriptCommandFactory = scriptCommandFactory; _metaRegistration = metaRegistration; + _scriptPluginServiceResolver = scriptPluginServiceResolver; Plugins = plugins; } @@ -277,12 +279,12 @@ namespace IW4MAdmin.Application { if (plugin is ScriptPlugin scriptPlugin) { - await scriptPlugin.Initialize(this, _scriptCommandFactory); + await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver); scriptPlugin.Watcher.Changed += async (sender, e) => { try { - await scriptPlugin.Initialize(this, _scriptCommandFactory); + await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver); } catch (Exception ex) @@ -449,7 +451,8 @@ namespace IW4MAdmin.Application Name = cmd.Name, Alias = cmd.Alias, MinimumPermission = cmd.Permission, - AllowImpersonation = cmd.AllowImpersonation + AllowImpersonation = cmd.AllowImpersonation, + SupportedGames = cmd.SupportedGames }); } diff --git a/Application/Main.cs b/Application/Main.cs index bb91e45c9..9cdce81e5 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -249,6 +249,7 @@ namespace IW4MAdmin.Application .AddSingleton, ClientService>() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton, ReceivedPenaltyResourceQueryHelper>() .AddSingleton, AdministeredPenaltyResourceQueryHelper>() .AddSingleton, UpdatedAliasResourceQueryHelper>() diff --git a/Application/Misc/ScriptPlugin.cs b/Application/Misc/ScriptPlugin.cs index 170edb44a..4a692d04f 100644 --- a/Application/Misc/ScriptPlugin.cs +++ b/Application/Misc/ScriptPlugin.cs @@ -61,7 +61,7 @@ namespace IW4MAdmin.Application.Misc _onProcessing.Dispose(); } - public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory) + public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory, IScriptPluginServiceResolver serviceResolver) { await _onProcessing.WaitAsync(); @@ -114,6 +114,7 @@ namespace IW4MAdmin.Application.Misc _scriptEngine.Execute(script); _scriptEngine.SetValue("_localization", Utilities.CurrentLocalization); + _scriptEngine.SetValue("_serviceResolver", serviceResolver); dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject(); Author = pluginObject.author; @@ -164,6 +165,11 @@ namespace IW4MAdmin.Application.Misc successfullyLoaded = true; } + catch (JavaScriptException ex) + { + throw new PluginException($"An error occured while initializing script plugin: {ex.Error} (Line: {ex.Location.Start.Line}, Character: {ex.Location.Start.Column})") { PluginFile = _fileName }; + } + catch { throw; diff --git a/Application/Misc/ScriptPluginServiceResolver.cs b/Application/Misc/ScriptPluginServiceResolver.cs new file mode 100644 index 000000000..f8f308ac7 --- /dev/null +++ b/Application/Misc/ScriptPluginServiceResolver.cs @@ -0,0 +1,31 @@ +using SharedLibraryCore.Interfaces; +using System; +using System.Linq; + +namespace IW4MAdmin.Application.Misc +{ + /// + /// implementation of IScriptPluginServiceResolver + /// + public class ScriptPluginServiceResolver : IScriptPluginServiceResolver + { + private readonly IServiceProvider _serviceProvider; + + public ScriptPluginServiceResolver(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public object ResolveService(string serviceName) + { + var serviceType = typeof(IScriptPluginServiceResolver).Assembly.GetTypes().FirstOrDefault(_type => _type.Name == serviceName); + + if (serviceType == null) + { + throw new InvalidOperationException($"No service type '{serviceName}' defined in IW4MAdmin assembly"); + } + + return _serviceProvider.GetService(serviceType); + } + } +} diff --git a/Plugins/ScriptPlugins/SampleScriptPluginCommand.js b/Plugins/ScriptPlugins/SampleScriptPluginCommand.js index 0b8ee4f05..b80724dad 100644 --- a/Plugins/ScriptPlugins/SampleScriptPluginCommand.js +++ b/Plugins/ScriptPlugins/SampleScriptPluginCommand.js @@ -44,6 +44,8 @@ let plugin = { }, onLoadAsync: function (manager) { + this.logger = _serviceResolver.ResolveService("ILogger"); + this.logger.WriteDebug("sample plugin loaded"); }, onUnloadAsync: function () { diff --git a/SharedLibraryCore/Command.cs b/SharedLibraryCore/Command.cs index fbe2cf300..2f0c5e5f4 100644 --- a/SharedLibraryCore/Command.cs +++ b/SharedLibraryCore/Command.cs @@ -5,6 +5,7 @@ using SharedLibraryCore.Commands; using SharedLibraryCore.Configuration; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Interfaces; +using static SharedLibraryCore.Server; namespace SharedLibraryCore { @@ -13,7 +14,7 @@ namespace SharedLibraryCore /// public abstract class Command : IManagerCommand { - private readonly CommandConfiguration _config; + protected readonly CommandConfiguration _config; protected readonly ITranslationLookup _translationLookup; protected ILogger logger; @@ -113,6 +114,25 @@ namespace SharedLibraryCore } private EFClient.Permission permission; + public Game[] SupportedGames + { + get => supportedGames; + protected set + { + try + { + var savedGames = _config?.Commands[GetType().Name].SupportedGames; + supportedGames = savedGames?.Length != 0 ? savedGames : value; + } + + catch (KeyNotFoundException) + { + supportedGames = value; + } + } + } + private Game[] supportedGames; + /// /// Argument list for the command diff --git a/SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs b/SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs new file mode 100644 index 000000000..95db8b216 --- /dev/null +++ b/SharedLibraryCore/Commands/PrivateMessageAdminsCommand.cs @@ -0,0 +1,37 @@ +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Interfaces; +using System; +using System.Linq; +using System.Threading.Tasks; +using static SharedLibraryCore.Server; + +namespace SharedLibraryCore.Commands +{ + public class PrivateMessageAdminsCommand : Command + { + public PrivateMessageAdminsCommand(CommandConfiguration config, ITranslationLookup lookup) : base(config, lookup) + { + Name = "privatemessageadmin"; + Description = lookup["COMMANDS_PMADMINS_DESC"]; + Alias = "pma"; + Permission = EFClient.Permission.Moderator; + SupportedGames = new[] { Game.IW4, Game.IW5 }; + } + + public override Task ExecuteAsync(GameEvent E) + { + bool isGameSupported = _config.Commands[nameof(PrivateMessageAdminsCommand)].SupportedGames.Length > 0 && + _config.Commands[nameof(PrivateMessageAdminsCommand)].SupportedGames.Contains(E.Owner.GameName); + + if (!isGameSupported) + { + E.Origin.Tell(_translationLookup["COMMANDS_GAME_NOT_SUPPORTED"].FormatExt(nameof(PrivateMessageAdminsCommand))); + return Task.CompletedTask; + } + + E.Owner.ToAdmins(E.Data); + return Task.CompletedTask; + } + } +} diff --git a/SharedLibraryCore/Configuration/CommandProperties.cs b/SharedLibraryCore/Configuration/CommandProperties.cs index 9eab8a9b7..88e52914d 100644 --- a/SharedLibraryCore/Configuration/CommandProperties.cs +++ b/SharedLibraryCore/Configuration/CommandProperties.cs @@ -1,6 +1,7 @@ -using Newtonsoft.Json.Converters; -using System.Text.Json.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using static SharedLibraryCore.Database.Models.EFClient; +using static SharedLibraryCore.Server; namespace SharedLibraryCore.Configuration { @@ -29,5 +30,11 @@ namespace SharedLibraryCore.Configuration /// Indicates if the command can be run by another user (impersonation) /// public bool AllowImpersonation { get; set; } + + /// + /// Specifies the games supporting the functionality of the command + /// + [JsonProperty(ItemConverterType = typeof(StringEnumConverter))] + public Game[] SupportedGames { get; set; } = new Game[0]; } } diff --git a/SharedLibraryCore/Interfaces/IManagerCommand.cs b/SharedLibraryCore/Interfaces/IManagerCommand.cs index 88743e36c..d9222bdab 100644 --- a/SharedLibraryCore/Interfaces/IManagerCommand.cs +++ b/SharedLibraryCore/Interfaces/IManagerCommand.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using static SharedLibraryCore.Database.Models.EFClient; +using static SharedLibraryCore.Server; namespace SharedLibraryCore.Interfaces { @@ -35,6 +36,11 @@ namespace SharedLibraryCore.Interfaces /// Permission Permission { get; } + /// + /// Games the command is supported on + /// + Game[] SupportedGames { get; } + /// /// Syntax for using the command /// diff --git a/SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs b/SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs new file mode 100644 index 000000000..08883074d --- /dev/null +++ b/SharedLibraryCore/Interfaces/IScriptPluginServiceResolver.cs @@ -0,0 +1,10 @@ +namespace SharedLibraryCore.Interfaces +{ + /// + /// interface used to dynamically resolve services by string name + /// + public interface IScriptPluginServiceResolver + { + object ResolveService(string serviceName); + } +} diff --git a/Tests/ApplicationTests/CommandTests.cs b/Tests/ApplicationTests/CommandTests.cs index d9c9fccbb..0576f924b 100644 --- a/Tests/ApplicationTests/CommandTests.cs +++ b/Tests/ApplicationTests/CommandTests.cs @@ -537,5 +537,46 @@ namespace ApplicationTests Assert.IsTrue(result.IsBroadcast); } #endregion + + #region PMADMINS + [Test] + public async Task Test_PrivateMessageAdmins_HappyPath() + { + var cmd = new PrivateMessageAdminsCommand(cmdConfig, transLookup); + var server = serviceProvider.GetRequiredService(); + var origin = ClientGenerators.CreateDatabaseClient(); + origin.Level = Permission.Administrator; + origin.CurrentServer = server; + var gameEvent = EventGenerators.GenerateEvent(GameEvent.EventType.Command, "", server); + cmdConfig.Commands.Add(nameof(PrivateMessageAdminsCommand), new CommandProperties { SupportedGames = new[] { server.GameName } }); + + server.Clients[0] = origin; + server.Clients[1] = origin; + await cmd.ExecuteAsync(gameEvent); + int expectedEvents = 2; + + Assert.AreEqual(expectedEvents, mockEventHandler.Events.Count(_event => _event.Type == GameEvent.EventType.Tell)); + } + + [Test] + public async Task Test_PrivateMessageAdmins_GameNotSupported() + { + var cmd = new PrivateMessageAdminsCommand(cmdConfig, transLookup); + var server = serviceProvider.GetRequiredService(); + var origin = ClientGenerators.CreateDatabaseClient(); + origin.Level = Permission.Administrator; + origin.CurrentServer = server; + var gameEvent = EventGenerators.GenerateEvent(GameEvent.EventType.Command, "", server); + gameEvent.Origin = origin; + cmdConfig.Commands.Add(nameof(PrivateMessageAdminsCommand), new CommandProperties()); + + server.Clients[0] = origin; + server.Clients[1] = origin; + await cmd.ExecuteAsync(gameEvent); + int expectedEvents = 1; + + Assert.AreEqual(expectedEvents, mockEventHandler.Events.Count(_event => _event.Type == GameEvent.EventType.Tell)); + } + #endregion } }