mirror of
https://github.com/AsgardEternal/SquadJS.git
synced 2024-09-28 11:54:23 -05:00
Merge branch 'v2' into auto-kick-afk
This commit is contained in:
commit
fae5dd4efa
82
README.md
82
README.md
@ -78,13 +78,6 @@ Connectors should be named, for example the above is named `discord`, and should
|
||||
|
||||
See below for more details on connectors and their associated config.
|
||||
|
||||
##### Discord
|
||||
Connects to Discord via `discord.js`.
|
||||
```json
|
||||
"discord": "Discord Login Token",
|
||||
```
|
||||
Requires a Discord bot login token.
|
||||
|
||||
##### Squad Layer Filter
|
||||
Connects to a filtered list of Squad layers and filters them either by an "initial filter" or an "active filter" that depends on current server information, e.g. player count.
|
||||
```js
|
||||
@ -153,19 +146,33 @@ Connects to a filtered list of Squad layers and filters them either by an "initi
|
||||
- `factionHistoryTolerance` - A faction can only be played again after this number of layers. Factions can be specified individually inside the object. If they are not listed then the filter is not applied.
|
||||
- `factionRepetitiveTolerance` - A faction can only be played this number of times in a row. Factions can be specified individually inside the object. If they are not listed then the filter is not applied.
|
||||
|
||||
##### MySQL
|
||||
Connects to a MySQL database.
|
||||
##### Discord
|
||||
Connects to Discord via `discord.js`.
|
||||
```json
|
||||
"mysql": {
|
||||
"connectionLimit": 10,
|
||||
"host": "host",
|
||||
"port": 3306,
|
||||
"user": "squadjs",
|
||||
"password": "password",
|
||||
"database": "squadjs"
|
||||
"discord": "Discord Login Token",
|
||||
```
|
||||
Requires a Discord bot login token.
|
||||
|
||||
|
||||
##### Databases
|
||||
SquadJS uses [Sequelize](https://sequelize.org/) to connect and use a wide range of SQL databases.
|
||||
|
||||
The connector should be configured using any of Sequelize's single argument configuration options.
|
||||
|
||||
For example:
|
||||
```json
|
||||
"mysql": "mysql://user:pass@example.com:5432/dbname"
|
||||
```
|
||||
|
||||
or:
|
||||
```json
|
||||
"sqlite": {
|
||||
"dialect": "sqlite",
|
||||
"storage": "path/to/database.sqlite"
|
||||
}
|
||||
```
|
||||
The config is a set of pool connection options as listed in the [Node.js mysql](https://www.npmjs.com/package/mysql) documentation.
|
||||
|
||||
See [Sequelize's documentation](https://sequelize.org/master/manual/getting-started.html#connecting-to-a-database) for more details.
|
||||
|
||||
#### Plugins
|
||||
The `plugins` section in your config file lists all plugins built into SquadJS, e.g.:
|
||||
@ -274,6 +281,30 @@ The following is a list of plugins built into SquadJS, you can click their title
|
||||
]</code></pre>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>DBLog</summary>
|
||||
<h2>DBLog</h2>
|
||||
<p>The <code>mysql-log</code> plugin will log various server statistics and events to a database. This is great for server performance monitoring and/or player stat tracking.
|
||||
|
||||
Grafana (NOT YET WORKING WITH V2):
|
||||
* [Grafana](https://grafana.com/) is a cool way of viewing server statistics stored in the database.
|
||||
* Install Grafana.
|
||||
* Add your database as a datasource named <code>SquadJS</code>.
|
||||
* Import the [SquadJS Dashboard](https://github.com/Thomas-Smyth/SquadJS/blob/master/plugins/mysql-log/SquadJS-Dashboard.json) to get a preconfigured MySQL only Grafana dashboard.
|
||||
* Install any missing Grafana plugins.</p>
|
||||
<h3>Options</h3>
|
||||
<h4>database (Required)</h4>
|
||||
<h6>Description</h6>
|
||||
<p>The Sequelize connector to log server information to.</p>
|
||||
<h6>Default</h6>
|
||||
<pre><code>mysql</code></pre>
|
||||
<h4>overrideServerID</h4>
|
||||
<h6>Description</h6>
|
||||
<p>A overridden server ID.</p>
|
||||
<h6>Default</h6>
|
||||
<pre><code>null</code></pre>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>DiscordAdminBroadcast</summary>
|
||||
<h2>DiscordAdminBroadcast</h2>
|
||||
@ -441,23 +472,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>
|
||||
|
18
config.json
18
config.json
@ -54,12 +54,12 @@
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"connectionLimit": 10,
|
||||
"host": "host",
|
||||
"port": 3306,
|
||||
"user": "squadjs",
|
||||
"username": "squadjs",
|
||||
"password": "password",
|
||||
"database": "squadjs"
|
||||
"database": "squadjs",
|
||||
"dialect": "mysql"
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
@ -94,6 +94,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plugin": "DBLog",
|
||||
"enabled": false,
|
||||
"database": "mysql",
|
||||
"overrideServerID": null
|
||||
},
|
||||
{
|
||||
"plugin": "DiscordAdminBroadcast",
|
||||
"enabled": false,
|
||||
@ -138,12 +144,6 @@
|
||||
"channelID": "",
|
||||
"events": []
|
||||
},
|
||||
{
|
||||
"plugin": "DiscordPlaceholder",
|
||||
"enabled": true,
|
||||
"discordClient": "discord",
|
||||
"command": "!placeholder"
|
||||
},
|
||||
{
|
||||
"plugin": "DiscordRcon",
|
||||
"enabled": false,
|
||||
|
5
index.js
5
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
|
||||
await Promise.all(server.plugins.map(async (plugin) => await plugin.mount()));
|
||||
}
|
||||
|
||||
main();
|
||||
|
@ -3,28 +3,32 @@ import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import Discord from 'discord.js';
|
||||
import mysql from 'mysql';
|
||||
import sequelize from 'sequelize';
|
||||
|
||||
import Logger from 'core/logger';
|
||||
|
||||
import SquadServer from './index.js';
|
||||
import plugins from './plugins/index.js';
|
||||
|
||||
const { Sequelize } = sequelize;
|
||||
|
||||
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,46 +40,31 @@ export default class SquadServerFactory {
|
||||
// ignore non connectors
|
||||
if (!option.connector) continue;
|
||||
|
||||
// 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...');
|
||||
// initialise plugins
|
||||
Logger.verbose('SquadServerFactory', 1, 'Initialising plugins...');
|
||||
|
||||
for (const pluginConfig of config.plugins) {
|
||||
if (!pluginConfig.enabled) continue;
|
||||
|
||||
@ -86,32 +75,42 @@ export default class SquadServerFactory {
|
||||
|
||||
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 === 'squadlayerpool') {
|
||||
return server.squadLayers[connectorConfig.type](
|
||||
connectorConfig.filter,
|
||||
connectorConfig.activeLayerFilter
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'discord') {
|
||||
const connector = new Discord.Client();
|
||||
await connector.login(connectorConfig);
|
||||
return connector;
|
||||
}
|
||||
|
||||
if (type === 'sequelize') {
|
||||
const connector = new Sequelize(connectorConfig);
|
||||
await connector.authenticate();
|
||||
return connector;
|
||||
}
|
||||
|
||||
throw new Error(`${type.connector} is an unsupported connector type.`);
|
||||
}
|
||||
|
||||
static parseConfig(configString) {
|
||||
try {
|
||||
return JSON.parse(configString);
|
||||
|
@ -21,6 +21,7 @@ export default class SquadServer extends EventEmitter {
|
||||
for (const option of ['host', 'queryPort'])
|
||||
if (!(option in options)) throw new Error(`${option} must be specified.`);
|
||||
|
||||
this.id = options.id;
|
||||
this.options = options;
|
||||
|
||||
this.layerHistory = [];
|
||||
@ -139,11 +140,10 @@ export default class SquadServer extends EventEmitter {
|
||||
});
|
||||
|
||||
this.logParser.on('NEW_GAME', (data) => {
|
||||
let layer;
|
||||
if (data.layer) layer = this.squadLayers.getLayerByLayerName(data.layer);
|
||||
else layer = this.squadLayers.getLayerByLayerClassname(data.layerClassname);
|
||||
if (data.layer) data.layer = this.squadLayers.getLayerByLayerName(data.layer);
|
||||
else data.layer = this.squadLayers.getLayerByLayerClassname(data.layerClassname);
|
||||
|
||||
this.layerHistory.unshift({ ...layer, time: data.time });
|
||||
this.layerHistory.unshift({ ...data.layer, time: data.time });
|
||||
this.layerHistory = this.layerHistory.slice(0, this.layerHistoryMaxLength);
|
||||
|
||||
this.emit('NEW_GAME', data);
|
||||
@ -192,7 +192,6 @@ export default class SquadServer extends EventEmitter {
|
||||
|
||||
this.logParser.on('PLAYER_DIED', async (data) => {
|
||||
data.victim = await this.getPlayerByName(data.victimName);
|
||||
data.attacker = await this.getPlayerByName(data.attackerName);
|
||||
|
||||
if (data.victim && data.attacker)
|
||||
data.teamkill =
|
||||
@ -266,6 +265,8 @@ export default class SquadServer extends EventEmitter {
|
||||
async updatePlayerList() {
|
||||
if (this.updatePlayerListTimeout) clearTimeout(this.updatePlayerListTimeout);
|
||||
|
||||
Logger.verbose('SquadServer', 1, `Updating player list...`);
|
||||
|
||||
try {
|
||||
const oldPlayerInfo = {};
|
||||
for (const player of this.players) {
|
||||
@ -288,16 +289,23 @@ export default class SquadServer extends EventEmitter {
|
||||
if (player.squadID !== oldPlayerInfo[player.steamID].squadID)
|
||||
this.emit('PLAYER_SQUAD_CHANGE', player);
|
||||
}
|
||||
|
||||
this.emit('UPDATED_PLAYER_INFORMATION');
|
||||
|
||||
} catch (err) {
|
||||
Logger.verbose('SquadServer', 1, 'Failed to update player list.', err);
|
||||
}
|
||||
|
||||
Logger.verbose('SquadServer', 1, `Updated player list.`);
|
||||
|
||||
this.updatePlayerListTimeout = setTimeout(this.updatePlayerList, this.updatePlayerListInterval);
|
||||
}
|
||||
|
||||
async updateLayerInformation() {
|
||||
if (this.updateLayerInformationTimeout) clearTimeout(this.updateLayerInformationTimeout);
|
||||
|
||||
Logger.verbose('SquadServer', 1, `Updating layer information...`);
|
||||
|
||||
try {
|
||||
const layerInfo = await this.rcon.getLayerInfo();
|
||||
|
||||
@ -309,10 +317,14 @@ export default class SquadServer extends EventEmitter {
|
||||
}
|
||||
|
||||
this.nextLayer = layerInfo.nextLayer;
|
||||
|
||||
this.emit('UPDATED_LAYER_INFORMATION');
|
||||
} catch (err) {
|
||||
Logger.verbose('SquadServer', 1, 'Failed to update layer information.', err);
|
||||
}
|
||||
|
||||
Logger.verbose('SquadServer', 1, `Updated layer information.`);
|
||||
|
||||
this.updateLayerInformationTimeout = setTimeout(
|
||||
this.updateLayerInformation,
|
||||
this.updateLayerInformationInterval
|
||||
@ -322,6 +334,8 @@ export default class SquadServer extends EventEmitter {
|
||||
async updateA2SInformation() {
|
||||
if (this.updateA2SInformationTimeout) clearTimeout(this.updateA2SInformationTimeout);
|
||||
|
||||
Logger.verbose('SquadServer', 1, `Updating A2S information...`);
|
||||
|
||||
try {
|
||||
const data = await Gamedig.query({
|
||||
type: 'squad',
|
||||
@ -341,10 +355,14 @@ export default class SquadServer extends EventEmitter {
|
||||
|
||||
this.matchTimeout = parseFloat(data.raw.rules.MatchTimeout_f);
|
||||
this.gameVersion = data.raw.version;
|
||||
|
||||
this.emit('UPDATED_A2S_INFORMATION');
|
||||
} catch (err) {
|
||||
Logger.verbose('SquadServer', 1, 'Failed to update A2S information.', err);
|
||||
}
|
||||
|
||||
Logger.verbose('SquadServer', 1, `Updated A2S information.`);
|
||||
|
||||
this.updateA2SInformationTimeout = setTimeout(
|
||||
this.updateA2SInformation,
|
||||
this.updateA2SInformationInterval
|
||||
|
@ -9,8 +9,14 @@
|
||||
"discord.js": "^12.3.1",
|
||||
"gamedig": "^2.0.20",
|
||||
"log-parser": "1.0.0",
|
||||
"mysql": "^2.18.1",
|
||||
"mariadb": "^2.5.1",
|
||||
"mysql2": "^2.2.5",
|
||||
"pg": "^8.5.1",
|
||||
"pg-hstore": "^2.3.3",
|
||||
"rcon": "1.0.0",
|
||||
"sequelize": "^6.3.5",
|
||||
"sqlite3": "^5.0.0",
|
||||
"tedious": "^9.2.1",
|
||||
"tinygradient": "^1.1.2"
|
||||
},
|
||||
"exports": {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
async mount() {
|
||||
this.server.on('TEAMKILL', this.onTeamkill);
|
||||
}
|
||||
|
||||
async unmount() {
|
||||
this.server.removeEventListener('TEAMKILL', this.onTeamkill);
|
||||
}
|
||||
|
||||
async onTeamkill(info) {
|
||||
await this.server.rcon.warn(info.attacker.steamID, this.options.message);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,35 @@
|
||||
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() {}
|
||||
|
||||
async mount() {}
|
||||
|
||||
async unmount() {}
|
||||
|
||||
static get description() {
|
||||
throw new Error('Plugin missing "static get description()" method.');
|
||||
}
|
||||
@ -11,9 +42,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);
|
||||
}
|
||||
}
|
||||
|
@ -36,9 +36,7 @@ export default class ChatCommands extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
|
||||
async mount() {
|
||||
for (const command of this.options.commands) {
|
||||
this.server.on(`CHAT_COMMAND:${command.command}`, async (data) => {
|
||||
if (command.ignoreChats.includes(data.chat)) return;
|
||||
|
540
squad-server/plugins/db-log.js
Normal file
540
squad-server/plugins/db-log.js
Normal file
@ -0,0 +1,540 @@
|
||||
import Sequelize from 'sequelize';
|
||||
|
||||
import BasePlugin from './base-plugin.js';
|
||||
|
||||
const { DataTypes } = Sequelize;
|
||||
|
||||
export default class DBLog extends BasePlugin {
|
||||
static get description() {
|
||||
return (
|
||||
'The <code>mysql-log</code> plugin will log various server statistics and events to a database. This is great ' +
|
||||
'for server performance monitoring and/or player stat tracking.' +
|
||||
'\n\n' +
|
||||
'Grafana (NOT YET WORKING WITH V2):\n' +
|
||||
' * [Grafana](https://grafana.com/) is a cool way of viewing server statistics stored in the database.\n' +
|
||||
' * Install Grafana.\n' +
|
||||
' * Add your database as a datasource named <code>SquadJS</code>.\n' +
|
||||
' * Import the [SquadJS Dashboard](https://github.com/Thomas-Smyth/SquadJS/blob/master/plugins/mysql-log/SquadJS-Dashboard.json) to get a preconfigured MySQL only Grafana dashboard.\n' +
|
||||
' * Install any missing Grafana plugins.'
|
||||
);
|
||||
}
|
||||
|
||||
static get defaultEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static get optionsSpecification() {
|
||||
return {
|
||||
database: {
|
||||
required: true,
|
||||
connector: 'sequelize',
|
||||
description: 'The Sequelize connector to log server information to.',
|
||||
default: 'mysql'
|
||||
},
|
||||
overrideServerID: {
|
||||
required: false,
|
||||
description: 'A overridden server ID.',
|
||||
default: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.models = {};
|
||||
|
||||
this.createModel('Server', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING
|
||||
}
|
||||
});
|
||||
|
||||
this.createModel('TickRate', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.DATE,
|
||||
notNull: true
|
||||
},
|
||||
tickRate: {
|
||||
type: DataTypes.FLOAT,
|
||||
notNull: true
|
||||
}
|
||||
});
|
||||
|
||||
this.createModel('PlayerCount', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.DATE,
|
||||
notNull: true,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
players: {
|
||||
type: DataTypes.INTEGER,
|
||||
notNull: true
|
||||
},
|
||||
publicQueue: {
|
||||
type: DataTypes.INTEGER,
|
||||
notNull: true
|
||||
},
|
||||
reserveQueue: {
|
||||
type: DataTypes.INTEGER,
|
||||
notNull: true
|
||||
}
|
||||
});
|
||||
|
||||
this.createModel('Match', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
dlc: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
mapClassname: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
layerClassname: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
map: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
layer: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
startTime: {
|
||||
type: DataTypes.DATE,
|
||||
notNull: true
|
||||
},
|
||||
endTime: {
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
winner: {
|
||||
type: DataTypes.STRING
|
||||
}
|
||||
});
|
||||
|
||||
this.createModel('SteamUser', {
|
||||
steamID: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true
|
||||
},
|
||||
lastName: {
|
||||
type: DataTypes.STRING
|
||||
}
|
||||
});
|
||||
|
||||
this.createModel('Wound', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.DATE,
|
||||
notNull: true
|
||||
},
|
||||
victimName: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
victimTeamID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
victimSquadID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
attackerName: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
attackerTeamID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
attackerSquadID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
damage: {
|
||||
type: DataTypes.FLOAT
|
||||
},
|
||||
weapon: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
teamkill: {
|
||||
type: DataTypes.BOOLEAN
|
||||
}
|
||||
});
|
||||
|
||||
this.createModel('Death', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.DATE,
|
||||
notNull: true
|
||||
},
|
||||
woundTime: {
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
victimName: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
victimTeamID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
victimSquadID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
attackerName: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
attackerTeamID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
attackerSquadID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
damage: {
|
||||
type: DataTypes.FLOAT
|
||||
},
|
||||
weapon: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
teamkill: {
|
||||
type: DataTypes.BOOLEAN
|
||||
}
|
||||
});
|
||||
|
||||
this.createModel('Revive', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.DATE,
|
||||
notNull: true
|
||||
},
|
||||
woundTime: {
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
victimName: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
victimTeamID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
victimSquadID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
attackerName: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
attackerTeamID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
attackerSquadID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
damage: {
|
||||
type: DataTypes.FLOAT
|
||||
},
|
||||
weapon: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
teamkill: {
|
||||
type: DataTypes.BOOLEAN
|
||||
},
|
||||
reviverName: {
|
||||
type: DataTypes.BOOLEAN
|
||||
},
|
||||
reviverTeamID: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
reviverSquadID: {
|
||||
type: DataTypes.INTEGER
|
||||
}
|
||||
});
|
||||
|
||||
this.models.Server.hasMany(this.models.TickRate, {
|
||||
foreignKey: { name: 'server', allowNull: false },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.Server.hasMany(this.models.PlayerCount, {
|
||||
foreignKey: { name: 'server', allowNull: false },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.Server.hasMany(this.models.Match, {
|
||||
foreignKey: { name: 'server', allowNull: false },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.Server.hasMany(this.models.Wound, {
|
||||
foreignKey: { name: 'server', allowNull: false },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.Server.hasMany(this.models.Death, {
|
||||
foreignKey: { name: 'server', allowNull: false },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.Server.hasMany(this.models.Revive, {
|
||||
foreignKey: { name: 'server', allowNull: false },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.SteamUser.hasMany(this.models.Wound, {
|
||||
foreignKey: { name: 'attacker' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.SteamUser.hasMany(this.models.Wound, {
|
||||
foreignKey: { name: 'victim' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.SteamUser.hasMany(this.models.Death, {
|
||||
foreignKey: { name: 'attacker' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.SteamUser.hasMany(this.models.Death, {
|
||||
foreignKey: { name: 'victim' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.SteamUser.hasMany(this.models.Revive, {
|
||||
foreignKey: { name: 'attacker' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.SteamUser.hasMany(this.models.Revive, {
|
||||
foreignKey: { name: 'victim' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.SteamUser.hasMany(this.models.Revive, {
|
||||
foreignKey: { name: 'reviver' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.Match.hasMany(this.models.Wound, {
|
||||
foreignKey: { name: 'match' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.Match.hasMany(this.models.Death, {
|
||||
foreignKey: { name: 'match' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.models.Match.hasMany(this.models.Revive, {
|
||||
foreignKey: { name: 'match' },
|
||||
onDelete: 'CASCADE'
|
||||
});
|
||||
|
||||
this.onTickRate = this.onTickRate.bind(this);
|
||||
this.onUpdatedA2SInformation = this.onUpdatedA2SInformation.bind(this);
|
||||
this.onNewGame = this.onNewGame.bind(this);
|
||||
this.onPlayerWounded = this.onPlayerWounded.bind(this);
|
||||
this.onPlayerDied = this.onPlayerDied.bind(this);
|
||||
this.onPlayerRevived = this.onPlayerRevived.bind(this);
|
||||
}
|
||||
|
||||
createModel(name, schema) {
|
||||
this.models[name] = this.options.database.define(`DBLog_${name}`, schema, {
|
||||
timestamps: false
|
||||
});
|
||||
}
|
||||
|
||||
async prepareToMount() {
|
||||
await this.models.Server.sync();
|
||||
await this.models.TickRate.sync();
|
||||
await this.models.PlayerCount.sync();
|
||||
await this.models.Match.sync();
|
||||
await this.models.SteamUser.sync();
|
||||
await this.models.Wound.sync();
|
||||
await this.models.Death.sync();
|
||||
await this.models.Revive.sync();
|
||||
}
|
||||
|
||||
async mount() {
|
||||
await this.models.Server.upsert({
|
||||
id: this.options.overrideServerID || this.server.id,
|
||||
name: this.server.serverName
|
||||
});
|
||||
|
||||
this.match = await this.models.Match.findOne({
|
||||
where: { server: this.options.overrideServerID || this.server.id, endTime: null }
|
||||
});
|
||||
|
||||
this.server.on('TICK_RATE', this.onTickRate);
|
||||
this.server.on('UPDATED_A2S_INFORMATION', this.onUpdatedA2SInformation);
|
||||
this.server.on('NEW_GAME', this.onNewGame);
|
||||
this.server.on('PLAYER_WOUNDED', this.onPlayerWounded);
|
||||
this.server.on('PLAYER_DIED', this.onPlayerDied);
|
||||
this.server.on('PLAYER_REVIVED', this.onPlayerRevived);
|
||||
}
|
||||
|
||||
async unmount() {
|
||||
this.server.removeEventListener('TICK_RATE', this.onTickRate);
|
||||
this.server.removeEventListener('UPDATED_A2S_INFORMATION', this.onTickRate);
|
||||
this.server.removeEventListener('NEW_GAME', this.onNewGame);
|
||||
this.server.removeEventListener('PLAYER_WOUNDED', this.onPlayerWounded);
|
||||
this.server.removeEventListener('PLAYER_DIED', this.onPlayerDied);
|
||||
this.server.removeEventListener('PLAYER_REVIVED', this.onPlayerRevived);
|
||||
}
|
||||
|
||||
async onTickRate(info) {
|
||||
await this.models.TickRate.create({
|
||||
server: this.options.overrideServerID || this.server.id,
|
||||
time: info.time,
|
||||
tickRate: info.tickRate
|
||||
});
|
||||
}
|
||||
|
||||
async onUpdatedA2SInformation() {
|
||||
await this.models.PlayerCount.create({
|
||||
server: this.options.overrideServerID || this.server.id,
|
||||
players: this.server.a2sPlayerCount,
|
||||
publicQueue: this.server.publicQueue,
|
||||
reserveQueue: this.server.reserveQueue
|
||||
});
|
||||
}
|
||||
|
||||
async onNewGame(info) {
|
||||
await this.models.Match.update(
|
||||
{ endTime: info.time, winner: info.winner },
|
||||
{ where: { server: this.options.overrideServerID || this.server.id, endTime: null } }
|
||||
);
|
||||
|
||||
this.match = await this.models.Match.create({
|
||||
server: this.options.overrideServerID || this.server.id,
|
||||
dlc: info.dlc,
|
||||
mapClassname: info.mapClassname,
|
||||
layerClassname: info.layerClassname,
|
||||
map: info.layer ? info.layer.map : null,
|
||||
layer: info.layer ? info.layer.layer : null,
|
||||
startTime: info.time
|
||||
});
|
||||
}
|
||||
|
||||
async onPlayerWounded(info) {
|
||||
if (info.attacker)
|
||||
await this.models.SteamUser.upsert({
|
||||
steamID: info.attacker.steamID,
|
||||
lastName: info.attacker.name
|
||||
});
|
||||
if (info.victim)
|
||||
await this.models.SteamUser.upsert({
|
||||
steamID: info.victim.steamID,
|
||||
lastName: info.victim.name
|
||||
});
|
||||
|
||||
await this.models.Wound.create({
|
||||
server: this.options.overrideServerID || this.server.id,
|
||||
match: this.match ? this.match.id : null,
|
||||
time: info.time,
|
||||
victim: info.victim ? info.victim.steamID : null,
|
||||
victimName: info.victim ? info.victim.name : null,
|
||||
victimTeamID: info.victim ? info.victim.teamID : null,
|
||||
victimSquadID: info.victim ? info.victim.squadID : null,
|
||||
attacker: info.attacker ? info.attacker.steamID : null,
|
||||
attackerName: info.attacker ? info.attacker.name : null,
|
||||
attackerTeamID: info.attacker ? info.attacker.teamID : null,
|
||||
attackerSquadID: info.attacker ? info.attacker.squadID : null,
|
||||
damage: info.damage,
|
||||
weapon: info.weapon,
|
||||
teamkill: info.teamkill
|
||||
});
|
||||
}
|
||||
|
||||
async onPlayerDied(info) {
|
||||
if (info.attacker)
|
||||
await this.models.SteamUser.upsert({
|
||||
steamID: info.attacker.steamID,
|
||||
lastName: info.attacker.name
|
||||
});
|
||||
if (info.victim)
|
||||
await this.models.SteamUser.upsert({
|
||||
steamID: info.victim.steamID,
|
||||
lastName: info.victim.name
|
||||
});
|
||||
|
||||
await this.models.Death.create({
|
||||
server: this.options.overrideServerID || this.server.id,
|
||||
match: this.match ? this.match.id : null,
|
||||
time: info.time,
|
||||
woundTime: info.woundTime,
|
||||
victim: info.victim ? info.victim.steamID : null,
|
||||
victimName: info.victim ? info.victim.name : null,
|
||||
victimTeamID: info.victim ? info.victim.teamID : null,
|
||||
victimSquadID: info.victim ? info.victim.squadID : null,
|
||||
attacker: info.attacker ? info.attacker.steamID : null,
|
||||
attackerName: info.attacker ? info.attacker.name : null,
|
||||
attackerTeamID: info.attacker ? info.attacker.teamID : null,
|
||||
attackerSquadID: info.attacker ? info.attacker.squadID : null,
|
||||
damage: info.damage,
|
||||
weapon: info.weapon,
|
||||
teamkill: info.teamkill
|
||||
});
|
||||
}
|
||||
|
||||
async onPlayerRevived(info) {
|
||||
if (info.attacker)
|
||||
await this.models.SteamUser.upsert({
|
||||
steamID: info.attacker.steamID,
|
||||
lastName: info.attacker.name
|
||||
});
|
||||
if (info.victim)
|
||||
await this.models.SteamUser.upsert({
|
||||
steamID: info.victim.steamID,
|
||||
lastName: info.victim.name
|
||||
});
|
||||
if (info.reviver)
|
||||
await this.models.SteamUser.upsert({
|
||||
steamID: info.reviver.steamID,
|
||||
lastName: info.reviver.name
|
||||
});
|
||||
|
||||
await this.models.Revive.create({
|
||||
server: this.options.overrideServerID || this.server.id,
|
||||
match: this.match ? this.match.id : null,
|
||||
time: info.time,
|
||||
woundTime: info.woundTime,
|
||||
victim: info.victim ? info.victim.steamID : null,
|
||||
victimName: info.victim ? info.victim.name : null,
|
||||
victimTeamID: info.victim ? info.victim.teamID : null,
|
||||
victimSquadID: info.victim ? info.victim.squadID : null,
|
||||
attacker: info.attacker ? info.attacker.steamID : null,
|
||||
attackerName: info.attacker ? info.attacker.name : null,
|
||||
attackerTeamID: info.attacker ? info.attacker.teamID : null,
|
||||
attackerSquadID: info.attacker ? info.attacker.squadID : null,
|
||||
damage: info.damage,
|
||||
weapon: info.weapon,
|
||||
teamkill: info.teamkill,
|
||||
reviver: info.reviver ? info.reviver.steamID : null,
|
||||
reviverName: info.reviver ? info.reviver.name : null,
|
||||
reviverTeamID: info.reviver ? info.reviver.teamID : null,
|
||||
reviverSquadID: info.reviver ? info.reviver.squadID : null
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
async mount() {
|
||||
this.server.on('ADMIN_BROADCAST', this.onAdminBroadcast);
|
||||
}
|
||||
|
||||
async 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()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -26,72 +26,85 @@ 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;
|
||||
async 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()
|
||||
}
|
||||
});
|
||||
});
|
||||
async 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];
|
||||
}
|
||||
}
|
||||
|
@ -57,68 +57,75 @@ 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;
|
||||
}
|
||||
async 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;
|
||||
}
|
||||
async 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.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,16 +14,11 @@ 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);
|
||||
|
||||
async sendDiscordMessage(message) {
|
||||
if (typeof message === 'object' && 'embed' in message)
|
||||
message.embed.footer = { text: COPYRIGHT_MESSAGE };
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
});
|
||||
async mount() {
|
||||
this.server.on('CHAT_MESSAGE', this.onChatMessage);
|
||||
}
|
||||
|
||||
async 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()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -30,9 +30,7 @@ export default class DiscordDebug extends DiscordBasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
|
||||
async mount() {
|
||||
for (const event of this.options.events) {
|
||||
this.server.on(event, async (info) => {
|
||||
await this.sendDiscordMessage(`\`\`\`${JSON.stringify({ ...info, event }, null, 2)}\`\`\``);
|
||||
|
@ -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.');
|
||||
});
|
||||
}
|
||||
}
|
@ -47,44 +47,53 @@ 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;
|
||||
async 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}: `
|
||||
);
|
||||
async 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.options.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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
async mount() {
|
||||
this.server.on('NEW_GAME', this.onNewGame);
|
||||
}
|
||||
|
||||
async 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()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
async mount() {
|
||||
this.interval = setInterval(this.update, this.options.updateInterval);
|
||||
}
|
||||
|
||||
async 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() {
|
||||
|
@ -33,30 +33,40 @@ 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;
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
async mount() {
|
||||
this.options.discordClient.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
await this.server.restartRCON();
|
||||
message.reply('restarted the SquadJS RCON subsystem.');
|
||||
async 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.');
|
||||
}
|
||||
|
||||
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.restartRCON();
|
||||
message.reply('restarted the SquadJS RCON subsystem.');
|
||||
}
|
||||
|
||||
await this.server.restartLogParser();
|
||||
message.reply('restarted the SquadJS LogParser 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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
];
|
||||
async 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})`
|
||||
});
|
||||
async 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()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import AutoTKWarn from './auto-tk-warn.js';
|
||||
import AutoKickUnassigned from './auto-kick-unassigned.js';
|
||||
import ChatCommands from './chat-commands.js';
|
||||
import DBLog from './db-log.js';
|
||||
import DiscordAdminBroadcast from './discord-admin-broadcast.js';
|
||||
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';
|
||||
@ -19,12 +19,12 @@ const plugins = [
|
||||
AutoTKWarn,
|
||||
AutoKickUnassigned,
|
||||
ChatCommands,
|
||||
DBLog,
|
||||
DiscordAdminBroadcast,
|
||||
DiscordAdminCamLogs,
|
||||
DiscordAdminRequest,
|
||||
DiscordChat,
|
||||
DiscordDebug,
|
||||
DiscordPlaceholder,
|
||||
DiscordRcon,
|
||||
DiscordRoundWinner,
|
||||
DiscordServerStatus,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
async mount() {
|
||||
this.interval = setInterval(this.broadcast, this.options.interval);
|
||||
}
|
||||
|
||||
async unmount() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
async broadcast() {
|
||||
await this.server.rcon.broadcast(this.options.broadcasts[0]);
|
||||
this.broadcasts.push(this.options.broadcasts.shift());
|
||||
}
|
||||
}
|
||||
|
@ -48,21 +48,28 @@ 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);
|
||||
}
|
||||
|
||||
async mount() {
|
||||
this.interval = setInterval(this.broadcast, this.options.interval);
|
||||
}
|
||||
|
||||
async 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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
async mount() {
|
||||
this.server.on(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand);
|
||||
}
|
||||
|
||||
let currentIndex = players.length;
|
||||
let temporaryValue;
|
||||
let randomIndex;
|
||||
async 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,12 +54,12 @@
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"connectionLimit": 10,
|
||||
"host": "host",
|
||||
"port": 3306,
|
||||
"user": "squadjs",
|
||||
"username": "squadjs",
|
||||
"password": "password",
|
||||
"database": "squadjs"
|
||||
"database": "squadjs",
|
||||
"dialect": "mysql"
|
||||
}
|
||||
},
|
||||
"plugins": [],
|
||||
|
@ -78,13 +78,6 @@ Connectors should be named, for example the above is named `discord`, and should
|
||||
|
||||
See below for more details on connectors and their associated config.
|
||||
|
||||
##### Discord
|
||||
Connects to Discord via `discord.js`.
|
||||
```json
|
||||
"discord": "Discord Login Token",
|
||||
```
|
||||
Requires a Discord bot login token.
|
||||
|
||||
##### Squad Layer Filter
|
||||
Connects to a filtered list of Squad layers and filters them either by an "initial filter" or an "active filter" that depends on current server information, e.g. player count.
|
||||
```js
|
||||
@ -153,19 +146,33 @@ Connects to a filtered list of Squad layers and filters them either by an "initi
|
||||
- `factionHistoryTolerance` - A faction can only be played again after this number of layers. Factions can be specified individually inside the object. If they are not listed then the filter is not applied.
|
||||
- `factionRepetitiveTolerance` - A faction can only be played this number of times in a row. Factions can be specified individually inside the object. If they are not listed then the filter is not applied.
|
||||
|
||||
##### MySQL
|
||||
Connects to a MySQL database.
|
||||
##### Discord
|
||||
Connects to Discord via `discord.js`.
|
||||
```json
|
||||
"mysql": {
|
||||
"connectionLimit": 10,
|
||||
"host": "host",
|
||||
"port": 3306,
|
||||
"user": "squadjs",
|
||||
"password": "password",
|
||||
"database": "squadjs"
|
||||
"discord": "Discord Login Token",
|
||||
```
|
||||
Requires a Discord bot login token.
|
||||
|
||||
|
||||
##### Databases
|
||||
SquadJS uses [Sequelize](https://sequelize.org/) to connect and use a wide range of SQL databases.
|
||||
|
||||
The connector should be configured using any of Sequelize's single argument configuration options.
|
||||
|
||||
For example:
|
||||
```json
|
||||
"mysql": "mysql://user:pass@example.com:5432/dbname"
|
||||
```
|
||||
|
||||
or:
|
||||
```json
|
||||
"sqlite": {
|
||||
"dialect": "sqlite",
|
||||
"storage": "path/to/database.sqlite"
|
||||
}
|
||||
```
|
||||
The config is a set of pool connection options as listed in the [Node.js mysql](https://www.npmjs.com/package/mysql) documentation.
|
||||
|
||||
See [Sequelize's documentation](https://sequelize.org/master/manual/getting-started.html#connecting-to-a-database) for more details.
|
||||
|
||||
#### Plugins
|
||||
The `plugins` section in your config file lists all plugins built into SquadJS, e.g.:
|
||||
|
Loading…
Reference in New Issue
Block a user