add configuration update callback for script plugins & update plugins to utilize

This commit is contained in:
RaidMax 2023-04-15 14:27:51 -05:00
parent c3be7f7de5
commit bb8f3fbe5b
7 changed files with 120 additions and 35 deletions

View File

@ -118,6 +118,8 @@ public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHand
} }
} }
public event Action<TConfigurationType> Updated;
private async Task InternalSet(TConfigurationType configuration, bool awaitSemaphore) private async Task InternalSet(TConfigurationType configuration, bool awaitSemaphore)
{ {
try try
@ -163,6 +165,7 @@ public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHand
else else
{ {
CopyUpdatedProperties(readConfiguration); CopyUpdatedProperties(readConfiguration);
Updated?.Invoke(readConfiguration);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@ -6,15 +7,20 @@ using System.Threading.Tasks;
using IW4MAdmin.Application.Configuration; using IW4MAdmin.Application.Configuration;
using Jint; using Jint;
using Jint.Native; using Jint.Native;
using Jint.Native.Json;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Plugin.Script; namespace IW4MAdmin.Application.Plugin.Script;
public class ScriptPluginConfigurationWrapper public class ScriptPluginConfigurationWrapper
{ {
public event Action<JsValue, Delegate> ConfigurationUpdated;
private readonly ScriptPluginConfiguration _config; private readonly ScriptPluginConfiguration _config;
private readonly IConfigurationHandlerV2<ScriptPluginConfiguration> _configHandler; private readonly IConfigurationHandlerV2<ScriptPluginConfiguration> _configHandler;
private readonly Engine _scriptEngine; private readonly Engine _scriptEngine;
private readonly JsonParser _engineParser;
private readonly List<(string, Delegate)> _updateCallbackActions = new();
private string _pluginName; private string _pluginName;
public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine, IConfigurationHandlerV2<ScriptPluginConfiguration> configHandler) public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine, IConfigurationHandlerV2<ScriptPluginConfiguration> configHandler)
@ -22,7 +28,14 @@ public class ScriptPluginConfigurationWrapper
_pluginName = pluginName; _pluginName = pluginName;
_scriptEngine = scriptEngine; _scriptEngine = scriptEngine;
_configHandler = configHandler; _configHandler = configHandler;
_configHandler.Updated += OnConfigurationUpdated;
_config = configHandler.Get("ScriptPluginSettings", new ScriptPluginConfiguration()).GetAwaiter().GetResult(); _config = configHandler.Get("ScriptPluginSettings", new ScriptPluginConfiguration()).GetAwaiter().GetResult();
_engineParser = new JsonParser(_scriptEngine);
}
~ScriptPluginConfigurationWrapper()
{
_configHandler.Updated -= OnConfigurationUpdated;
} }
public void SetName(string name) public void SetName(string name)
@ -64,7 +77,9 @@ public class ScriptPluginConfigurationWrapper
await _configHandler.Set(_config); await _configHandler.Set(_config);
} }
public JsValue GetValue(string key) public JsValue GetValue(string key) => GetValue(key, null);
public JsValue GetValue(string key, Delegate updateCallback)
{ {
if (!_config.ContainsKey(_pluginName)) if (!_config.ContainsKey(_pluginName))
{ {
@ -83,6 +98,20 @@ public class ScriptPluginConfigurationWrapper
item = jElem.Deserialize<List<dynamic>>(); item = jElem.Deserialize<List<dynamic>>();
} }
if (updateCallback is not null)
{
_updateCallbackActions.Add((key, updateCallback));
}
try
{
return _engineParser.Parse(item!.ToString()!);
}
catch
{
// ignored
}
return JsValue.FromObject(_scriptEngine, item); return JsValue.FromObject(_scriptEngine, item);
} }
@ -90,4 +119,12 @@ public class ScriptPluginConfigurationWrapper
{ {
return int.TryParse(value.ToString(CultureInfo.InvariantCulture), out var parsed) ? parsed : null; return int.TryParse(value.ToString(CultureInfo.InvariantCulture), out var parsed) ? parsed : null;
} }
private void OnConfigurationUpdated(ScriptPluginConfiguration config)
{
foreach (var callback in _updateCallbackActions)
{
ConfigurationUpdated?.Invoke(GetValue(callback.Item1), callback.Item2);
}
}
} }

View File

@ -291,6 +291,15 @@ public class ScriptPluginV2 : IPluginV2
_scriptPluginConfigurationWrapper = _scriptPluginConfigurationWrapper =
new ScriptPluginConfigurationWrapper(_fileName.Split(Path.DirectorySeparatorChar).Last(), ScriptEngine, new ScriptPluginConfigurationWrapper(_fileName.Split(Path.DirectorySeparatorChar).Last(), ScriptEngine,
_configHandler); _configHandler);
_scriptPluginConfigurationWrapper.ConfigurationUpdated += (configValue, callbackAction) =>
{
WrapJavaScriptErrorHandling(() =>
{
callbackAction.DynamicInvoke(JsValue.Undefined, new[] { configValue });
return Task.CompletedTask;
}, _logger, _fileName, _onProcessingScript);
};
} }
private void UnregisterScriptEntities(IManager manager) private void UnregisterScriptEntities(IManager manager)

View File

@ -1,5 +1,5 @@
const init = (registerEventCallback, serviceResolver, _) => { const init = (registerEventCallback, serviceResolver, configWrapper) => {
plugin.onLoad(serviceResolver); plugin.onLoad(serviceResolver, configWrapper);
registerEventCallback('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => { registerEventCallback('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => {
plugin.onPenalty(penaltyEvent); plugin.onPenalty(penaltyEvent);
@ -10,18 +10,17 @@ const init = (registerEventCallback, serviceResolver, _) => {
const plugin = { const plugin = {
author: 'RaidMax', author: 'RaidMax',
version: '2.0', version: '2.1',
name: 'Action on Report', name: 'Action on Report',
config: {
enabled: false, // indicates if the plugin is enabled enabled: false, // indicates if the plugin is enabled
reportAction: 'TempBan', // can be TempBan or Ban reportAction: 'TempBan', // can be TempBan or Ban
maxReportCount: 5, // how many reports before action is taken maxReportCount: 5, // how many reports before action is taken
tempBanDurationMinutes: 60, // how long to temporarily ban the player tempBanDurationMinutes: 60 // how long to temporarily ban the player
penaltyType: {
'report': 0
}, },
onPenalty: function (penaltyEvent) { onPenalty: function (penaltyEvent) {
if (!this.enabled || penaltyEvent.penalty.type !== this.penaltyType['report']) { if (!this.config.enabled || penaltyEvent.penalty.type !== 'Report') {
return; return;
} }
@ -34,11 +33,11 @@ const plugin = {
reportCount++; reportCount++;
this.reportCounts[penaltyEvent.client.networkId] = reportCount; this.reportCounts[penaltyEvent.client.networkId] = reportCount;
if (reportCount >= this.maxReportCount) { if (reportCount >= this.config.maxReportCount) {
switch (this.reportAction) { switch (this.config.reportAction) {
case 'TempBan': case 'TempBan':
this.logger.logInformation(`TempBanning client (id) ${penaltyEvent.client.clientId} because they received ${reportCount} reports`); this.logger.logInformation(`TempBanning client (id) ${penaltyEvent.client.clientId} because they received ${reportCount} reports`);
penaltyEvent.client.tempBan(this.translations['PLUGINS_REPORT_ACTION'], System.TimeSpan.FromMinutes(this.tempBanDurationMinutes), penaltyEvent.Client.CurrentServer.asConsoleClient()); penaltyEvent.client.tempBan(this.translations['PLUGINS_REPORT_ACTION'], System.TimeSpan.FromMinutes(this.config.tempBanDurationMinutes), penaltyEvent.Client.CurrentServer.asConsoleClient());
break; break;
case 'Ban': case 'Ban':
this.logger.logInformation(`Banning client (id) ${penaltyEvent.client.clientId} because they received ${reportCount} reports`); this.logger.logInformation(`Banning client (id) ${penaltyEvent.client.clientId} because they received ${reportCount} reports`);
@ -48,10 +47,25 @@ const plugin = {
} }
}, },
onLoad: function (serviceResolver) { onLoad: function (serviceResolver, configWrapper) {
this.translations = serviceResolver.resolveService('ITranslationLookup'); this.translations = serviceResolver.resolveService('ITranslationLookup');
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']); this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
this.logger.logInformation('ActionOnReport {version} by {author} loaded. Enabled={enabled}', this.version, this.author, this.enabled); this.configWrapper = configWrapper;
const storedConfig = this.configWrapper.getValue('config', newConfig => {
if (newConfig) {
plugin.logger.logInformation('ActionOnReport config reloaded. Enabled={Enabled}', newConfig.enabled);
plugin.config = newConfig;
}
});
if (storedConfig != null) {
this.config = storedConfig
} else {
this.configWrapper.setValue('config', this.config);
}
this.logger.logInformation('ActionOnReport {version} by {author} loaded. Enabled={Enabled}', this.version, this.author, this.config.enabled);
this.reportCounts = {}; this.reportCounts = {};
} }
}; };

View File

@ -7,15 +7,16 @@ const init = (registerNotify, serviceResolver, config) => {
const plugin = { const plugin = {
author: 'Amos, RaidMax', author: 'Amos, RaidMax',
version: '2.0', version: '2.1',
name: 'Broadcast Bans', name: 'Broadcast Bans',
config: null, config: null,
logger: null, logger: null,
translations: null, translations: null,
manager: null, manager: null,
enableBroadcastBans: false,
onClientPenalty: function (penaltyEvent) { onClientPenalty: function (penaltyEvent) {
if (!this.enableBroadcastBans || penaltyEvent.penalty.type !== 5) { if (!this.enableBroadcastBans || penaltyEvent.penalty.type !== 'Ban') {
return; return;
} }
@ -43,7 +44,10 @@ const plugin = {
onLoad: function (serviceResolver, config) { onLoad: function (serviceResolver, config) {
this.config = config; this.config = config;
this.config.setName(this.name); this.config.setName(this.name);
this.enableBroadcastBans = this.config.getValue('EnableBroadcastBans'); this.enableBroadcastBans = this.config.getValue('EnableBroadcastBans', newConfig => {
plugin.logger.logInformation('{Name} config reloaded. Enabled={Enabled}', plugin.name, newConfig);
plugin.enableBroadcastBans = newConfig;
});
this.manager = serviceResolver.resolveService('IManager'); this.manager = serviceResolver.resolveService('IManager');
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']); this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
@ -54,7 +58,7 @@ const plugin = {
this.config.setValue('EnableBroadcastBans', this.enableBroadcastBans); this.config.setValue('EnableBroadcastBans', this.enableBroadcastBans);
} }
this.logger.logInformation('{Name} {Version} by {Author} loaded. Enabled={enabled}', this.name, this.version, this.logger.logInformation('{Name} {Version} by {Author} loaded. Enabled={Enabled}', this.name, this.version,
this.author, this.enableBroadcastBans); this.author, this.enableBroadcastBans);
} }
}; };

View File

@ -2,23 +2,24 @@ let vpnExceptionIds = [];
const vpnAllowListKey = 'Webfront::Nav::Admin::VPNAllowList'; const vpnAllowListKey = 'Webfront::Nav::Admin::VPNAllowList';
const vpnWhitelistKey = 'Webfront::Profile::VPNWhitelist'; const vpnWhitelistKey = 'Webfront::Profile::VPNWhitelist';
const init = (registerNotify, serviceResolver, config, pluginHelper) => { const init = (registerNotify, serviceResolver, configWrapper, pluginHelper) => {
registerNotify('IManagementEventSubscriptions.ClientStateAuthorized', (authorizedEvent, token) => plugin.onClientAuthorized(authorizedEvent, token)); registerNotify('IManagementEventSubscriptions.ClientStateAuthorized', (authorizedEvent, token) => plugin.onClientAuthorized(authorizedEvent, token));
plugin.onLoad(serviceResolver, config, pluginHelper); plugin.onLoad(serviceResolver, configWrapper, pluginHelper);
return plugin; return plugin;
}; };
const plugin = { const plugin = {
author: 'RaidMax', author: 'RaidMax',
version: '2.0', version: '2.1',
name: 'VPN Detection Plugin', name: 'VPN Detection Plugin',
manager: null, manager: null,
config: null, configWrapper: null,
logger: null, logger: null,
serviceResolver: null, serviceResolver: null,
translations: null, translations: null,
pluginHelper: null, pluginHelper: null,
enabled: true,
commands: [{ commands: [{
name: 'whitelistvpn', name: 'whitelistvpn',
@ -32,7 +33,7 @@ const plugin = {
}], }],
execute: (gameEvent) => { execute: (gameEvent) => {
vpnExceptionIds.push(gameEvent.Target.ClientId); vpnExceptionIds.push(gameEvent.Target.ClientId);
plugin.config.setValue('vpnExceptionIds', vpnExceptionIds); plugin.configWrapper.setValue('vpnExceptionIds', vpnExceptionIds);
gameEvent.origin.tell(`Successfully whitelisted ${gameEvent.target.name}`); gameEvent.origin.tell(`Successfully whitelisted ${gameEvent.target.name}`);
} }
@ -49,7 +50,7 @@ const plugin = {
}], }],
execute: (gameEvent) => { execute: (gameEvent) => {
vpnExceptionIds = vpnExceptionIds.filter(exception => parseInt(exception) !== parseInt(gameEvent.Target.ClientId)); vpnExceptionIds = vpnExceptionIds.filter(exception => parseInt(exception) !== parseInt(gameEvent.Target.ClientId));
plugin.config.setValue('vpnExceptionIds', vpnExceptionIds); plugin.configWrapper.setValue('vpnExceptionIds', vpnExceptionIds);
gameEvent.origin.tell(`Successfully disallowed ${gameEvent.target.name} from connecting with VPN`); gameEvent.origin.tell(`Successfully disallowed ${gameEvent.target.name} from connecting with VPN`);
} }
@ -148,30 +149,45 @@ const plugin = {
], ],
onClientAuthorized: async function (authorizeEvent, token) { onClientAuthorized: async function (authorizeEvent, token) {
if (authorizeEvent.client.isBot) { if (authorizeEvent.client.isBot || !this.enabled) {
return; return;
} }
await this.checkForVpn(authorizeEvent.client, token); await this.checkForVpn(authorizeEvent.client, token);
}, },
onLoad: function (serviceResolver, config, pluginHelper) { onLoad: function (serviceResolver, configWrapper, pluginHelper) {
this.serviceResolver = serviceResolver; this.serviceResolver = serviceResolver;
this.config = config; this.configWrapper = configWrapper;
this.pluginHelper = pluginHelper; this.pluginHelper = pluginHelper;
this.manager = this.serviceResolver.resolveService('IManager'); this.manager = this.serviceResolver.resolveService('IManager');
this.logger = this.serviceResolver.resolveService('ILogger', ['ScriptPluginV2']); this.logger = this.serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
this.translations = this.serviceResolver.resolveService('ITranslationLookup'); this.translations = this.serviceResolver.resolveService('ITranslationLookup');
this.config.setName(this.name); // use legacy key this.configWrapper.setName(this.name); // use legacy key
this.config.getValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(parseInt(element))); this.configWrapper.getValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(parseInt(element)));
this.logger.logInformation(`Loaded ${vpnExceptionIds.length} ids into whitelist`); this.logger.logInformation(`Loaded ${vpnExceptionIds.length} ids into whitelist`);
this.enabled = this.configWrapper.getValue('enabled', newValue => {
if (newValue) {
plugin.logger.logInformation('{Name} configuration updated. Enabled={Enabled}', newValue);
plugin.enabled = newValue;
}
});
if (this.enabled === undefined) {
this.configWrapper.setValue('enabled', true);
this.enabled = true;
}
this.interactionRegistration = this.serviceResolver.resolveService('IInteractionRegistration'); this.interactionRegistration = this.serviceResolver.resolveService('IInteractionRegistration');
this.interactionRegistration.unregisterInteraction(vpnWhitelistKey); this.interactionRegistration.unregisterInteraction(vpnWhitelistKey);
this.interactionRegistration.unregisterInteraction(vpnAllowListKey); this.interactionRegistration.unregisterInteraction(vpnAllowListKey);
this.logger.logInformation('{Name} {Version} by {Author} loaded. Enabled={Enabled}', this.name, this.version,
this.author, this.enabled);
}, },
checkForVpn: async function (origin, token) { checkForVpn: async function (origin, _) {
let exempt = false; let exempt = false;
// prevent players that are exempt from being kicked // prevent players that are exempt from being kicked
vpnExceptionIds.forEach(function (id) { vpnExceptionIds.forEach(function (id) {

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces; namespace SharedLibraryCore.Interfaces;
@ -7,4 +8,5 @@ public interface IConfigurationHandlerV2<TConfigurationType> where TConfiguratio
Task<TConfigurationType> Get(string configurationName, TConfigurationType defaultConfiguration = null); Task<TConfigurationType> Get(string configurationName, TConfigurationType defaultConfiguration = null);
Task Set(TConfigurationType configuration); Task Set(TConfigurationType configuration);
Task Set(); Task Set();
event Action<TConfigurationType> Updated;
} }