Rewrite of plugin system

This commit is contained in:
Thomas Smyth 2020-12-03 14:16:07 +00:00
parent 86d0878174
commit dd685ecd80
23 changed files with 546 additions and 486 deletions

View File

@ -384,23 +384,6 @@ The following is a list of plugins built into SquadJS, you can click their title
]</code></pre>
</details>
<details>
<summary>DiscordPlaceholder</summary>
<h2>DiscordPlaceholder</h2>
<p>The <code>DiscordPlaceholder</code> plugin can be used to create placeholder messages in Discord for use by other plugins.</p>
<h3>Options</h3>
<h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre>
<h4>command</h4>
<h6>Description</h6>
<p>Command that triggers the placeholder message.</p>
<h6>Default</h6>
<pre><code>!placeholder</code></pre>
</details>
<details>
<summary>DiscordRcon</summary>
<h2>DiscordRcon</h2>

View File

@ -123,12 +123,6 @@
"channelID": "",
"events": []
},
{
"plugin": "DiscordPlaceholder",
"enabled": true,
"discordClient": "discord",
"command": "!placeholder"
},
{
"plugin": "DiscordRcon",
"enabled": false,

View File

@ -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();

View File

@ -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 {
<p>${option.description}</p>
<h6>Default</h6>
<pre><code>${
typeof option.default === 'object'
? JSON.stringify(option.default, null, 2)
: option.default
}</code></pre>`;
typeof option.default === 'object'
? JSON.stringify(option.default, null, 2)
: option.default
}</code></pre>`;
if (option.example)
optionInfo += `<h6>Example</h6>
<pre><code>${
typeof option.example === 'object'
? JSON.stringify(option.example, null, 2)
: option.example
}</code></pre>`;
typeof option.example === 'object'
? JSON.stringify(option.example, null, 2)
: option.example
}</code></pre>`;
options.push(optionInfo);
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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()
}
});
}
}

View File

@ -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];
}
}

View File

@ -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.`
);
}
}

View File

@ -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);
}

View File

@ -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()
}
});
}
}

View File

@ -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)}\`\`\``);

View File

@ -1,42 +0,0 @@
import BasePlugin from './base-plugin.js';
export default class DiscordPlaceholder extends BasePlugin {
static get description() {
return (
'The <code>DiscordPlaceholder</code> 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.');
});
}
}

View File

@ -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) {

View File

@ -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()
}
});
}
}

View File

@ -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() {

View File

@ -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.');
}
}
}

View File

@ -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()
}
});
}
}

View File

@ -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,

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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';
}
}
}