Merge pull request #133 from RaidMax/feature/issue-132-script-command-registration
implement script plugin command registration - issue #132
This commit is contained in:
commit
420e0d5ab5
@ -62,12 +62,13 @@ namespace IW4MAdmin.Application
|
||||
private readonly IParserRegexFactory _parserRegexFactory;
|
||||
private readonly IEnumerable<IRegisterEvent> _customParserEvents;
|
||||
private readonly IEventHandler _eventHandler;
|
||||
private readonly IScriptCommandFactory _scriptCommandFactory;
|
||||
|
||||
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
|
||||
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
|
||||
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
|
||||
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
|
||||
IEventHandler eventHandler)
|
||||
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory)
|
||||
{
|
||||
MiddlewareActionHandler = actionHandler;
|
||||
_servers = new ConcurrentBag<Server>();
|
||||
@ -92,6 +93,7 @@ namespace IW4MAdmin.Application
|
||||
_parserRegexFactory = parserRegexFactory;
|
||||
_customParserEvents = customParserEvents;
|
||||
_eventHandler = eventHandler;
|
||||
_scriptCommandFactory = scriptCommandFactory;
|
||||
Plugins = plugins;
|
||||
}
|
||||
|
||||
@ -267,12 +269,12 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
if (plugin is ScriptPlugin scriptPlugin)
|
||||
{
|
||||
await scriptPlugin.Initialize(this);
|
||||
await scriptPlugin.Initialize(this, _scriptCommandFactory);
|
||||
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await scriptPlugin.Initialize(this);
|
||||
await scriptPlugin.Initialize(this, _scriptCommandFactory);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
@ -817,5 +819,17 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
_operationLookup.Add(operationName, operation);
|
||||
}
|
||||
|
||||
public void AddAdditionalCommand(IManagerCommand command)
|
||||
{
|
||||
if (_commands.Any(_command => _command.Name == command.Name || _command.Alias == command.Alias))
|
||||
{
|
||||
throw new InvalidOperationException($"Duplicate command name or alias ({command.Name}, {command.Alias})");
|
||||
}
|
||||
|
||||
_commands.Add(command);
|
||||
}
|
||||
|
||||
public void RemoveCommandByName(string commandName) => _commands.RemoveAll(_command => _command.Name == commandName);
|
||||
}
|
||||
}
|
||||
|
40
Application/Factories/ScriptCommandFactory.cs
Normal file
40
Application/Factories/ScriptCommandFactory.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static SharedLibraryCore.Database.Models.EFClient;
|
||||
|
||||
namespace IW4MAdmin.Application.Factories
|
||||
{
|
||||
/// <summary>
|
||||
/// implementation of IScriptCommandFactory
|
||||
/// </summary>
|
||||
public class ScriptCommandFactory : IScriptCommandFactory
|
||||
{
|
||||
private CommandConfiguration _config;
|
||||
private readonly ITranslationLookup _transLookup;
|
||||
|
||||
public ScriptCommandFactory(CommandConfiguration config, ITranslationLookup transLookup)
|
||||
{
|
||||
_config = config;
|
||||
_transLookup = transLookup;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
|
||||
{
|
||||
var permissionEnum = Enum.Parse<Permission>(permission);
|
||||
var argsArray = args.Select(_arg => new CommandArgument
|
||||
{
|
||||
Name = _arg.Item1,
|
||||
Required = _arg.Item2
|
||||
}).ToArray();
|
||||
|
||||
return new ScriptCommand(name, alias, description, permissionEnum, argsArray, executeAction, _config, _transLookup);
|
||||
}
|
||||
}
|
||||
}
|
@ -286,6 +286,7 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
|
||||
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
|
||||
.AddSingleton<IGameLogReaderFactory, GameLogReaderFactory>()
|
||||
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
|
||||
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
|
||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||
.AddSingleton(_serviceProvider =>
|
||||
|
41
Application/Misc/ScriptCommand.cs
Normal file
41
Application/Misc/ScriptCommand.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using static SharedLibraryCore.Database.Models.EFClient;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
/// <summary>
|
||||
/// generic script command implementation
|
||||
/// </summary>
|
||||
public class ScriptCommand : Command
|
||||
{
|
||||
private readonly Action<GameEvent> _executeAction;
|
||||
|
||||
public ScriptCommand(string name, string alias, string description, Permission permission,
|
||||
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout)
|
||||
: base(config, layout)
|
||||
{
|
||||
|
||||
_executeAction = executeAction;
|
||||
Name = name;
|
||||
Alias = alias;
|
||||
Description = description;
|
||||
Permission = permission;
|
||||
Arguments = args;
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
if (_executeAction == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No execute action defined for command \"{Name}\"");
|
||||
}
|
||||
|
||||
return Task.Run(() => _executeAction(E));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
using Jint;
|
||||
using Jint.Native;
|
||||
using Jint.Runtime;
|
||||
using Microsoft.CSharp.RuntimeBinder;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -34,6 +38,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
private readonly string _fileName;
|
||||
private readonly SemaphoreSlim _onProcessing;
|
||||
private bool successfullyLoaded;
|
||||
private readonly List<string> _registeredCommandNames;
|
||||
|
||||
public ScriptPlugin(string filename, string workingDirectory = null)
|
||||
{
|
||||
@ -47,6 +52,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
Watcher.EnableRaisingEvents = true;
|
||||
_onProcessing = new SemaphoreSlim(1, 1);
|
||||
_registeredCommandNames = new List<string>();
|
||||
}
|
||||
|
||||
~ScriptPlugin()
|
||||
@ -55,7 +61,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
_onProcessing.Dispose();
|
||||
}
|
||||
|
||||
public async Task Initialize(IManager manager)
|
||||
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory)
|
||||
{
|
||||
await _onProcessing.WaitAsync();
|
||||
|
||||
@ -75,6 +81,14 @@ namespace IW4MAdmin.Application.Misc
|
||||
if (!firstRun)
|
||||
{
|
||||
await OnUnloadAsync();
|
||||
|
||||
foreach (string commandName in _registeredCommandNames)
|
||||
{
|
||||
manager.GetLogger(0).WriteDebug($"Removing plugin registered command \"{commandName}\"");
|
||||
manager.RemoveCommandByName(commandName);
|
||||
}
|
||||
|
||||
_registeredCommandNames.Clear();
|
||||
}
|
||||
|
||||
successfullyLoaded = false;
|
||||
@ -106,6 +120,26 @@ namespace IW4MAdmin.Application.Misc
|
||||
Name = pluginObject.name;
|
||||
Version = (float)pluginObject.version;
|
||||
|
||||
var commands = _scriptEngine.GetValue("commands");
|
||||
|
||||
if (commands != JsValue.Undefined)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory))
|
||||
{
|
||||
manager.GetLogger(0).WriteDebug($"Adding plugin registered command \"{command.Name}\"");
|
||||
manager.AddAdditionalCommand(command);
|
||||
_registeredCommandNames.Add(command.Name);
|
||||
}
|
||||
}
|
||||
|
||||
catch (RuntimeBinderException e)
|
||||
{
|
||||
throw new PluginException($"Not all required fields were found: {e.Message}") { PluginFile = _fileName };
|
||||
}
|
||||
}
|
||||
|
||||
await OnLoadAsync(manager);
|
||||
|
||||
try
|
||||
@ -193,5 +227,67 @@ namespace IW4MAdmin.Application.Misc
|
||||
await Task.FromResult(_scriptEngine.Execute("plugin.onUnloadAsync()").GetCompletionValue());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// finds declared script commands in the script plugin
|
||||
/// </summary>
|
||||
/// <param name="commands">commands value from jint parser</param>
|
||||
/// <param name="scriptCommandFactory">factory to create the command from</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory)
|
||||
{
|
||||
List<IManagerCommand> commandList = new List<IManagerCommand>();
|
||||
|
||||
// go through each defined command
|
||||
foreach (var command in commands.AsArray())
|
||||
{
|
||||
dynamic dynamicCommand = command.ToObject();
|
||||
string name = dynamicCommand.name;
|
||||
string alias = dynamicCommand.alias;
|
||||
string description = dynamicCommand.description;
|
||||
string permission = dynamicCommand.permission;
|
||||
|
||||
List<(string, bool)> args = new List<(string, bool)>();
|
||||
dynamic arguments = null;
|
||||
|
||||
try
|
||||
{
|
||||
arguments = dynamicCommand.arguments;
|
||||
}
|
||||
|
||||
catch (RuntimeBinderException)
|
||||
{
|
||||
// arguments are optional
|
||||
}
|
||||
|
||||
if (arguments != null)
|
||||
{
|
||||
foreach (var arg in dynamicCommand.arguments)
|
||||
{
|
||||
args.Add((arg.name, (bool)arg.required));
|
||||
}
|
||||
}
|
||||
|
||||
void execute(GameEvent e)
|
||||
{
|
||||
_scriptEngine.SetValue("_event", e);
|
||||
var jsEventObject = _scriptEngine.GetValue("_event");
|
||||
|
||||
try
|
||||
{
|
||||
dynamicCommand.execute.Target.Invoke(jsEventObject);
|
||||
}
|
||||
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
throw new PluginException($"An error occured while executing action for script plugin: {ex.Error} (Line: {ex.Location.Start.Line}, Character: {ex.Location.Start.Column})") { PluginFile = _fileName };
|
||||
}
|
||||
}
|
||||
|
||||
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, args, execute));
|
||||
}
|
||||
|
||||
return commandList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
||||
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
|
||||
Plugins\ScriptPlugins\ParserT7.js = Plugins\ScriptPlugins\ParserT7.js
|
||||
Plugins\ScriptPlugins\ParserTeknoMW3.js = Plugins\ScriptPlugins\ParserTeknoMW3.js
|
||||
Plugins\ScriptPlugins\SampleScriptPluginCommand.js = Plugins\ScriptPlugins\SampleScriptPluginCommand.js
|
||||
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
|
||||
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
||||
EndProjectSection
|
||||
|
54
Plugins/ScriptPlugins/SampleScriptPluginCommand.js
Normal file
54
Plugins/ScriptPlugins/SampleScriptPluginCommand.js
Normal file
@ -0,0 +1,54 @@
|
||||
let commands = [{
|
||||
// required
|
||||
name: "pingpong",
|
||||
// required
|
||||
description: "pongs a ping",
|
||||
// required
|
||||
alias: "pp",
|
||||
// required
|
||||
permission: "User",
|
||||
// optional
|
||||
arguments: [{
|
||||
name: "times to ping",
|
||||
required: true
|
||||
}],
|
||||
// required
|
||||
execute: (gameEvent) => {
|
||||
// parse the first argument (number of times)
|
||||
let times = parseInt(gameEvent.Data);
|
||||
|
||||
// we only want to allow ping pong up to 5 times
|
||||
if (times > 5 || times <= 0) {
|
||||
gameEvent.Origin.Tell("You can only ping pong between 1 and 5 times");
|
||||
return;
|
||||
}
|
||||
|
||||
// we want to print out a pong message for the number of times they requested
|
||||
for (var i = 0; i < times; i++) {
|
||||
gameEvent.Origin.Tell(`^${i}pong #${i + 1}^7`);
|
||||
|
||||
// don't want to wait if it's the last pong
|
||||
if (i < times - 1) {
|
||||
System.Threading.Tasks.Task.Delay(1000).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
let plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.0,
|
||||
name: 'Ping Pong Sample Command Plugin',
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
11
SharedLibraryCore/Exceptions/PluginException.cs
Normal file
11
SharedLibraryCore/Exceptions/PluginException.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace SharedLibraryCore.Exceptions
|
||||
{
|
||||
public class PluginException : Exception
|
||||
{
|
||||
public PluginException(string message) : base(message) { }
|
||||
|
||||
public string PluginFile { get; set; }
|
||||
}
|
||||
}
|
@ -70,5 +70,15 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// </summary>
|
||||
/// <param name="gameEvent">event to be processed</param>
|
||||
void AddEvent(GameEvent gameEvent);
|
||||
/// <summary>
|
||||
/// adds an additional (script) command to the command list
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
void AddAdditionalCommand(IManagerCommand command);
|
||||
/// <summary>
|
||||
/// removes a command by its name
|
||||
/// </summary>
|
||||
/// <param name="name">name of command</param>
|
||||
void RemoveCommandByName(string name);
|
||||
}
|
||||
}
|
||||
|
23
SharedLibraryCore/Interfaces/IScriptCommandFactory.cs
Normal file
23
SharedLibraryCore/Interfaces/IScriptCommandFactory.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines capabilities of script command factory
|
||||
/// </summary>
|
||||
public interface IScriptCommandFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// generate a new script command from parsed source
|
||||
/// </summary>
|
||||
/// <param name="name">name of command</param>
|
||||
/// <param name="alias">alias of command</param>
|
||||
/// <param name="description">description of command</param>
|
||||
/// <param name="permission">minimum required permission</param>
|
||||
/// <param name="args">command arguments (name, is required)</param>
|
||||
/// <param name="executeAction">action to peform when commmand is executed</param>
|
||||
/// <returns></returns>
|
||||
IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction);
|
||||
}
|
||||
}
|
@ -12,8 +12,11 @@ namespace WebfrontCore.Controllers
|
||||
{
|
||||
public class HomeController : BaseController
|
||||
{
|
||||
public HomeController(IManager manager) : base(manager)
|
||||
private readonly ITranslationLookup _translationLookup;
|
||||
|
||||
public HomeController(IManager manager, ITranslationLookup translationLookup) : base(manager)
|
||||
{
|
||||
_translationLookup = translationLookup;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index(Game? game = null)
|
||||
@ -69,7 +72,8 @@ namespace WebfrontCore.Controllers
|
||||
// we need the plugin type the command is defined in
|
||||
var pluginType = _cmd.GetType().Assembly.GetTypes().FirstOrDefault(_type => _type.Assembly != excludedAssembly && typeof(IPlugin).IsAssignableFrom(_type));
|
||||
return pluginType == null ?
|
||||
Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_HELP_COMMAND_NATIVE"] :
|
||||
_translationLookup["WEBFRONT_HELP_COMMAND_NATIVE"] :
|
||||
pluginType.Name == "ScriptPlugin" ? _translationLookup["WEBFRONT_HELP_SCRIPT_PLUGIN"] :
|
||||
Manager.Plugins.First(_plugin => _plugin.GetType() == pluginType).Name; // for now we're just returning the name of the plugin, maybe later we'll include more info
|
||||
})
|
||||
.Select(_grp => (_grp.Key, _grp.AsEnumerable()));
|
||||
|
Loading…
Reference in New Issue
Block a user