From dd685ecd8037ecc9c2e6e50f5e72fc451d48bbd5 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Thu, 3 Dec 2020 14:16:07 +0000 Subject: [PATCH] Rewrite of plugin system --- README.md | 17 --- config.json | 6 - index.js | 5 + squad-server/factory.js | 107 +++++++-------- squad-server/plugins/auto-tk-warn.js | 20 ++- squad-server/plugins/base-plugin.js | 33 ++++- squad-server/plugins/chat-commands.js | 4 +- .../plugins/discord-admin-broadcast.js | 42 +++--- .../plugins/discord-admin-cam-logs.js | 124 ++++++++++-------- squad-server/plugins/discord-admin-request.js | 116 ++++++++-------- squad-server/plugins/discord-base-plugin.js | 14 +- squad-server/plugins/discord-chat.js | 72 +++++----- squad-server/plugins/discord-debug.js | 4 +- squad-server/plugins/discord-placeholder.js | 42 ------ squad-server/plugins/discord-rcon.js | 68 ++++++---- squad-server/plugins/discord-round-winner.js | 42 +++--- squad-server/plugins/discord-server-status.js | 46 ++++--- .../plugins/discord-subsystem-restarter.js | 60 +++++---- squad-server/plugins/discord-teamkill.js | 94 +++++++------ squad-server/plugins/index.js | 2 - .../plugins/intervalled-broadcasts.js | 22 +++- squad-server/plugins/seeding-mode.js | 40 +++--- squad-server/plugins/team-randomizer.js | 52 +++++--- 23 files changed, 546 insertions(+), 486 deletions(-) delete mode 100644 squad-server/plugins/discord-placeholder.js diff --git a/README.md b/README.md index db8df1b..dcfd18a 100644 --- a/README.md +++ b/README.md @@ -384,23 +384,6 @@ The following is a list of plugins built into SquadJS, you can click their title ] -
- DiscordPlaceholder -

DiscordPlaceholder

-

The DiscordPlaceholder plugin can be used to create placeholder messages in Discord for use by other plugins.

-

Options

-

discordClient (Required)

-
Description
-

Discord connector name.

-
Default
-
discord
-

command

-
Description
-

Command that triggers the placeholder message.

-
Default
-
!placeholder
-
-
DiscordRcon

DiscordRcon

diff --git a/config.json b/config.json index 574def2..8117c35 100644 --- a/config.json +++ b/config.json @@ -123,12 +123,6 @@ "channelID": "", "events": [] }, - { - "plugin": "DiscordPlaceholder", - "enabled": true, - "discordClient": "discord", - "command": "!placeholder" - }, { "plugin": "DiscordRcon", "enabled": false, diff --git a/index.js b/index.js index cd9e390..64b1aed 100644 --- a/index.js +++ b/index.js @@ -8,11 +8,16 @@ async function main() { const configPath = process.argv[2]; if (config && configPath) throw new Error('Cannot accept both a config and config path.'); + // create a SquadServer instance const server = config ? await SquadServerFactory.buildFromConfigString(config) : await SquadServerFactory.buildFromConfigFile(configPath || './config.json'); + // watch the server await server.watch(); + + // now mount the plugins + server.plugins.forEach(plugin => plugin.mount()); } main(); diff --git a/squad-server/factory.js b/squad-server/factory.js index 0e8185a..6879af0 100644 --- a/squad-server/factory.js +++ b/squad-server/factory.js @@ -14,17 +14,19 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default class SquadServerFactory { static async buildFromConfig(config) { - // Setup logging levels + // setup logging levels for (const [module, verboseness] of Object.entries(config.verboseness)) { Logger.setVerboseness(module, verboseness); } + // create SquadServer Logger.verbose('SquadServerFactory', 1, 'Creating SquadServer...'); const server = new SquadServer(config.server); // pull layers read to use to create layer filter connectors await server.squadLayers.pull(); + // initialise connectors Logger.verbose('SquadServerFactory', 1, 'Preparing connectors...'); const connectors = {}; for (const pluginConfig of config.plugins) { @@ -36,82 +38,63 @@ export default class SquadServerFactory { // ignore non connectors if (!option.connector) continue; - if (!(optionName in pluginConfig)) - throw new Error( - `${Plugin.name}: ${optionName} (${option.connector} connector) is missing.` - ); + // check the connector is listed in the options + if (!(optionName in pluginConfig)) throw new Error(`${Plugin.name}: ${optionName} (${option.connector} connector) is missing.`); + // get the name of the connector const connectorName = pluginConfig[optionName]; // skip already created connectors if (connectors[connectorName]) continue; - const connectorConfig = config.connectors[connectorName]; - - if (option.connector === 'discord') { - Logger.verbose('SquadServerFactory', 1, `Starting discord connector ${connectorName}...`); - connectors[connectorName] = new Discord.Client(); - await connectors[connectorName].login(connectorConfig); - } else if (option.connector === 'mysql') { - Logger.verbose( - 'SquadServerFactory', - 1, - `Starting mysqlPool connector ${connectorName}...` - ); - connectors[connectorName] = mysql.createPool(connectorConfig); - } else if (option.connector === 'squadlayerpool') { - Logger.verbose( - 'SquadServer', - 1, - `Starting squadlayerfilter connector ${connectorName}...` - ); - connectors[connectorName] = server.squadLayers[connectorConfig.type]( - connectorConfig.filter, - connectorConfig.activeLayerFilter - ); - } else { - throw new Error(`${option.connector} is an unsupported connector type.`); - } + // create the connector + connectors[connectorName] = await SquadServerFactory.createConnector(server, option.connector, connectorName, config.connectors[connectorName]) } } - Logger.verbose('SquadServerFactory', 1, 'Applying plugins to SquadServer...'); - for (const pluginConfig of config.plugins) { + // initialise plugins + Logger.verbose('SquadServerFactory', 1, 'Initialising plugins...'); + + for(const pluginConfig of config.plugins) { if (!pluginConfig.enabled) continue; - if (!plugins[pluginConfig.plugin]) - throw new Error(`Plugin ${pluginConfig.plugin} does not exist.`); + if (!plugins[pluginConfig.plugin]) throw new Error(`Plugin ${pluginConfig.plugin} does not exist.`); const Plugin = plugins[pluginConfig.plugin]; Logger.verbose('SquadServerFactory', 1, `Initialising ${Plugin.name}...`); - const options = {}; - for (const [optionName, option] of Object.entries(Plugin.optionsSpecification)) { - if (option.connector) { - options[optionName] = connectors[pluginConfig[optionName]]; - } else { - if (option.required) { - if (!(optionName in pluginConfig)) - throw new Error(`${Plugin.name}: ${optionName} is required but missing.`); - if (option.default === pluginConfig[optionName]) - throw new Error( - `${Plugin.name}: ${optionName} is required but is the default value.` - ); - } + const plugin = new Plugin(server, pluginConfig, connectors); - options[optionName] = pluginConfig[optionName] || option.default; - } - } + // allow the plugin to do any asynchronous work needed before it can be mounted + await plugin.prepareToMount(); - server.plugins.push(new Plugin(server, options, pluginConfig)); + server.plugins.push(plugin); } - Logger.verbose('SquadServerFactory', 1, 'SquadServer built.'); - return server; } + static async createConnector(server, type, connectorName, connectorConfig) { + Logger.verbose('SquadServerFactory', 1, `Starting ${type} connector ${connectorName}...`); + + if (type === 'discord') { + const connector = new Discord.Client(); + await connector.login(connectorConfig); + return connector; + } + + if (type === 'mysql') { + return mysql.createPool(connectorConfig); + } + + if (type === 'squadlayerpool') { + return server.squadLayers[connectorConfig.type](connectorConfig.filter, connectorConfig.activeLayerFilter); + } + + throw new Error(`${type.connector} is an unsupported connector type.`); + } + static parseConfig(configString) { try { return JSON.parse(configString); @@ -182,18 +165,18 @@ export default class SquadServerFactory {

${option.description}

Default
${
-             typeof option.default === 'object'
-               ? JSON.stringify(option.default, null, 2)
-               : option.default
-           }
`; + typeof option.default === 'object' + ? JSON.stringify(option.default, null, 2) + : option.default + }`; if (option.example) optionInfo += `
Example
${
-             typeof option.example === 'object'
-               ? JSON.stringify(option.example, null, 2)
-               : option.example
-           }
`; + typeof option.example === 'object' + ? JSON.stringify(option.example, null, 2) + : option.example + }`; options.push(optionInfo); } diff --git a/squad-server/plugins/auto-tk-warn.js b/squad-server/plugins/auto-tk-warn.js index 9af4f9c..b5bee9b 100644 --- a/squad-server/plugins/auto-tk-warn.js +++ b/squad-server/plugins/auto-tk-warn.js @@ -19,11 +19,21 @@ export default class AutoTKWarn extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.server.on('TEAMKILL', async (info) => { - await this.server.rcon.warn(info.attacker.steamID, this.options.message); - }); + this.onTeamkill = this.onTeamkill.bind(this); + } + + mount() { + this.server.on('TEAMKILL', this.onTeamkill); + } + + unmount() { + this.server.removeEventListener('TEAMKILL', this.onTeamkill); + } + + async onTeamkill(info) { + await this.server.rcon.warn(info.attacker.steamID, this.options.message); } } diff --git a/squad-server/plugins/base-plugin.js b/squad-server/plugins/base-plugin.js index c1e9f35..a1a6600 100644 --- a/squad-server/plugins/base-plugin.js +++ b/squad-server/plugins/base-plugin.js @@ -1,6 +1,31 @@ import Logger from 'core/logger'; export default class BasePlugin { + constructor(server, options, connectors) { + this.server = server; + this.options = {}; + this.rawOptions = options; + + for (const [optionName, option] of Object.entries(this.constructor.optionsSpecification)) { + if (option.connector) { + this.options[optionName] = connectors[this.rawOptions[optionName]]; + } else { + if (option.required) { + if (!(optionName in this.rawOptions)) throw new Error(`${this.constructor.name}: ${optionName} is required but missing.`); + if (option.default === this.rawOptions[optionName]) throw new Error(`${this.constructor.name}: ${optionName} is required but is the default value.`); + } + + this.options[optionName] = this.rawOptions[optionName] || option.default; + } + } + } + + async prepareToMount() {} + + mount() {} + + unmount() {} + static get description() { throw new Error('Plugin missing "static get description()" method.'); } @@ -13,13 +38,7 @@ export default class BasePlugin { throw new Error('Plugin missing "static get optionSpecification()" method.'); } - constructor(server, options = {}, optionsRaw = {}) { - this.server = server; - this.options = options; - this.optionsRaw = optionsRaw; - } - verbose(...args) { Logger.verbose(this.constructor.name, ...args); } -} +} \ No newline at end of file diff --git a/squad-server/plugins/chat-commands.js b/squad-server/plugins/chat-commands.js index 8b730aa..13e0610 100644 --- a/squad-server/plugins/chat-commands.js +++ b/squad-server/plugins/chat-commands.js @@ -36,9 +36,7 @@ export default class ChatCommands extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); - + mount() { for (const command of this.options.commands) { this.server.on(`CHAT_COMMAND:${command.command}`, async (data) => { if (command.ignoreChats.includes(data.chat)) return; diff --git a/squad-server/plugins/discord-admin-broadcast.js b/squad-server/plugins/discord-admin-broadcast.js index 79e89c6..91bd136 100644 --- a/squad-server/plugins/discord-admin-broadcast.js +++ b/squad-server/plugins/discord-admin-broadcast.js @@ -29,23 +29,33 @@ export default class DiscordAdminBroadcast extends DiscordBasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.server.on('ADMIN_BROADCAST', async (info) => { - await this.sendDiscordMessage({ - embed: { - title: 'Admin Broadcast', - color: this.options.color, - fields: [ - { - name: 'Message', - value: `${info.message}` - } - ], - timestamp: info.time.toISOString() - } - }); + this.onAdminBroadcast = this.onAdminBroadcast.bind(this); + } + + mount() { + this.server.on('ADMIN_BROADCAST', this.onAdminBroadcast); + } + + unmount() { + this.server.removeEventListener('ADMIN_BROADCAST', this.onAdminBroadcast); + } + + async onAdminBroadcast(info) { + await this.sendDiscordMessage({ + embed: { + title: 'Admin Broadcast', + color: this.options.color, + fields: [ + { + name: 'Message', + value: `${info.message}` + } + ], + timestamp: info.time.toISOString() + } }); } } diff --git a/squad-server/plugins/discord-admin-cam-logs.js b/squad-server/plugins/discord-admin-cam-logs.js index c007042..658c852 100644 --- a/squad-server/plugins/discord-admin-cam-logs.js +++ b/squad-server/plugins/discord-admin-cam-logs.js @@ -26,72 +26,80 @@ export default class DiscordAdminCamLogs extends DiscordBasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); this.adminsInCam = {}; - this.server.on('PLAYER_POSSESS', async (info) => { - if (info.player === null || info.possessClassname !== 'CameraMan') return; + this.onPlayerPossess = this.onPlayerPossess.bind(this); + this.onPlayerUnPossess = this.onPlayerUnPossess.bind(this); + } - this.adminsInCam[info.player.steamID] = info.time; + mount() { + this.server.on('PLAYER_POSSESS', this.onPlayerPossess); + this.server.on('PLAYER_UNPOSSESS', this.onPlayerUnPossess); + } - await this.sendDiscordMessage({ - embed: { - title: `Admin Entered Admin Camera`, - color: this.options.color, - fields: [ - { - name: "Admin's Name", - value: info.player.name, - inline: true - }, - { - name: "Admin's SteamID", - value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`, - inline: true - } - ], - timestamp: info.time.toISOString() - } - }); - }); + unmount() { + this.server.removeEventListener('PLAYER_POSSESS', this.onPlayerPossess); + this.server.removeEventListener('PLAYER_UNPOSSESS', this.onPlayerUnPossess); + } - this.server.on('PLAYER_UNPOSSESS', async (info) => { - if ( - info.player === null || - info.switchPossess === true || - !(info.player.steamID in this.adminsInCam) - ) - return; + async onPlayerPossess(info) { + if (info.player === null || info.possessClassname !== 'CameraMan') return; - await this.sendDiscordMessage({ - embed: { - title: `Admin Left Admin Camera`, - color: this.options.color, - fields: [ - { - name: "Admin's Name", - value: info.player.name, - inline: true - }, - { - name: "Admin's SteamID", - value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`, - inline: true - }, - { - name: 'Time in Admin Camera', - value: `${Math.round( - (info.time.getTime() - this.adminsInCam[info.player.steamID].getTime()) / 60000 - )} mins` - } - ], - timestamp: info.time.toISOString() - } - }); + this.adminsInCam[info.player.steamID] = info.time; - delete this.adminsInCam[info.player.steamID]; + await this.sendDiscordMessage({ + embed: { + title: `Admin Entered Admin Camera`, + color: this.options.color, + fields: [ + { + name: "Admin's Name", + value: info.player.name, + inline: true + }, + { + name: "Admin's SteamID", + value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`, + inline: true + } + ], + timestamp: info.time.toISOString() + } }); } + + async onPlayerUnPossess(info) { + if (info.player === null || info.switchPossess === true || !(info.player.steamID in this.adminsInCam)) return; + + await this.sendDiscordMessage({ + embed: { + title: `Admin Left Admin Camera`, + color: this.options.color, + fields: [ + { + name: "Admin's Name", + value: info.player.name, + inline: true + }, + { + name: "Admin's SteamID", + value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`, + inline: true + }, + { + name: 'Time in Admin Camera', + value: `${Math.round( + (info.time.getTime() - this.adminsInCam[info.player.steamID].getTime()) / 60000 + )} mins` + } + ], + timestamp: info.time.toISOString() + } + }); + + delete this.adminsInCam[info.player.steamID]; + } } diff --git a/squad-server/plugins/discord-admin-request.js b/squad-server/plugins/discord-admin-request.js index 4f93790..dc7abd8 100644 --- a/squad-server/plugins/discord-admin-request.js +++ b/squad-server/plugins/discord-admin-request.js @@ -57,68 +57,78 @@ export default class DiscordAdminRequest extends DiscordBasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.lastPing = Date.now() - this.pingDelay; + this.lastPing = Date.now() - this.options.pingDelay; - this.server.on(`CHAT_COMMAND:${this.options.command}`, async (info) => { - if (this.options.ignoreChats.includes(info.chat)) return; + this.onChatCommand = this.onChatCommand.bind(this); + } - for (const ignorePhrase of this.options.ignorePhrases) { - if (info.message.includes(ignorePhrase)) return; - } + mount() { + this.server.on(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand); + } - if (info.message.length === 0) { - await this.server.rcon.warn( - info.player.steamID, - `Please specify what you would like help with when requesting an admin.` - ); - return; - } + unmount() { + this.server.removeEventListener(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand) + } - const message = { - embed: { - title: `${info.player.name} has requested admin support!`, - color: this.options.color, - fields: [ - { - name: 'Player', - value: info.player.name, - inline: true - }, - { - name: 'SteamID', - value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`, - inline: true - }, - { - name: 'Team & Squad', - value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}` - }, - { - name: 'Message', - value: info.message - } - ], - timestamp: info.time.toISOString() - } - }; + async onChatCommand(info) { + if (this.options.ignoreChats.includes(info.chat)) return; - if ( - this.options.pingGroups.length > 0 && - Date.now() - this.options.pingDelay > this.lastPing - ) { - message.content = this.options.pingGroups.map((groupID) => `<@&${groupID}>`).join(' '); - this.lastPing = Date.now(); - } - - await this.sendDiscordMessage(message); + for (const ignorePhrase of this.options.ignorePhrases) { + if (info.message.includes(ignorePhrase)) return; + } + if (info.message.length === 0) { await this.server.rcon.warn( info.player.steamID, - `An admin has been notified, please wait for us to get back to you.` + `Please specify what you would like help with when requesting an admin.` ); - }); + return; + } + + const message = { + embed: { + title: `${info.player.name} has requested admin support!`, + color: this.options.color, + fields: [ + { + name: 'Player', + value: info.player.name, + inline: true + }, + { + name: 'SteamID', + value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`, + inline: true + }, + { + name: 'Team & Squad', + value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}` + }, + { + name: 'Message', + value: info.message + } + ], + timestamp: info.time.toISOString() + } + }; + + if ( + this.options.pingGroups.length > 0 && + Date.now() - this.options.pingDelay > this.lastPing + ) { + message.content = this.options.pingGroups.map((groupID) => `<@&${groupID}>`).join(' '); + this.lastPing = Date.now(); + } + + await this.sendDiscordMessage(message); + + await this.server.rcon.warn( + info.player.steamID, + `An admin has been notified, please wait for us to get back to you.` + ); } } diff --git a/squad-server/plugins/discord-base-plugin.js b/squad-server/plugins/discord-base-plugin.js index 2e276e6..d01180b 100644 --- a/squad-server/plugins/discord-base-plugin.js +++ b/squad-server/plugins/discord-base-plugin.js @@ -14,18 +14,12 @@ export default class DiscordBasePlugin extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); - - this.channel = null; + async prepareToMount() { + this.channel = await this.options.discordClient.channels.fetch(this.options.channelID); } - async sendDiscordMessage(message, channelID = this.options.channelID) { - if (this.channel === null) - this.channel = await this.options.discordClient.channels.fetch(channelID); - - if (typeof message === 'object' && 'embed' in message) - message.embed.footer = { text: COPYRIGHT_MESSAGE }; + async sendDiscordMessage(message) { + if (typeof message === 'object' && 'embed' in message) message.embed.footer = { text: COPYRIGHT_MESSAGE }; await this.channel.send(message); } diff --git a/squad-server/plugins/discord-chat.js b/squad-server/plugins/discord-chat.js index 5ec628d..02f2858 100644 --- a/squad-server/plugins/discord-chat.js +++ b/squad-server/plugins/discord-chat.js @@ -37,39 +37,49 @@ export default class DiscordChat extends DiscordBasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.server.on('CHAT_MESSAGE', async (info) => { - if (this.options.ignoreChats.includes(info.chat)) return; + this.onChatMessage = this.onChatMessage.bind(this); + } - await this.sendDiscordMessage({ - embed: { - title: info.chat, - color: this.options.chatColors[info.chat] || this.options.color, - fields: [ - { - name: 'Player', - value: info.player.name, - inline: true - }, - { - name: 'SteamID', - value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.steamID})`, - inline: true - }, - { - name: 'Team & Squad', - value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}` - }, - { - name: 'Message', - value: `${info.message}` - } - ], - timestamp: info.time.toISOString() - } - }); + mount() { + this.server.on('CHAT_MESSAGE', this.onChatMessage); + } + + unmount() { + this.server.removeEventListener('CHAT_MESSAGE', this.onChatMessage); + } + + async onChatMessage(info) { + if (this.options.ignoreChats.includes(info.chat)) return; + + await this.sendDiscordMessage({ + embed: { + title: info.chat, + color: this.options.chatColors[info.chat] || this.options.color, + fields: [ + { + name: 'Player', + value: info.player.name, + inline: true + }, + { + name: 'SteamID', + value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.steamID})`, + inline: true + }, + { + name: 'Team & Squad', + value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}` + }, + { + name: 'Message', + value: `${info.message}` + } + ], + timestamp: info.time.toISOString() + } }); } } diff --git a/squad-server/plugins/discord-debug.js b/squad-server/plugins/discord-debug.js index 7b6a3bd..8175f06 100644 --- a/squad-server/plugins/discord-debug.js +++ b/squad-server/plugins/discord-debug.js @@ -30,9 +30,7 @@ export default class DiscordDebug extends DiscordBasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); - + mount() { for (const event of this.options.events) { this.server.on(event, async (info) => { await this.sendDiscordMessage(`\`\`\`${JSON.stringify({ ...info, event }, null, 2)}\`\`\``); diff --git a/squad-server/plugins/discord-placeholder.js b/squad-server/plugins/discord-placeholder.js deleted file mode 100644 index ce9136a..0000000 --- a/squad-server/plugins/discord-placeholder.js +++ /dev/null @@ -1,42 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class DiscordPlaceholder extends BasePlugin { - static get description() { - return ( - 'The DiscordPlaceholder plugin can be used to create placeholder messages in Discord for use by ' + - 'other plugins.' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - discordClient: { - required: true, - description: 'Discord connector name.', - connector: 'discord', - default: 'discord' - }, - command: { - required: false, - description: 'Command that triggers the placeholder message.', - default: '!placeholder' - } - }; - } - - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); - - this.options.discordClient.on('message', async (message) => { - // check the author of the message is not a bot - if (message.author.bot || !message.content.toLowerCase().startsWith(this.options.command)) - return; - - await message.channel.send('Placeholder.'); - }); - } -} diff --git a/squad-server/plugins/discord-rcon.js b/squad-server/plugins/discord-rcon.js index 572a0fb..7e0763d 100644 --- a/squad-server/plugins/discord-rcon.js +++ b/squad-server/plugins/discord-rcon.js @@ -47,44 +47,54 @@ export default class DiscordRcon extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.options.discordClient.on('message', async (message) => { - // check the author of the message is not a bot and that the channel is the RCON console channel - if (message.author.bot || message.channel.id !== this.channelID) return; + this.onMessage = this.onMessage.bind(this); + } - let command = message.content; + mount() { + this.options.discordClient.on('message', this.onMessage); + } - // write admin's name into broadcast command if prependAdminNameInBroadcast is enabled - if (this.options.prependAdminNameInBroadcast) - command = command.replace( - /^AdminBroadcast /i, - `AdminBroadcast ${message.member.displayName}: ` - ); + unmount() { + this.options.discordClient.removeEventListener('message', this.onMessage); + } - // check the admin has permissions - if (Object.keys(this.options.permissions).length !== 0) { - const commandPrefix = command.match(/([^ ]+)/); + async onMessage(message) { + // check the author of the message is not a bot and that the channel is the RCON console channel + if (message.author.bot || message.channel.id !== this.channelID) return; - let hasPermission = false; - for (const [role, allowedCommands] of Object.entries(this.options.permissions)) { - if (!message.member._roles.includes(role)) continue; + let command = message.content; - for (const allowedCommand of allowedCommands) - if (commandPrefix[1].toLowerCase() === allowedCommand.toLowerCase()) - hasPermission = true; - } + // write admin's name into broadcast command if prependAdminNameInBroadcast is enabled + if (this.options.prependAdminNameInBroadcast) + command = command.replace( + /^AdminBroadcast /i, + `AdminBroadcast ${message.member.displayName}: ` + ); - if (!hasPermission) { - await message.reply('you do not have permission to run that command.'); - return; - } + // check the admin has permissions + if (Object.keys(this.options.permissions).length !== 0) { + const commandPrefix = command.match(/([^ ]+)/); + + let hasPermission = false; + for (const [role, allowedCommands] of Object.entries(this.options.permissions)) { + if (!message.member._roles.includes(role)) continue; + + for (const allowedCommand of allowedCommands) + if (commandPrefix[1].toLowerCase() === allowedCommand.toLowerCase()) + hasPermission = true; } - // execute command and print response - await this.respondToMessage(message, await this.server.rcon.execute(command)); - }); + if (!hasPermission) { + await message.reply('you do not have permission to run that command.'); + return; + } + } + + // execute command and print response + await this.respondToMessage(message, await this.server.rcon.execute(command)); } async respondToMessage(message, response) { diff --git a/squad-server/plugins/discord-round-winner.js b/squad-server/plugins/discord-round-winner.js index faf027d..89c858a 100644 --- a/squad-server/plugins/discord-round-winner.js +++ b/squad-server/plugins/discord-round-winner.js @@ -26,23 +26,33 @@ export default class DiscordRoundWinner extends DiscordBasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.server.on('NEW_GAME', async (info) => { - await this.sendDiscordMessage({ - embed: { - title: 'Round Winner', - color: this.options.color, - fields: [ - { - name: 'Message', - value: `${info.winner} won on ${info.layer}.` - } - ], - timestamp: info.time.toISOString() - } - }); + this.onNewGame = this.onNewGame.bind(this); + } + + mount() { + this.server.on('NEW_GAME', this.onNewGame); + } + + unmount() { + this.server.removeEventListener('NEW_GAME', this.onNewGame); + } + + async onNewGame(info) { + await this.sendDiscordMessage({ + embed: { + title: 'Round Winner', + color: this.options.color, + fields: [ + { + name: 'Message', + value: `${info.winner} won on ${info.layer}.` + } + ], + timestamp: info.time.toISOString() + } }); } } diff --git a/squad-server/plugins/discord-server-status.js b/squad-server/plugins/discord-server-status.js index 8e6a955..0c47028 100644 --- a/squad-server/plugins/discord-server-status.js +++ b/squad-server/plugins/discord-server-status.js @@ -41,28 +41,38 @@ export default class DiscordServerStatus extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - setInterval(async () => { - for (const messageID of this.options.messageIDs) { - try { - const channel = await this.options.discordClient.channels.fetch(messageID.channelID); - const message = await channel.messages.fetch(messageID.messageID); + this.update = this.update.bind(this); + } - await message.edit(this.getEmbed()); - } catch (err) { - console.log(err); - } + mount() { + this.interval = setInterval(this.update, this.options.updateInterval); + } + + unmount() { + clearInterval(this.interval); + } + + async update() { + for (const messageID of this.options.messageIDs) { + try { + const channel = await this.options.discordClient.channels.fetch(messageID.channelID); + const message = await channel.messages.fetch(messageID.messageID); + + await message.edit(this.getEmbed()); + } catch (err) { + console.log(err); } + } - await this.options.discordClient.user.setActivity( - `(${this.server.a2sPlayerCount}/${this.server.publicSlots}) ${ - this.server.layerHistory[0].layer || 'Unknown' - }`, - { type: 'WATCHING' } - ); - }, this.options.updateInterval); + await this.options.discordClient.user.setActivity( + `(${this.server.a2sPlayerCount}/${this.server.publicSlots}) ${ + this.server.layerHistory[0].layer || 'Unknown' + }`, + { type: 'WATCHING' } + ); } getEmbed() { diff --git a/squad-server/plugins/discord-subsystem-restarter.js b/squad-server/plugins/discord-subsystem-restarter.js index a3bc6be..03a7ad4 100644 --- a/squad-server/plugins/discord-subsystem-restarter.js +++ b/squad-server/plugins/discord-subsystem-restarter.js @@ -33,30 +33,42 @@ export default class DiscordSubsystemRestarter extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.options.discordClient.on('message', async (message) => { - // check the author of the message is not a bot - if (message.author.bot) return; - - if (message.content.match(/!squadjs restartsubsystem rcon/i)) { - if (!message.member._roles.includes(this.options.role)) { - message.reply('you do not have permission to do that.'); - } - - await this.server.restartRCON(); - message.reply('restarted the SquadJS RCON subsystem.'); - } - - if (message.content.match(/!squadjs restartsubsystem logparser/i)) { - if (!message.member._roles.includes(this.options.role)) { - message.reply('you do not have permission to do that.'); - } - - await this.server.restartLogParser(); - message.reply('restarted the SquadJS LogParser subsystem.'); - } - }); + this.onMessage = this.onMessage.bind(this); } + + mount(){ + this.options.discordClient.on('message', this.onMessage); + } + + unmount() { + this.options.discordClient.removeEventListener('message', this.onMessage); + } + + async onMessage(message) { + // check the author of the message is not a bot + if (message.author.bot) return; + + if (message.content.match(/!squadjs restartsubsystem rcon/i)) { + if (!message.member._roles.includes(this.options.role)) { + message.reply('you do not have permission to do that.'); + } + + await this.server.restartRCON(); + message.reply('restarted the SquadJS RCON subsystem.'); + } + + if (message.content.match(/!squadjs restartsubsystem logparser/i)) { + if (!message.member._roles.includes(this.options.role)) { + message.reply('you do not have permission to do that.'); + } + + await this.server.restartLogParser(); + message.reply('restarted the SquadJS LogParser subsystem.'); + } + } + + } diff --git a/squad-server/plugins/discord-teamkill.js b/squad-server/plugins/discord-teamkill.js index 2fa21b5..7260e6a 100644 --- a/squad-server/plugins/discord-teamkill.js +++ b/squad-server/plugins/discord-teamkill.js @@ -34,53 +34,63 @@ export default class DiscordTeamkill extends DiscordBasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.server.on('TEAMKILL', async (info) => { - if (!info.attacker) return; + this.onTeamkill = this.onTeamkill.bind(this); + } - const fields = [ - { - name: "Attacker's Name", - value: info.attacker.name, - inline: true - }, - { - name: "Attacker's SteamID", - value: `[${info.attacker.steamID}](https://steamcommunity.com/profiles/${info.attacker.steamID})`, - inline: true - }, - { - name: 'Weapon', - value: info.weapon - }, - { - name: "Victim's Name", - value: info.victim.name, - inline: true - }, - { - name: "Victim's SteamID", - value: `[${info.victim.steamID}](https://steamcommunity.com/profiles/${info.victim.steamID})`, - inline: true - } - ]; + mount() { + this.server.on('TEAMKILL', this.onTeamkill); + } - if (!this.options.disableSCBL) - fields.push({ - name: 'Squad Community Ban List', - value: `[Attacker's Bans](https://squad-community-ban-list.com/search/${info.attacker.steamID})` - }); + unmount() { + this.server.removeEventListener('TEAMKILL', this.onTeamkill); + } - await this.sendDiscordMessage({ - embed: { - title: `Teamkill: ${info.attacker.name}`, - color: this.options.color, - fields: fields, - timestamp: info.time.toISOString() - } + async onTeamkill(info) { + if (!info.attacker) return; + + const fields = [ + { + name: "Attacker's Name", + value: info.attacker.name, + inline: true + }, + { + name: "Attacker's SteamID", + value: `[${info.attacker.steamID}](https://steamcommunity.com/profiles/${info.attacker.steamID})`, + inline: true + }, + { + name: 'Weapon', + value: info.weapon + }, + { + name: "Victim's Name", + value: info.victim.name, + inline: true + }, + { + name: "Victim's SteamID", + value: `[${info.victim.steamID}](https://steamcommunity.com/profiles/${info.victim.steamID})`, + inline: true + } + ]; + + if (!this.options.disableSCBL) + fields.push({ + name: 'Squad Community Ban List', + value: `[Attacker's Bans](https://squad-community-ban-list.com/search/${info.attacker.steamID})` }); + + await this.sendDiscordMessage({ + embed: { + title: `Teamkill: ${info.attacker.name}`, + color: this.options.color, + fields: fields, + timestamp: info.time.toISOString() + } }); } } diff --git a/squad-server/plugins/index.js b/squad-server/plugins/index.js index 4eb672e..dff935b 100644 --- a/squad-server/plugins/index.js +++ b/squad-server/plugins/index.js @@ -5,7 +5,6 @@ import DiscordAdminCamLogs from './discord-admin-cam-logs.js'; import DiscordAdminRequest from './discord-admin-request.js'; import DiscordChat from './discord-chat.js'; import DiscordDebug from './discord-debug.js'; -import DiscordPlaceholder from './discord-placeholder.js'; import DiscordRcon from './discord-rcon.js'; import DiscordRoundWinner from './discord-round-winner.js'; import DiscordServerStatus from './discord-server-status.js'; @@ -22,7 +21,6 @@ const plugins = [ DiscordAdminRequest, DiscordChat, DiscordDebug, - DiscordPlaceholder, DiscordRcon, DiscordRoundWinner, DiscordServerStatus, diff --git a/squad-server/plugins/intervalled-broadcasts.js b/squad-server/plugins/intervalled-broadcasts.js index 4483744..64774bd 100644 --- a/squad-server/plugins/intervalled-broadcasts.js +++ b/squad-server/plugins/intervalled-broadcasts.js @@ -28,12 +28,22 @@ export default class IntervalledBroadcasts extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - setInterval(async () => { - await this.server.rcon.broadcast(this.options.broadcasts[0]); - this.broadcasts.push(this.options.broadcasts.shift()); - }, this.options.interval); + this.broadcast = this.broadcast.bind(this); + } + + mount() { + this.interval = setInterval(this.broadcast, this.options.interval); + } + + unmount() { + clearInterval(this.interval); + } + + async broadcast() { + await this.server.rcon.broadcast(this.options.broadcasts[0]); + this.broadcasts.push(this.options.broadcasts.shift()); } } diff --git a/squad-server/plugins/seeding-mode.js b/squad-server/plugins/seeding-mode.js index a01d555..fe58ea4 100644 --- a/squad-server/plugins/seeding-mode.js +++ b/squad-server/plugins/seeding-mode.js @@ -48,21 +48,31 @@ export default class SeedingMode extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - setInterval(async () => { - if ( - this.server.a2sPlayerCount !== 0 && - this.server.a2sPlayerCount < this.options.liveThreshold - ) - await this.server.rcon.broadcast(this.options.seedingMessage); - else if ( - this.server.a2sPlayerCount !== 0 && - this.options.liveEnabled && - this.server.a2sPlayerCount < this.options.liveThreshold - ) - await this.server.rcon.broadcast(this.options.liveMessage); - }, this.options.interval); + this.broadcast = this.broadcast.bind(this); + } + + mount() { + this.interval = setInterval(this.broadcast, this.options.interval); + } + + unmount() { + clearInterval(this.interval); + } + + async broadcast() { + if ( + this.server.a2sPlayerCount !== 0 && + this.server.a2sPlayerCount < this.options.liveThreshold + ) + await this.server.rcon.broadcast(this.options.seedingMessage); + else if ( + this.server.a2sPlayerCount !== 0 && + this.options.liveEnabled && + this.server.a2sPlayerCount < this.options.liveThreshold + ) + await this.server.rcon.broadcast(this.options.liveMessage); } } diff --git a/squad-server/plugins/team-randomizer.js b/squad-server/plugins/team-randomizer.js index 55396fe..7907727 100644 --- a/squad-server/plugins/team-randomizer.js +++ b/squad-server/plugins/team-randomizer.js @@ -22,34 +22,44 @@ export default class TeamRandomizer extends BasePlugin { }; } - constructor(server, options, optionsRaw) { - super(server, options, optionsRaw); + constructor(server, options, connectors) { + super(server, options, connectors); - this.server.on(`CHAT_COMMAND:${this.options.command}`, async (info) => { - if (info.chat !== 'ChatAdmin') return; + this.onChatCommand = this.onChatCommand.bind(this); + } - const players = this.server.players.slice(0); + mount() { + this.server.on(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand); + } - let currentIndex = players.length; - let temporaryValue; - let randomIndex; + unmount() { + this.server.removeEventListener(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand); + } - while (currentIndex !== 0) { - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; + async onChatCommand(info) { + if (info.chat !== 'ChatAdmin') return; - temporaryValue = players[currentIndex]; - players[currentIndex] = players[randomIndex]; - players[randomIndex] = temporaryValue; - } + const players = this.server.players.slice(0); - let team = '1'; + let currentIndex = players.length; + let temporaryValue; + let randomIndex; - for (const player of players) { - if (player.teamID !== team) await this.server.rcon.switchTeam(player.steamID); + while (currentIndex !== 0) { + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; - team = team === '1' ? '2' : '1'; - } - }); + temporaryValue = players[currentIndex]; + players[currentIndex] = players[randomIndex]; + players[randomIndex] = temporaryValue; + } + + let team = '1'; + + for (const player of players) { + if (player.teamID !== team) await this.server.rcon.switchTeam(player.steamID); + + team = team === '1' ? '2' : '1'; + } } }