implement PluginV2 for script plugins

This commit is contained in:
RaidMax
2023-04-04 18:24:13 -05:00
parent ad20572879
commit fab3cf95d6
33 changed files with 1659 additions and 1026 deletions

View File

@ -1,7 +1,7 @@
const init = (registerEventCallback, serviceResolver, _) => {
plugin.onLoad(serviceResolver);
registerEventCallback('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, token) => {
registerEventCallback('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => {
plugin.onPenalty(penaltyEvent);
});
@ -10,7 +10,7 @@ const init = (registerEventCallback, serviceResolver, _) => {
const plugin = {
author: 'RaidMax',
version: '1.2',
version: '2.0',
name: 'Action on Report',
enabled: false, // indicates if the plugin is enabled
reportAction: 'TempBan', // can be TempBan or Ban
@ -20,7 +20,7 @@ const plugin = {
'report': 0
},
onPenalty: function(penaltyEvent) {
onPenalty: function (penaltyEvent) {
if (!this.enabled || penaltyEvent.penalty.type !== this.penaltyType['report']) {
return;
}
@ -48,7 +48,7 @@ const plugin = {
}
},
onLoad: function(serviceResolver) {
onLoad: function (serviceResolver) {
this.translations = serviceResolver.resolveService('ITranslationLookup');
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
this.logger.logInformation('ActionOnReport {version} by {author} loaded. Enabled={enabled}', this.version, this.author, this.enabled);

View File

@ -1,48 +1,60 @@
const broadcastMessage = (server, message) => {
server.Manager.GetServers().forEach(s => {
s.Broadcast(message);
});
const init = (registerNotify, serviceResolver, config) => {
registerNotify('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => plugin.onClientPenalty(penaltyEvent));
plugin.onLoad(serviceResolver, config);
return plugin;
};
const plugin = {
author: 'Amos',
version: 1.0,
author: 'Amos, RaidMax',
version: '2.0',
name: 'Broadcast Bans',
config: null,
logger: null,
translations: null,
manager: null,
onEventAsync: function (gameEvent, server) {
onClientPenalty: function (penaltyEvent) {
if (!this.enableBroadcastBans) {
return;
}
if (gameEvent.TypeName === 'Ban') {
let penalty = undefined;
gameEvent.Origin.AdministeredPenalties?.forEach(p => {
penalty = p.AutomatedOffense;
})
let automatedPenaltyMessage;
if (gameEvent.Origin.ClientId === 1 && penalty !== undefined) {
let localization = _localization.LocalizationIndex['PLUGINS_BROADCAST_BAN_ACMESSAGE'].replace('{{targetClient}}', gameEvent.Target.CleanedName);
broadcastMessage(server, localization);
} else {
let localization = _localization.LocalizationIndex['PLUGINS_BROADCAST_BAN_MESSAGE'].replace('{{targetClient}}', gameEvent.Target.CleanedName);
broadcastMessage(server, localization);
}
penaltyEvent.penalty.punisher.administeredPenalties?.forEach(penalty => {
automatedPenaltyMessage = penalty.automatedOffense;
});
if (penaltyEvent.penalty.punisher.clientId === 1 && automatedPenaltyMessage !== undefined) {
let message = this.translations['PLUGINS_BROADCAST_BAN_ACMESSAGE'].replace('{{targetClient}}', penaltyEvent.client.cleanedName);
this.broadcastMessage(message);
} else {
let message = this.translations['PLUGINS_BROADCAST_BAN_MESSAGE'].replace('{{targetClient}}', penaltyEvent.client.cleanedName);
this.broadcastMessage(message);
}
},
onLoadAsync: function (manager) {
this.configHandler = _configHandler;
this.enableBroadcastBans = this.configHandler.GetValue('EnableBroadcastBans');
broadcastMessage: function (message) {
this.manager.getServers().forEach(server => {
server.broadcast(message);
});
},
onLoad: function (serviceResolver, config) {
this.config = config;
this.config.setName(this.name);
this.enableBroadcastBans = this.config.getValue('EnableBroadcastBans');
this.manager = serviceResolver.resolveService('IManager');
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
this.translations = serviceResolver.resolveService('ITranslationLookup');
if (this.enableBroadcastBans === undefined) {
this.enableBroadcastBans = false;
this.configHandler.SetValue('EnableBroadcastBans', this.enableBroadcastBans);
this.config.setValue('EnableBroadcastBans', this.enableBroadcastBans);
}
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
this.logger.logInformation('{Name} {Version} by {Author} loaded. Enabled={enabled}', this.name, this.version,
this.author, this.enableBroadcastBans);
}
};

View File

@ -1,98 +1,400 @@
const servers = {};
const inDvar = 'sv_iw4madmin_in';
const outDvar = 'sv_iw4madmin_out';
const pollRate = 900;
const enableCheckTimeout = 10000;
let logger = {};
const maxQueuedMessages = 25;
const integrationEnabledDvar = 'sv_iw4madmin_integration_enabled';
const pollingRate = 300;
let plugin = {
const init = (registerNotify, serviceResolver, config) => {
registerNotify('IManagementEventSubscriptions.ClientStateInitialized', (clientEvent, _) => plugin.onClientEnteredMatch(clientEvent));
registerNotify('IGameServerEventSubscriptions.ServerValueReceived', (serverValueEvent, _) => plugin.onServerValueReceived(serverValueEvent));
registerNotify('IGameServerEventSubscriptions.ServerValueSetCompleted', (serverValueEvent, _) => plugin.onServerValueSetCompleted(serverValueEvent));
registerNotify('IGameServerEventSubscriptions.MonitoringStarted', (monitorStartEvent, _) => plugin.onServerMonitoringStart(monitorStartEvent));
registerNotify('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => plugin.onPenalty(penaltyEvent));
plugin.onLoad(serviceResolver, config);
return plugin;
};
const plugin = {
author: 'RaidMax',
version: 1.1,
version: '2.0',
name: 'Game Interface',
serviceResolver: null,
eventManager: null,
logger: null,
commands: null,
onEventAsync: (gameEvent, server) => {
if (servers[server.EndPoint] != null && !servers[server.EndPoint].enabled) {
return;
}
onLoad: function (serviceResolver, config) {
this.serviceResolver = serviceResolver;
this.eventManager = serviceResolver.resolveService('IManager');
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
this.commands = commands;
this.config = config;
},
const eventType = String(gameEvent.TypeName).toLowerCase();
onClientEnteredMatch: function (clientEvent) {
const serverState = servers[clientEvent.client.currentServer.id];
if (eventType === undefined) {
return;
}
switch (eventType) {
case 'start':
const enabled = initialize(server);
if (!enabled) {
return;
}
break;
case 'preconnect':
// when the plugin is reloaded after the servers are started
if (servers[server.EndPoint] === undefined || servers[server.EndPoint] == null) {
const enabled = initialize(server);
if (!enabled) {
return;
}
}
const timer = servers[server.EndPoint].timer;
if (!timer.IsRunning) {
timer.Start(0, pollRate);
}
break;
case 'warn':
const warningTitle = _localization.LocalizationIndex['GLOBAL_WARNING'];
sendScriptCommand(server, 'Alert', gameEvent.Origin, gameEvent.Target, {
alertType: warningTitle + '!',
message: gameEvent.Data
});
break;
if (serverState === undefined || serverState == null) {
this.initializeServer(clientEvent.client.currentServer);
} else if (!serverState.running && !serverState.initializationInProgress) {
serverState.running = true;
this.requestGetDvar(inDvar, clientEvent.client.currentServer);
}
},
onLoadAsync: manager => {
logger = _serviceResolver.ResolveService('ILogger');
logger.WriteInfo('Game Interface Startup');
onPenalty: function (penaltyEvent) {
const warning = 1;
if (penaltyEvent.penalty.type !== warning || !penaltyEvent.client.isIngame) {
return;
}
sendScriptCommand(penaltyEvent.client.currentServer, 'Alert', penaltyEvent.penalty.punisher, penaltyEvent.client, {
alertType: this.translations('GLOBAL_WARNING') + '!',
message: penaltyEvent.penalty.offense
});
},
onUnloadAsync: () => {
for (let i = 0; i < servers.length; i++) {
if (servers[i].enabled) {
servers[i].timer.Stop();
onServerValueReceived: function (serverValueEvent) {
const name = serverValueEvent.response.name;
if (name === integrationEnabledDvar) {
this.handleInitializeServerData(serverValueEvent);
} else if (name === inDvar) {
this.handleIncomingServerData(serverValueEvent);
}
},
onServerValueSetCompleted: async function (serverValueEvent) {
if (serverValueEvent.valueName !== inDvar && serverValueEvent.valueName !== outDvar) {
this.logger.logDebug('Ignoring set complete of {name}', serverValueEvent.valueName);
return;
}
const serverState = servers[serverValueEvent.server.id];
serverState.outQueue.shift();
this.logger.logDebug('outQueue len = {outLen}, inQueue len = {inLen}', serverState.outQueue.length, serverState.inQueue.length);
// if it didn't succeed, we need to retry
if (!serverValueEvent.success && !this.eventManager.cancellationToken.isCancellationRequested) {
this.logger.logDebug('Set of server value failed... retrying');
this.requestSetDvar(serverValueEvent.valueName, serverValueEvent.value, serverValueEvent.server);
return;
}
// we informed the server that we received the event
if (serverState.inQueue.length > 0 && serverValueEvent.valueName === inDvar) {
const input = serverState.inQueue.shift();
// if we queued an event then the next loop will be at the value set complete
if (await this.processEventMessage(input, serverValueEvent.server)) {
// return;
}
}
this.logger.logDebug('loop complete');
// loop restarts
this.requestGetDvar(inDvar, serverValueEvent.server);
},
onTickAsync: server => {
initializeServer: function (server) {
servers[server.id] = {
enabled: false,
running: false,
initializationInProgress: true,
queuedMessages: [],
inQueue: [],
outQueue: [],
commandQueue: []
};
this.logger.logDebug('Initializing game interface for {serverId}', server.id);
this.requestGetDvar(integrationEnabledDvar, server);
},
handleInitializeServerData: function (responseEvent) {
this.logger.logInformation('GSC integration enabled = {integrationValue} for {server}',
responseEvent.response.value, responseEvent.server.id);
if (responseEvent.response.value !== '1') {
return;
}
const serverState = servers[responseEvent.server.id];
serverState.outQueue.shift();
serverState.enabled = true;
serverState.running = true;
serverState.initializationInProgress = false;
this.requestGetDvar(inDvar, responseEvent.server);
},
handleIncomingServerData: function (responseEvent) {
this.logger.logDebug('Received {dvarName}={dvarValue} success={success} from {server}', responseEvent.response.name,
responseEvent.response.value, responseEvent.success, responseEvent.server.id);
const serverState = servers[responseEvent.server.id];
serverState.outQueue.shift();
if (responseEvent.server.connectedClients.count === 0) {
// no clients connected so we don't need to query
serverState.running = false;
return;
}
// read failed, so let's retry
if (!responseEvent.success && !this.eventManager.cancellationToken.isCancellationRequested) {
this.logger.logDebug('Get of server value failed... retrying');
this.requestGetDvar(responseEvent.response.name, responseEvent.server);
return;
}
let input = responseEvent.response.value;
const server = responseEvent.server;
if (this.eventManager.cancellationToken.isCancellationRequested) {
return;
}
// no data available so we poll again or send any outgoing messages
if (isEmpty(input)) {
this.logger.logDebug('No data to process from server');
if (serverState.commandQueue.length > 0) {
this.logger.logDebug('Sending next out message');
const nextMessage = serverState.commandQueue.shift();
this.requestSetDvar(outDvar, nextMessage, server);
} else {
this.requestGetDvar(inDvar, server);
}
return;
}
serverState.inQueue.push(input);
// let server know that we received the data
this.requestSetDvar(inDvar, '', server);
},
processEventMessage: async function (input, server) {
let messageQueued = false;
const event = parseEvent(input);
this.logger.logDebug('Processing input... {eventType} {subType} {data} {clientNumber}', event.eventType,
event.subType, event.data.toString(), event.clientNumber);
const metaService = this.serviceResolver.ResolveService('IMetaServiceV2');
const threading = importNamespace('System.Threading');
const tokenSource = new threading.CancellationTokenSource();
const token = tokenSource.token;
// todo: refactor to mapping if possible
if (event.eventType === 'ClientDataRequested') {
const client = server.getClientByNumber(event.clientNumber);
if (client != null) {
this.logger.logDebug('Found client {name}', client.name);
let data = [];
const metaService = this.serviceResolver.ResolveService('IMetaServiceV2');
if (event.subType === 'Meta') {
const meta = (await metaService.getPersistentMeta(event.data, client.clientId, token)).result;
data[event.data] = meta === null ? '' : meta.Value;
this.logger.logDebug('event data is {data}', event.data);
} else {
const clientStats = getClientStats(client, server);
const tagMeta = (await metaService.getPersistentMetaByLookup('ClientTagV2', 'ClientTagNameV2', client.clientId, token)).result;
data = {
level: client.level,
clientId: client.clientId,
lastConnection: client.lastConnection,
tag: tagMeta?.value ?? '',
performance: clientStats?.performance ?? 200.0
};
}
this.sendEventMessage(server, false, 'ClientDataReceived', event.subType, client, undefined, data);
messageQueued = true;
} else {
this.logger.logWarning('Could not find client slot {clientNumber} when processing {eventType}', event.clientNumber, event.eventType);
this.sendEventMessage(server, false, 'ClientDataReceived', 'Fail', event.clientNumber, undefined, {
ClientNumber: event.clientNumber
});
messageQueued = true;
}
}
if (event.eventType === 'SetClientDataRequested') {
let client = server.getClientByNumber(event.clientNumber);
let clientId;
if (client != null) {
clientId = client.clientId;
} else {
clientId = parseInt(event.data['clientId']);
}
this.logger.logDebug('ClientId={clientId}', clientId);
if (clientId == null) {
this.logger.logWarning('Could not find client slot {clientNumber} when processing {eventType}', event.clientNumber, event.eventType);
this.sendEventMessage(server, false, 'SetClientDataCompleted', 'Meta', {
ClientNumber: event.clientNumber
}, undefined, {
status: 'Fail'
});
messageQueued = true;
} else {
if (event.subType === 'Meta') {
try {
if (event.data['value'] != null && event.data['key'] != null) {
this.logger.logDebug('Key={key}, Value={value}, Direction={direction} {token}', event.data['key'], event.data['value'], event.data['direction'], token);
if (event.data['direction'] != null) {
const parsedValue = parseInt(event.data['value']);
const key = event.data['key'].toString();
if (!isNaN(parsedValue)) {
event.data['direction'] = 'up' ?
(await metaService.incrementPersistentMeta(key, parsedValue, clientId, token)).result :
(await metaService.decrementPersistentMeta(key, parsedValue, clientId, token)).result;
}
} else {
const _ = (await metaService.setPersistentMeta(event.data['key'], event.data['value'], clientId, token)).result;
}
if (event.data['key'] === 'PersistentClientGuid') {
const serverEvents = importNamespace('SharedLibraryCore.Events.Management');
const persistentIdEvent = new serverEvents.ClientPersistentIdReceiveEvent(client, event.data['value']);
this.eventManager.queueEvent(persistentIdEvent);
}
}
this.sendEventMessage(server, false, 'SetClientDataCompleted', 'Meta', {
ClientNumber: event.clientNumber
}, undefined, {
status: 'Complete'
});
messageQueued = true;
} catch (error) {
this.sendEventMessage(server, false, 'SetClientDataCompleted', 'Meta', {
ClientNumber: event.clientNumber
}, undefined, {
status: 'Fail'
});
this.logger.logError('Could not persist client meta {Key}={Value} {error} for {Client}', event.data['key'], event.data['value'], error.toString(), clientId);
messageQueued = true;
}
}
}
}
tokenSource.dispose();
return messageQueued;
},
sendEventMessage: function (server, responseExpected, event, subtype, origin, target, data) {
let targetClientNumber = -1;
if (target != null) {
targetClientNumber = target.ClientNumber;
}
const output = `${responseExpected ? '1' : '0'};${event};${subtype};${origin.ClientNumber};${targetClientNumber};${buildDataString(data)}`;
this.logger.logDebug('Queuing output for server {output}', output);
servers[server.id].commandQueue.push(output);
},
requestGetDvar: function (dvarName, server) {
const serverState = servers[server.id];
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
const requestEvent = new serverEvents.ServerValueRequestEvent(dvarName, server);
requestEvent.delayMs = pollingRate;
requestEvent.timeoutMs = 2000;
requestEvent.source = this.name;
if (server.matchEndTime !== null) {
const extraDelay = 15000;
const end = new Date(server.matchEndTime.toString());
const diff = new Date().getTime() - end.getTime();
if (diff < extraDelay) {
requestEvent.delayMs = (extraDelay - diff) + pollingRate;
this.logger.logDebug('Increasing delay time to {Delay}ms due to recent map change', requestEvent.delayMs);
}
}
this.logger.logDebug('requesting {dvar}', dvarName);
serverState.outQueue.push(requestEvent);
if (serverState.outQueue.length <= 1) {
this.eventManager.queueEvent(requestEvent);
} else {
this.logger.logError('[requestGetDvar] Queue is full!');
}
},
requestSetDvar: function (dvarName, dvarValue, server) {
const serverState = servers[server.id];
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
const requestEvent = new serverEvents.ServerValueSetRequestEvent(dvarName, dvarValue, server);
requestEvent.delayMs = pollingRate;
requestEvent.timeoutMs = 2000;
requestEvent.source = this.name;
if (server.matchEndTime !== null) {
const extraDelay = 15000;
const end = new Date(server.matchEndTime.toString());
const diff = new Date().getTime() - end.getTime();
if (diff < extraDelay) {
requestEvent.delayMs = (extraDelay - diff) + pollingRate;
this.logger.logDebug('Increasing delay time to {Delay}ms due to recent map change', requestEvent.delayMs);
}
}
serverState.outQueue.push(requestEvent);
this.logger.logDebug('outQueue size = {length}', serverState.outQueue.length);
// if this is the only item in the out-queue we can send it immediately
if (serverState.outQueue.length === 1) {
this.eventManager.queueEvent(requestEvent);
} else {
this.logger.logError('[requestSetDvar] Queue is full!');
}
},
onServerMonitoringStart: function (monitorStartEvent) {
this.initializeServer(monitorStartEvent.server);
}
};
let commands = [{
name: 'giveweapon',
description: 'gives specified weapon',
alias: 'gw',
permission: 'SeniorAdmin',
targetRequired: true,
arguments: [{
name: 'player',
required: true
},
{
name: 'weapon name',
required: true
}],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'GiveWeapon', gameEvent.Origin, gameEvent.Target, {weaponName: gameEvent.Data});
}
const commands = [{
name: 'giveweapon',
description: 'gives specified weapon',
alias: 'gw',
permission: 'SeniorAdmin',
targetRequired: true,
arguments: [{
name: 'player',
required: true
},
{
name: 'weapon name',
required: true
}
],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.owner, 'GiveWeapon', gameEvent.origin, gameEvent.target, {
weaponName: gameEvent.data
});
}
},
{
name: 'takeweapons',
description: 'take all weapons from specified player',
@ -105,10 +407,10 @@ let commands = [{
}],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'TakeWeapons', gameEvent.Origin, gameEvent.Target, undefined);
sendScriptCommand(gameEvent.owner, 'TakeWeapons', gameEvent.origin, gameEvent.target, undefined);
}
},
{
@ -123,10 +425,10 @@ let commands = [{
}],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'SwitchTeams', gameEvent.Origin, gameEvent.Target, undefined);
sendScriptCommand(gameEvent.owner, 'SwitchTeams', gameEvent.origin, gameEvent.target, undefined);
}
},
{
@ -141,10 +443,10 @@ let commands = [{
}],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'LockControls', gameEvent.Origin, gameEvent.Target, undefined);
sendScriptCommand(gameEvent.owner, 'LockControls', gameEvent.origin, gameEvent.target, undefined);
}
},
{
@ -156,10 +458,10 @@ let commands = [{
arguments: [],
supportedGames: ['IW4', 'IW5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'NoClip', gameEvent.Origin, gameEvent.Origin, undefined);
sendScriptCommand(gameEvent.owner, 'NoClip', gameEvent.origin, gameEvent.origin, undefined);
}
},
{
@ -171,10 +473,10 @@ let commands = [{
arguments: [],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'Hide', gameEvent.Origin, gameEvent.Origin, undefined);
sendScriptCommand(gameEvent.owner, 'Hide', gameEvent.origin, gameEvent.origin, undefined);
}
},
{
@ -190,13 +492,14 @@ let commands = [{
{
name: 'message',
required: true
}],
}
],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'Alert', gameEvent.Origin, gameEvent.Target, {
sendScriptCommand(gameEvent.Owner, 'Alert', gameEvent.origin, gameEvent.target, {
alertType: 'Alert',
message: gameEvent.Data
});
@ -214,10 +517,10 @@ let commands = [{
}],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'Goto', gameEvent.Origin, gameEvent.Target, undefined);
sendScriptCommand(gameEvent.owner, 'Goto', gameEvent.origin, gameEvent.target, undefined);
}
},
{
@ -232,10 +535,10 @@ let commands = [{
}],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'PlayerToMe', gameEvent.Origin, gameEvent.Target, undefined);
sendScriptCommand(gameEvent.owner, 'PlayerToMe', gameEvent.origin, gameEvent.target, undefined);
}
},
{
@ -255,15 +558,16 @@ let commands = [{
{
name: 'z',
required: true
}],
}
],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
const args = String(gameEvent.Data).split(' ');
sendScriptCommand(gameEvent.Owner, 'Goto', gameEvent.Origin, gameEvent.Target, {
sendScriptCommand(gameEvent.owner, 'Goto', gameEvent.origin, gameEvent.target, {
x: args[0],
y: args[1],
z: args[2]
@ -282,10 +586,10 @@ let commands = [{
}],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'Kill', gameEvent.Origin, gameEvent.Target, undefined);
sendScriptCommand(gameEvent.owner, 'Kill', gameEvent.origin, gameEvent.target, undefined);
}
},
{
@ -300,244 +604,30 @@ let commands = [{
}],
supportedGames: ['IW4', 'IW5', 'T5'],
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.Owner, 'SetSpectator', gameEvent.Origin, gameEvent.Target, undefined);
sendScriptCommand(gameEvent.owner, 'SetSpectator', gameEvent.origin, gameEvent.target, undefined);
}
}];
}
];
const sendScriptCommand = (server, command, origin, target, data) => {
const state = servers[server.EndPoint];
if (state === undefined || !state.enabled) {
const serverState = servers[server.id];
if (serverState === undefined || !serverState.enabled) {
return;
}
sendEvent(server, false, 'ExecuteCommandRequested', command, origin, target, data);
}
const sendEvent = (server, responseExpected, event, subtype, origin, target, data) => {
const logger = _serviceResolver.ResolveService('ILogger');
const state = servers[server.EndPoint];
if (state.queuedMessages.length >= maxQueuedMessages) {
logger.WriteWarning('Too many queued messages so we are skipping');
return;
}
let targetClientNumber = -1;
if (target != null) {
targetClientNumber = target.ClientNumber;
}
const output = `${responseExpected ? '1' : '0'};${event};${subtype};${origin.ClientNumber};${targetClientNumber};${buildDataString(data)}`;
logger.WriteDebug(`Queuing output for server ${output}`);
state.queuedMessages.push(output);
plugin.sendEventMessage(server, false, 'ExecuteCommandRequested', command, origin, target, data);
};
const initialize = (server) => {
const logger = _serviceResolver.ResolveService('ILogger');
servers[server.EndPoint] = {
enabled: false
}
let enabled = false;
try {
enabled = server.GetServerDvar('sv_iw4madmin_integration_enabled', enableCheckTimeout) === '1';
} catch (error) {
logger.WriteError(`Could not get integration status of ${server.EndPoint} - ${error}`);
}
logger.WriteInfo(`GSC Integration enabled = ${enabled}`);
if (!enabled) {
return false;
}
logger.WriteDebug(`Setting up bus timer for ${server.EndPoint}`);
let timer = _serviceResolver.ResolveService('IScriptPluginTimerHelper');
timer.OnTick(() => pollForEvents(server), `GameEventPoller ${server.ToString()}`);
// necessary to prevent multi-threaded access to the JS context
timer.SetDependency(_lock);
servers[server.EndPoint].timer = timer;
servers[server.EndPoint].enabled = true;
servers[server.EndPoint].waitingOnInput = false;
servers[server.EndPoint].waitingOnOutput = false;
servers[server.EndPoint].queuedMessages = [];
setDvar(server, inDvar, '', onSetDvar);
setDvar(server, outDvar, '', onSetDvar);
return true;
}
const getClientStats = (client, server) => {
const contextFactory = _serviceResolver.ResolveService('IDatabaseContextFactory');
const context = contextFactory.CreateContext(false);
const stats = context.ClientStatistics.GetClientsStatData([client.ClientId], server.GetId()); // .Find(client.ClientId, serverId);
context.Dispose();
return stats.length > 0 ? stats[0] : undefined;
}
const contextFactory = plugin.serviceResolver.ResolveService('IDatabaseContextFactory');
const context = contextFactory.createContext(false);
const stats = context.clientStatistics.getClientsStatData([client.ClientId], server.legacyDatabaseId);
context.dispose();
function onReceivedDvar(server, dvarName, dvarValue, success) {
const logger = _serviceResolver.ResolveService('ILogger');
logger.WriteDebug(`Received ${dvarName}=${dvarValue} success=${success}`);
let input = dvarValue;
const state = servers[server.EndPoint];
if (state.waitingOnOutput && dvarName === outDvar && isEmpty(dvarValue)) {
logger.WriteDebug('Setting out bus to read to send');
// reset our flag letting use the out bus is open
state.waitingOnOutput = !success;
}
if (state.waitingOnInput && dvarName === inDvar) {
logger.WriteDebug('Setting in bus to ready to receive');
// we've received the data so now we can mark it as ready for more
state.waitingOnInput = false;
}
if (isEmpty(input)) {
input = '';
}
if (input.length > 0) {
const event = parseEvent(input)
logger.WriteDebug(`Processing input... ${event.eventType} ${event.subType} ${event.data.toString()} ${event.clientNumber}`);
const metaService = _serviceResolver.ResolveService('IMetaServiceV2');
const threading = importNamespace('System.Threading');
const token = new threading.CancellationTokenSource().Token;
// todo: refactor to mapping if possible
if (event.eventType === 'ClientDataRequested') {
const client = server.GetClientByNumber(event.clientNumber);
if (client != null) {
logger.WriteDebug(`Found client ${client.Name}`);
let data = [];
const metaService = _serviceResolver.ResolveService('IMetaServiceV2');
if (event.subType === 'Meta') {
const meta = metaService.GetPersistentMeta(event.data, client.ClientId, token).GetAwaiter().GetResult();
data[event.data] = meta === null ? '' : meta.Value;
logger.WriteDebug(`event data is ${event.data}`);
} else {
const clientStats = getClientStats(client, server);
const tagMeta = metaService.GetPersistentMetaByLookup('ClientTagV2', 'ClientTagNameV2', client.ClientId, token).GetAwaiter().GetResult();
data = {
level: client.Level,
clientId: client.ClientId,
lastConnection: client.LastConnection,
tag: tagMeta?.Value ?? '',
performance: clientStats?.Performance ?? 200.0
};
}
sendEvent(server, false, 'ClientDataReceived', event.subType, client, undefined, data);
} else {
logger.WriteWarning(`Could not find client slot ${event.clientNumber} when processing ${event.eventType}`);
sendEvent(server, false, 'ClientDataReceived', 'Fail', event.clientNumber, undefined, {ClientNumber: event.clientNumber});
}
}
if (event.eventType === 'SetClientDataRequested') {
let client = server.GetClientByNumber(event.clientNumber);
let clientId;
if (client != null) {
clientId = client.ClientId;
} else {
clientId = parseInt(event.data.clientId);
}
logger.WriteDebug(`ClientId=${clientId}`);
if (clientId == null) {
logger.WriteWarning(`Could not find client slot ${event.clientNumber} when processing ${event.eventType}`);
sendEvent(server, false, 'SetClientDataCompleted', 'Meta', {ClientNumber: event.clientNumber}, undefined, {status: 'Fail'});
} else {
if (event.subType === 'Meta') {
try {
if (event.data['value'] != null && event.data['key'] != null) {
logger.WriteDebug(`Key=${event.data['key']}, Value=${event.data['value']}, Direction=${event.data['direction']} ${token}`);
if (event.data['direction'] != null) {
event.data['direction'] = 'up'
? metaService.IncrementPersistentMeta(event.data['key'], parseInt(event.data['value']), clientId, token).GetAwaiter().GetResult()
: metaService.DecrementPersistentMeta(event.data['key'], parseInt(event.data['value']), clientId, token).GetAwaiter().GetResult();
} else {
metaService.SetPersistentMeta(event.data['key'], event.data['value'], clientId, token).GetAwaiter().GetResult();
}
}
sendEvent(server, false, 'SetClientDataCompleted', 'Meta', {ClientNumber: event.clientNumber}, undefined, {status: 'Complete'});
} catch (error) {
sendEvent(server, false, 'SetClientDataCompleted', 'Meta', {ClientNumber: event.clientNumber}, undefined, {status: 'Fail'});
logger.WriteError('Could not persist client meta ' + error.toString());
}
}
}
}
setDvar(server, inDvar, '', onSetDvar);
} else if (server.ClientNum === 0) {
servers[server.EndPoint].timer.Stop();
}
}
function onSetDvar(server, dvarName, dvarValue, success) {
const logger = _serviceResolver.ResolveService('ILogger');
logger.WriteDebug(`Completed set of dvar ${dvarName}=${dvarValue}, success=${success}`);
const state = servers[server.EndPoint];
if (dvarName === inDvar && success && isEmpty(dvarValue)) {
logger.WriteDebug('In bus is ready for new data');
// reset our flag letting use the in bus is ready for more data
state.waitingOnInput = false;
}
}
const pollForEvents = server => {
const state = servers[server.EndPoint];
if (state === null || !state.enabled) {
return;
}
if (server.Throttled) {
logger.WriteDebug('Server is throttled so we are not polling for game data');
return;
}
if (!state.waitingOnInput) {
state.waitingOnInput = true;
logger.WriteDebug('Attempting to get in dvar value');
getDvar(server, inDvar, onReceivedDvar);
}
if (!state.waitingOnOutput) {
if (state.queuedMessages.length === 0) {
logger.WriteDebug('No messages in queue');
return;
}
state.waitingOnOutput = true;
const nextMessage = state.queuedMessages.splice(0, 1);
setDvar(server, outDvar, nextMessage, onSetDvar);
}
if (state.waitingOnOutput) {
getDvar(server, outDvar, onReceivedDvar);
}
}
return stats.length > 0 ? stats[0] : undefined;
};
const parseEvent = (input) => {
if (input === undefined) {
@ -551,8 +641,8 @@ const parseEvent = (input) => {
subType: eventInfo[2],
clientNumber: eventInfo[3],
data: eventInfo.length > 4 ? parseDataString(eventInfo[4]) : undefined
}
}
};
};
const buildDataString = data => {
if (data === undefined) {
@ -561,21 +651,23 @@ const buildDataString = data => {
let formattedData = '';
for (const prop in data) {
formattedData += `${prop}=${data[prop]}|`;
for (let [key, value] of Object.entries(data)) {
formattedData += `${key}=${value}|`;
}
return formattedData.substring(0, Math.max(0, formattedData.length - 1));
}
return formattedData.slice(0, -1);
};
const parseDataString = data => {
if (data === undefined) {
return '';
}
const dict = {}
const dict = {};
const split = data.split('|');
for (const segment of data.split('|')) {
for (let i = 0; i < split.length; i++) {
const segment = split[i];
const keyValue = segment.split('=');
if (keyValue.length !== 2) {
continue;
@ -584,16 +676,16 @@ const parseDataString = data => {
}
return Object.keys(dict).length === 0 ? data : dict;
}
};
const validateEnabled = (server, origin) => {
const enabled = servers[server.EndPoint] != null && servers[server.EndPoint].enabled;
const enabled = servers[server.id] != null && servers[server.id].enabled;
if (!enabled) {
origin.Tell('Game interface is not enabled on this server');
origin.tell('Game interface is not enabled on this server');
}
return enabled;
}
};
function isEmpty(value) {
const isEmpty = (value) => {
return value == null || false || value === '' || value === 'null';
}
};

View File

@ -1,90 +0,0 @@
let commands = [{
// required
name: "pingpong",
// required
description: "pongs a ping",
// required
alias: "pp",
// required
permission: "User",
// optional (defaults to false)
targetRequired: false,
// 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.1,
name: 'Ping Pong Sample Command Plugin',
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
this.logger = _serviceResolver.ResolveService("ILogger");
this.logger.WriteDebug("sample plugin loaded");
const intArray = [
1337,
1505,
999
];
const stringArray = [
"ping",
"pong",
"hello"
];
this.configHandler = _configHandler;
this.configHandler.SetValue("SampleIntegerValue", 123);
this.configHandler.SetValue("SampleStringValue", this.author);
this.configHandler.SetValue("SampleFloatValue", this.version);
this.configHandler.SetValue("SampleNumericalArray", intArray);
this.configHandler.SetValue("SampleStringArray", stringArray);
this.logger.WriteDebug(this.configHandler.GetValue("SampleIntegerValue"));
this.logger.WriteDebug(this.configHandler.GetValue("SampleStringValue"));
this.logger.WriteDebug(this.configHandler.GetValue("SampleFloatValue"));
this.configHandler.GetValue("SampleNumericalArray").forEach((element) => {
this.logger.WriteDebug(element);
});
this.configHandler.GetValue("SampleStringArray").forEach((element) => {
this.logger.WriteDebug(element);
});
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -1,39 +0,0 @@
var plugin = {
author: 'RaidMax',
version: 1.1,
name: 'Shared GUID Kicker Plugin',
onEventAsync: function (gameEvent, server) {
// make sure we only check for IW4(x)
if (server.GameName !== 2) {
return false;
}
// connect or join event
if (gameEvent.Type === 3) {
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
if (gameEvent.Origin.NetworkId === -805366929435212061 ||
gameEvent.Origin.NetworkId === 3150799945255696069 ||
gameEvent.Origin.NetworkId === 5859032128210324569 ||
gameEvent.Origin.NetworkId === 2908745942105435771 ||
gameEvent.Origin.NetworkId === -6492697076432899192 ||
gameEvent.Origin.NetworkId === 1145760003260769995 ||
gameEvent.Origin.NetworkId === -7102887284306116957 ||
gameEvent.Origin.NetworkId === 3474936520447289592 ||
gameEvent.Origin.NetworkId === -1168897558496584395 ||
gameEvent.Origin.NetworkId === 8348020621355817691 ||
gameEvent.Origin.NetworkId === 3259219574061214058 ||
gameEvent.Origin.NetworkId === 3304388024725980231) {
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);
}
}
},
onLoadAsync: function (manager) {
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -3,151 +3,92 @@ const validCIDR = input => cidrRegex.test(input);
const subnetBanlistKey = 'Webfront::Nav::Admin::SubnetBanlist';
let subnetList = [];
const commands = [{
name: "bansubnet",
description: "bans an IPv4 subnet",
alias: "bs",
permission: "SeniorAdmin",
targetRequired: false,
arguments: [{
name: "subnet in IPv4 CIDR notation",
required: true
}],
const init = (registerNotify, serviceResolver, config) => {
registerNotify('IManagementEventSubscriptions.ClientStateAuthorized', (authorizedEvent, _) => plugin.onClientAuthorized(authorizedEvent));
execute: (gameEvent) => {
const input = String(gameEvent.Data).trim();
plugin.onLoad(serviceResolver, config);
return plugin;
};
if (!validCIDR(input)) {
gameEvent.Origin.Tell('Invalid CIDR input');
return;
}
const plugin = {
author: 'RaidMax',
version: '2.0',
name: 'Subnet Banlist Plugin',
manager: null,
logger: null,
config: null,
serviceResolver: null,
banMessage: '',
subnetList.push(input);
_configHandler.SetValue('SubnetBanList', subnetList);
gameEvent.Origin.Tell(`Added ${input} to subnet banlist`);
}
},
{
name: 'unbansubnet',
description: 'unbans an IPv4 subnet',
alias: 'ubs',
commands: [{
name: 'bansubnet',
description: 'bans an IPv4 subnet',
alias: 'bs',
permission: 'SeniorAdmin',
targetRequired: false,
arguments: [{
name: 'subnet in IPv4 CIDR notation',
required: true
}],
execute: (gameEvent) => {
const input = String(gameEvent.Data).trim();
const input = String(gameEvent.data).trim();
if (!validCIDR(input)) {
gameEvent.Origin.Tell('Invalid CIDR input');
gameEvent.origin.tell('Invalid CIDR input');
return;
}
if (!subnetList.includes(input)) {
gameEvent.Origin.Tell('Subnet is not banned');
return;
}
subnetList.push(input);
plugin.config.setValue('SubnetBanList', subnetList);
subnetList = subnetList.filter(item => item !== input);
_configHandler.SetValue('SubnetBanList', subnetList);
gameEvent.Origin.Tell(`Removed ${input} from subnet banlist`);
}
}];
convertIPtoLong = ip => {
let components = String(ip).match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
if (components) {
let ipLong = 0;
let power = 1;
for (let i = 4; i >= 1; i -= 1) {
ipLong += power * parseInt(components[i]);
power *= 256;
}
return ipLong;
} else {
return -1;
}
};
isInSubnet = (ip, subnet) => {
const mask = subnet.match(/^(.*?)\/(\d{1,2})$/);
if (!mask) {
return false;
}
const baseIP = convertIPtoLong(mask[1]);
const longIP = convertIPtoLong(ip);
if (mask && baseIP >= 0) {
const freedom = Math.pow(2, 32 - parseInt(mask[2]));
return (longIP > baseIP) && (longIP < baseIP + freedom - 1);
} else return false;
};
isSubnetBanned = (ip, list) => {
const matchingSubnets = list.filter(subnet => isInSubnet(ip, subnet));
return matchingSubnets.length !== 0;
}
const plugin = {
author: 'RaidMax',
version: 1.1,
name: 'Subnet Banlist Plugin',
manager: null,
logger: null,
banMessage: '',
onEventAsync: (gameEvent, server) => {
if (gameEvent.TypeName === 'Join') {
if (!isSubnetBanned(gameEvent.Origin.IPAddressString, subnetList, this.logger)) {
return;
}
this.logger.WriteInfo(`Kicking ${gameEvent.Origin} because they are subnet banned.`);
gameEvent.Origin.Kick(this.banMessage, _IW4MAdminClient);
gameEvent.origin.tell(`Added ${input} to subnet banlist`);
}
},
onLoadAsync: manager => {
this.manager = manager;
this.logger = manager.GetLogger(0);
this.configHandler = _configHandler;
subnetList = [];
this.interactionRegistration = _serviceResolver.ResolveService('IInteractionRegistration');
{
name: 'unbansubnet',
description: 'unbans an IPv4 subnet',
alias: 'ubs',
permission: 'SeniorAdmin',
targetRequired: false,
arguments: [{
name: 'subnet in IPv4 CIDR notation',
required: true
}],
execute: (gameEvent) => {
const input = String(gameEvent.data).trim();
const list = this.configHandler.GetValue('SubnetBanList');
if (list !== undefined) {
list.forEach(element => {
const ban = String(element);
subnetList.push(ban)
});
this.logger.WriteInfo(`Loaded ${list.length} banned subnets`);
} else {
this.configHandler.SetValue('SubnetBanList', []);
if (!validCIDR(input)) {
gameEvent.origin.tell('Invalid CIDR input');
return;
}
if (!subnetList.includes(input)) {
gameEvent.origin.tell('Subnet is not banned');
return;
}
subnetList = subnetList.filter(item => item !== input);
plugin.config.setValue('SubnetBanList', subnetList);
gameEvent.origin.tell(`Removed ${input} from subnet banlist`);
}
}
],
this.banMessage = this.configHandler.GetValue('BanMessage');
if (this.banMessage === undefined) {
this.banMessage = 'You are not allowed to join this server.';
this.configHandler.SetValue('BanMessage', this.banMessage);
}
this.interactionRegistration.RegisterScriptInteraction(subnetBanlistKey, plugin.name, (targetId, game, token) => {
interactions: [{
name: subnetBanlistKey,
action: function (_, __, ___) {
const helpers = importNamespace('SharedLibraryCore.Helpers');
const interactionData = new helpers.InteractionData();
interactionData.Name = 'Subnet Banlist'; // navigation link name
interactionData.Description = `List of banned subnets (${subnetList.length} Total)`; // alt and title
interactionData.DisplayMeta = 'oi-circle-x'; // nav icon
interactionData.InteractionId = subnetBanlistKey;
interactionData.MinimumPermission = 3; // moderator
interactionData.InteractionType = 2; // 1 is RawContent for apis etc..., 2 is
interactionData.Source = plugin.name;
interactionData.name = 'Subnet Banlist'; // navigation link name
interactionData.description = `List of banned subnets (${subnetList.length} Total)`; // alt and title
interactionData.displayMeta = 'oi-circle-x'; // nav icon
interactionData.interactionId = subnetBanlistKey;
interactionData.minimumPermission = 3;
interactionData.interactionType = 2;
interactionData.source = plugin.name;
interactionData.ScriptAction = (sourceId, targetId, game, meta, token) => {
let table = '<table class="table bg-dark-dm bg-light-lm">';
@ -160,7 +101,7 @@ const plugin = {
};
subnetList.forEach(subnet => {
unbanSubnetInteraction.Data += ' ' + subnet
unbanSubnetInteraction.Data += ' ' + subnet;
table += `<tr>
<td>
<p>${subnet}</p>
@ -180,16 +121,84 @@ const plugin = {
table += '</table>';
return table;
}
};
return interactionData;
});
}
}],
onLoad: function (serviceResolver, config) {
this.serviceResolver = serviceResolver;
this.config = config;
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
subnetList = [];
const list = this.config.getValue('SubnetBanList');
if (list !== undefined) {
list.forEach(element => {
const ban = String(element);
subnetList.push(ban);
});
this.logger.logInformation('Loaded {Count} banned subnets', list.length);
} else {
this.config.setValue('SubnetBanList', []);
}
this.banMessage = this.config.getValue('BanMessage');
if (this.banMessage === undefined) {
this.banMessage = 'You are not allowed to join this server.';
this.config.setValue('BanMessage', this.banMessage);
}
const interactionRegistration = serviceResolver.resolveService('IInteractionRegistration');
interactionRegistration.unregisterInteraction(subnetBanlistKey);
this.logger.logInformation('Subnet Ban loaded');
},
onUnloadAsync: () => {
this.interactionRegistration.UnregisterInteraction(subnetBanlistKey);
},
onClientAuthorized: (clientEvent) => {
if (!isSubnetBanned(clientEvent.client.ipAddressString, subnetList)) {
return;
}
onTickAsync: server => {
this.logger.logInformation(`Kicking {Client} because they are subnet banned.`, clientEvent.client);
clientEvent.client.kick(this.banMessage, clientEvent.client.currentServer.asConsoleClient());
}
};
const convertIPtoLong = ip => {
let components = String(ip).match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
if (components) {
let ipLong = 0;
let power = 1;
for (let i = 4; i >= 1; i -= 1) {
ipLong += power * parseInt(components[i]);
power *= 256;
}
return ipLong;
} else {
return -1;
}
};
const isInSubnet = (ip, subnet) => {
const mask = subnet.match(/^(.*?)\/(\d{1,2})$/);
if (!mask) {
return false;
}
const baseIP = convertIPtoLong(mask[1]);
const longIP = convertIPtoLong(ip);
if (mask && baseIP >= 0) {
const freedom = Math.pow(2, 32 - parseInt(mask[2]));
return (longIP > baseIP) && (longIP < baseIP + freedom - 1);
} else return false;
};
const isSubnetBanned = (ip, list) => {
const matchingSubnets = list.filter(subnet => isInSubnet(ip, subnet));
return matchingSubnets.length !== 0;
};

View File

@ -2,22 +2,23 @@ let vpnExceptionIds = [];
const vpnAllowListKey = 'Webfront::Nav::Admin::VPNAllowList';
const vpnWhitelistKey = 'Webfront::Profile::VPNWhitelist';
const init = (registerNotify, serviceResolver, config) => {
registerNotify('IManagementEventSubscriptions.ClientStateAuthorized', (authorizedEvent, _) => plugin.onClientAuthorized(authorizedEvent));
const init = (registerNotify, serviceResolver, config, pluginHelper) => {
registerNotify('IManagementEventSubscriptions.ClientStateAuthorized', (authorizedEvent, token) => plugin.onClientAuthorized(authorizedEvent, token));
plugin.onLoad(serviceResolver, config);
plugin.onLoad(serviceResolver, config, pluginHelper);
return plugin;
};
const plugin = {
author: 'RaidMax',
version: '1.6',
version: '2.0',
name: 'VPN Detection Plugin',
manager: null,
config: null,
logger: null,
serviceResolver: null,
translations: null,
pluginHelper: null,
commands: [{
name: 'whitelistvpn',
@ -58,7 +59,7 @@ const plugin = {
interactions: [{
// registers the profile action
name: vpnWhitelistKey,
action: function(targetId, game, token) {
action: function (targetId, game, token) {
const helpers = importNamespace('SharedLibraryCore.Helpers');
const interactionData = new helpers.InteractionData();
@ -91,7 +92,7 @@ const plugin = {
},
{
name: vpnAllowListKey,
action: function(targetId, game, token) {
action: function (targetId, game, token) {
const helpers = importNamespace('SharedLibraryCore.Helpers');
const interactionData = new helpers.InteractionData();
@ -146,13 +147,17 @@ const plugin = {
}
],
onClientAuthorized: function(authorizeEvent) {
this.checkForVpn(authorizeEvent.client);
onClientAuthorized: async function (authorizeEvent, token) {
if (authorizeEvent.client.isBot) {
return;
}
await this.checkForVpn(authorizeEvent.client, token);
},
onLoad: function(serviceResolver, config) {
onLoad: function (serviceResolver, config, pluginHelper) {
this.serviceResolver = serviceResolver;
this.config = config;
this.pluginHelper = pluginHelper;
this.manager = this.serviceResolver.resolveService('IManager');
this.logger = this.serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
this.translations = this.serviceResolver.resolveService('ITranslationLookup');
@ -166,10 +171,10 @@ const plugin = {
this.interactionRegistration.unregisterInteraction(vpnAllowListKey);
},
checkForVpn: function(origin) {
checkForVpn: async function (origin, token) {
let exempt = false;
// prevent players that are exempt from being kicked
vpnExceptionIds.forEach(function(id) {
vpnExceptionIds.forEach(function (id) {
if (parseInt(id) === parseInt(origin.clientId)) {
exempt = true;
return false;
@ -181,25 +186,40 @@ const plugin = {
return;
}
let usingVPN = false;
try {
const cl = new System.Net.Http.HttpClient();
const re = cl.getAsync(`https://api.xdefcon.com/proxy/check/?ip=${origin.IPAddressString}`).result;
const userAgent = `IW4MAdmin-${this.manager.getApplicationSettings().configuration().id}`;
cl.defaultRequestHeaders.add('User-Agent', userAgent);
const co = re.content;
const parsedJSON = JSON.parse(co.readAsStringAsync().result);
co.dispose();
re.dispose();
cl.dispose();
usingVPN = parsedJSON.success && parsedJSON.proxy;
} catch (ex) {
this.logger.logWarning('There was a problem checking client IP for VPN {message}', ex.message);
if (origin.IPAddressString === null) {
this.logger.logDebug('{Client} does not have an IP Address yet, so we are no checking their VPN status', origin);
}
const userAgent = `IW4MAdmin-${this.manager.getApplicationSettings().configuration().id}`;
const headers = {
'User-Agent': userAgent
};
try {
this.pluginHelper.getUrl(`https://api.xdefcon.com/proxy/check/?ip=${origin.IPAddressString}`, headers,
(response) => this.onVpnResponse(response, origin));
} catch (ex) {
this.logger.logWarning('There was a problem checking client IP ({IP}) for VPN - {message}',
origin.IPAddressString, ex.message);
}
},
onVpnResponse: function (response, origin) {
let parsedJSON = null;
try {
parsedJSON = JSON.parse(response);
} catch {
this.logger.logWarning('There was a problem checking client IP ({IP}) for VPN - {message}',
origin.IPAddressString, response);
return;
}
const usingVPN = parsedJSON.success && parsedJSON.proxy;
if (usingVPN) {
this.logger.logInformation('{origin} is using a VPN ({ip})', origin.toString(), origin.ipAddressString);
this.logger.logInformation('{origin} is using a VPN ({ip})', origin.toString(), origin.IPAddressString);
const contactUrl = this.manager.getApplicationSettings().configuration().contactUri;
let additionalInfo = '';
if (contactUrl) {
@ -207,11 +227,11 @@ const plugin = {
}
origin.kick(this.translations['SERVER_KICK_VPNS_NOTALLOWED'] + ' ' + additionalInfo, origin.currentServer.asConsoleClient());
} else {
this.logger.logDebug('{client} is not using a VPN', origin);
this.logger.logDebug('{Client} is not using a VPN', origin);
}
},
getClientsData: function(clientIds) {
getClientsData: function (clientIds) {
const contextFactory = this.serviceResolver.resolveService('IDatabaseContextFactory');
const context = contextFactory.createContext(false);
const clientSet = context.clients;