Merge branch 'Thomas-Smyth-master' into master

This commit is contained in:
SeanWalsh95 2021-02-25 11:54:31 -05:00
commit a67f90ba7e
22 changed files with 673 additions and 828 deletions

556
README.md
View File

@ -266,107 +266,61 @@ The following is a list of plugins built into SquadJS, you can click their title
Interested in creating your own plugin? [See more here](./squad-server/plugins/readme.md)
<details>
<summary>AutoKickUnassigned</summary>
<h2>AutoKickUnassigned</h2>
<p>The <code>AutoKickUnassigned</code> plugin will automatically kick players that are not in a squad after a specified ammount of time.</p>
<summary>DiscordSubsystemRestarter</summary>
<h2>DiscordSubsystemRestarter</h2>
<p>The <code>DiscordSubSystemRestarter</code> plugin allows you to manually restart SquadJS subsystems in case an issues arises with them.<ul><li><code>!squadjs restartsubsystem rcon</code></li><li><code>!squadjs restartsubsystem logparser</code></li></ul></p>
<h3>Options</h3>
<ul><li><h4>warningMessage</h4>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Message SquadJS will send to players warning them they will be kicked</p>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>Join a squad, you are are unassigned and will be kicked</code></pre></li>
<li><h4>kickMessage</h4>
<pre><code>discord</code></pre></li>
<li><h4>role (Required)</h4>
<h6>Description</h6>
<p>Message to send to players when they are kicked</p>
<p>ID of role required to run the sub system restart commands.</p>
<h6>Default</h6>
<pre><code>Unassigned - automatically removed</code></pre></li>
<li><h4>frequencyOfWarnings</h4>
<h6>Description</h6>
<p>How often in <b>Seconds</b> should we warn the player about being unassigned?</p>
<h6>Default</h6>
<pre><code>30</code></pre></li>
<li><h4>unassignedTimer</h4>
<h6>Description</h6>
<p>How long in <b>Seconds</b> to wait before a unassigned player is kicked</p>
<h6>Default</h6>
<pre><code>360</code></pre></li>
<li><h4>playerThreshold</h4>
<h6>Description</h6>
<p>Player count required for AutoKick to start kicking players, set to -1 to disable</p>
<h6>Default</h6>
<pre><code>93</code></pre></li>
<li><h4>roundStartDelay</h4>
<h6>Description</h6>
<p>Time delay in <b>Seconds</b> from start of the round before AutoKick starts kicking again</p>
<h6>Default</h6>
<pre><code>900</code></pre></li>
<li><h4>ignoreAdmins</h4>
<h6>Description</h6>
<p><ul><li><code>true</code>: Admins will <b>NOT</b> be kicked</li><li><code>false</code>: Admins <b>WILL</b> be kicked</li></ul></p>
<h6>Default</h6>
<pre><code>false</code></pre></li>
<li><h4>ignoreWhitelist</h4>
<h6>Description</h6>
<p><ul><li><code>true</code>: Reserve slot players will <b>NOT</b> be kicked</li><li><code>false</code>: Reserve slot players <b>WILL</b> be kicked</li></ul></p>
<h6>Default</h6>
<pre><code>false</code></pre></li></ul>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre></ul>
</details>
<details>
<summary>AutoTKWarn</summary>
<h2>AutoTKWarn</h2>
<p>The <code>AutoTkWarn</code> plugin will automatically warn players with a message when they teamkill.</p>
<summary>DiscordChat</summary>
<h2>DiscordChat</h2>
<p>The <code>DiscordChat</code> plugin will log in-game chat to a Discord channel.</p>
<h3>Options</h3>
<ul><li><h4>message</h4>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>The message to warn players with.</p>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>Please apologise for ALL TKs in ALL chat!</code></pre></li></ul>
</details>
<details>
<summary>ChatCommands</summary>
<h2>ChatCommands</h2>
<p>The <code>ChatCommands</code> plugin can be configured to make chat commands that broadcast or warn the caller with present messages.</p>
<h3>Options</h3>
<ul><li><h4>commands</h4>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>An array of objects containing the following properties: <ul><li><code>command</code> - The command that initiates the message.</li><li><code>type</code> - Either <code>warn</code> or <code>broadcast</code>.</li><li><code>response</code> - The message to respond with.</li><li><code>ignoreChats</code> - A list of chats to ignore the commands in. Use this to limit it to admins.</li></ul></p>
<p>The ID of the channel to log admin broadcasts to.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>chatColors</h4>
<h6>Description</h6>
<p>The color of the embed for each chat.</p>
<h6>Default</h6>
<pre><code>{}</code></pre></li><h6>Example</h6>
<pre><code>{
"ChatAll": 16761867
}</code></pre>
<li><h4>color</h4>
<h6>Description</h6>
<p>The color of the embed.</p>
<h6>Default</h6>
<pre><code>16761867</code></pre></li>
<li><h4>ignoreChats</h4>
<h6>Description</h6>
<p>A list of chat names to ignore.</p>
<h6>Default</h6>
<pre><code>[
{
"command": "squadjs",
"type": "warn",
"response": "This server is powered by SquadJS.",
"ignoreChats": []
}
"ChatSquad"
]</code></pre></li></ul>
</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):
<ul><li> <a href="https://grafana.com/">Grafana</a> is a cool way of viewing server statistics stored in the database.</li>
<li>Install Grafana.</li>
<li>Add your database as a datasource named <code>SquadJS</code>.</li>
<li>Import the <a href="https://github.com/Thomas-Smyth/SquadJS/blob/master/plugins/mysql-log/SquadJS-Dashboard.json">SquadJS Dashboard</a> to get a preconfigured MySQL only Grafana dashboard.</li>
<li>Install any missing Grafana plugins.</li></ul></p>
<h3>Options</h3>
<ul><li><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></li>
<li><h4>overrideServerID</h4>
<h6>Description</h6>
<p>A overridden server ID.</p>
<h6>Default</h6>
<pre><code>null</code></pre></li></ul>
</details>
<details>
<summary>DiscordAdminBroadcast</summary>
<h2>DiscordAdminBroadcast</h2>
@ -390,29 +344,6 @@ Grafana (NOT YET WORKING WITH V2):
<pre><code>16761867</code></pre></li></ul>
</details>
<details>
<summary>DiscordAdminCamLogs</summary>
<h2>DiscordAdminCamLogs</h2>
<p>The <code>DiscordAdminCamLogs</code> plugin will log in game admin camera usage to a Discord channel.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>The ID of the channel to log admin camera usage to.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>color</h4>
<h6>Description</h6>
<p>The color of the embed.</p>
<h6>Default</h6>
<pre><code>16761867</code></pre></li></ul>
</details>
<details>
<summary>DiscordAdminRequest</summary>
<h2>DiscordAdminRequest</h2>
@ -470,128 +401,6 @@ Grafana (NOT YET WORKING WITH V2):
<pre><code>16761867</code></pre></li></ul>
</details>
<details>
<summary>DiscordChat</summary>
<h2>DiscordChat</h2>
<p>The <code>DiscordChat</code> plugin will log in-game chat to a Discord channel.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>The ID of the channel to log admin broadcasts to.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>chatColors</h4>
<h6>Description</h6>
<p>The color of the embed for each chat.</p>
<h6>Default</h6>
<pre><code>{}</code></pre></li><h6>Example</h6>
<pre><code>{
"ChatAll": 16761867
}</code></pre>
<li><h4>color</h4>
<h6>Description</h6>
<p>The color of the embed.</p>
<h6>Default</h6>
<pre><code>16761867</code></pre></li>
<li><h4>ignoreChats</h4>
<h6>Description</h6>
<p>A list of chat names to ignore.</p>
<h6>Default</h6>
<pre><code>[
"ChatSquad"
]</code></pre></li></ul>
</details>
<details>
<summary>DiscordDebug</summary>
<h2>DiscordDebug</h2>
<p>The <code>DiscordDebug</code> plugin can be used to help debug SquadJS by dumping SquadJS events to a Discord channel.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>The ID of the channel to log events to.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>events (Required)</h4>
<h6>Description</h6>
<p>A list of events to dump.</p>
<h6>Default</h6>
<pre><code>[]</code></pre></li><h6>Example</h6>
<pre><code>[
"PLAYER_DIED"
]</code></pre></ul>
</details>
<details>
<summary>DiscordRcon</summary>
<h2>DiscordRcon</h2>
<p>The <code>DiscordRcon</code> plugin allows a specified Discord channel to be used as a RCON console to run RCON commands.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>ID of channel to turn into RCON console.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>permissions</h4>
<h6>Description</h6>
<p><ul><li>Dictionary of roles and a list of the permissions they are allowed to use.<li>If dictionary is empty (<code>{}</code>) permissions will be disabled</li><li>A list of available RCON commands can be found here <a>https://squad.gamepedia.com/Server_Administration#Admin_Console_Commands</a>.</ul></p>
<h6>Default</h6>
<pre><code>{}</code></pre></li><h6>Example</h6>
<pre><code>{
"123456789123456789": [
"AdminBroadcast",
"AdminForceTeamChange",
"AdminDemoteCommander"
]
}</code></pre>
<li><h4>prependAdminNameInBroadcast</h4>
<h6>Description</h6>
<p>Prepend admin names when making announcements.</p>
<h6>Default</h6>
<pre><code>false</code></pre></li></ul>
</details>
<details>
<summary>DiscordRoundWinner</summary>
<h2>DiscordRoundWinner</h2>
<p>The <code>DiscordRoundWinner</code> plugin will send the round winner to a Discord channel.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>The ID of the channel to log admin broadcasts to.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>color</h4>
<h6>Description</h6>
<p>The color of the embed.</p>
<h6>Default</h6>
<pre><code>16761867</code></pre></li></ul>
</details>
<details>
<summary>DiscordServerStatus</summary>
<h2>DiscordServerStatus</h2>
@ -626,21 +435,96 @@ Grafana (NOT YET WORKING WITH V2):
</details>
<details>
<summary>DiscordSubsystemRestarter</summary>
<h2>DiscordSubsystemRestarter</h2>
<p>The <code>DiscordSubSystemRestarter</code> plugin allows you to manually restart SquadJS subsystems in case an issues arises with them.<ul><li><code>!squadjs restartsubsystem rcon</code></li><li><code>!squadjs restartsubsystem logparser</code></li></ul></p>
<summary>AutoKickUnassigned</summary>
<h2>AutoKickUnassigned</h2>
<p>The <code>AutoKickUnassigned</code> plugin will automatically kick players that are not in a squad after a specified ammount of time.</p>
<h3>Options</h3>
<ul><li><h4>warningMessage</h4>
<h6>Description</h6>
<p>Message SquadJS will send to players warning them they will be kicked</p>
<h6>Default</h6>
<pre><code>Join a squad, you are are unassigned and will be kicked</code></pre></li>
<li><h4>kickMessage</h4>
<h6>Description</h6>
<p>Message to send to players when they are kicked</p>
<h6>Default</h6>
<pre><code>Unassigned - automatically removed</code></pre></li>
<li><h4>frequencyOfWarnings</h4>
<h6>Description</h6>
<p>How often in <b>Seconds</b> should we warn the player about being unassigned?</p>
<h6>Default</h6>
<pre><code>30</code></pre></li>
<li><h4>unassignedTimer</h4>
<h6>Description</h6>
<p>How long in <b>Seconds</b> to wait before a unassigned player is kicked</p>
<h6>Default</h6>
<pre><code>360</code></pre></li>
<li><h4>playerThreshold</h4>
<h6>Description</h6>
<p>Player count required for AutoKick to start kicking players, set to -1 to disable</p>
<h6>Default</h6>
<pre><code>93</code></pre></li>
<li><h4>roundStartDelay</h4>
<h6>Description</h6>
<p>Time delay in <b>Seconds</b> from start of the round before AutoKick starts kicking again</p>
<h6>Default</h6>
<pre><code>900</code></pre></li>
<li><h4>ignoreAdmins</h4>
<h6>Description</h6>
<p><ul><li><code>true</code>: Admins will <b>NOT</b> be kicked</li><li><code>false</code>: Admins <b>WILL</b> be kicked</li></ul></p>
<h6>Default</h6>
<pre><code>false</code></pre></li>
<li><h4>ignoreWhitelist</h4>
<h6>Description</h6>
<p><ul><li><code>true</code>: Reserve slot players will <b>NOT</b> be kicked</li><li><code>false</code>: Reserve slot players <b>WILL</b> be kicked</li></ul></p>
<h6>Default</h6>
<pre><code>false</code></pre></li></ul>
</details>
<details>
<summary>DiscordDebug</summary>
<h2>DiscordDebug</h2>
<p>The <code>DiscordDebug</code> plugin can be used to help debug SquadJS by dumping SquadJS events to a Discord channel.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>role (Required)</h4>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>ID of role required to run the sub system restart commands.</p>
<p>The ID of the channel to log events to.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre></ul>
<pre><code>667741905228136459</code></pre>
<li><h4>events (Required)</h4>
<h6>Description</h6>
<p>A list of events to dump.</p>
<h6>Default</h6>
<pre><code>[]</code></pre></li><h6>Example</h6>
<pre><code>[
"PLAYER_DIED"
]</code></pre></ul>
</details>
<details>
<summary>IntervalledBroadcasts</summary>
<h2>IntervalledBroadcasts</h2>
<p>The <code>IntervalledBroadcasts</code> plugin allows you to set broadcasts, which will be broadcasted at preset intervals</p>
<h3>Options</h3>
<ul><li><h4>broadcasts</h4>
<h6>Description</h6>
<p>Messages to broadcast.</p>
<h6>Default</h6>
<pre><code>[]</code></pre></li><h6>Example</h6>
<pre><code>[
"This server is powered by SquadJS."
]</code></pre>
<li><h4>interval</h4>
<h6>Description</h6>
<p>Frequency of the broadcasts in milliseconds.</p>
<h6>Default</h6>
<pre><code>300000</code></pre></li></ul>
</details>
<details>
@ -671,49 +555,6 @@ Grafana (NOT YET WORKING WITH V2):
<pre><code>false</code></pre></li></ul>
</details>
<details>
<summary>IntervalledBroadcasts</summary>
<h2>IntervalledBroadcasts</h2>
<p>The <code>IntervalledBroadcasts</code> plugin allows you to set broadcasts, which will be broadcasted at preset intervals</p>
<h3>Options</h3>
<ul><li><h4>broadcasts</h4>
<h6>Description</h6>
<p>Messages to broadcast.</p>
<h6>Default</h6>
<pre><code>[]</code></pre></li><h6>Example</h6>
<pre><code>[
"This server is powered by SquadJS."
]</code></pre>
<li><h4>interval</h4>
<h6>Description</h6>
<p>Frequency of the broadcasts in milliseconds.</p>
<h6>Default</h6>
<pre><code>300000</code></pre></li></ul>
</details>
<details>
<summary>SCBLInfo</summary>
<h2>SCBLInfo</h2>
<p>The <code>SCBLInfo</code> plugin alerts admins when a harmful player is detected joining their server based on data from the <a href="https://squad-community-ban-list.com/">Squad Community Ban List</a>.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>The ID of the channel to alert admins through.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>threshold</h4>
<h6>Description</h6>
<p>Admins will be alerted when a player has this or more reputation points. For more information on reputation points, see the <a href="https://squad-community-ban-list.com/faq">Squad Community Ban List's FAQ</a></p>
<h6>Default</h6>
<pre><code>6</code></pre></li></ul>
</details>
<details>
<summary>SeedingMode</summary>
<h2>SeedingMode</h2>
@ -751,6 +592,142 @@ Grafana (NOT YET WORKING WITH V2):
<pre><code>Live!</code></pre></li></ul>
</details>
<details>
<summary>AutoTKWarn</summary>
<h2>AutoTKWarn</h2>
<p>The <code>AutoTkWarn</code> plugin will automatically warn players with a message when they teamkill.</p>
<h3>Options</h3>
<ul><li><h4>message</h4>
<h6>Description</h6>
<p>The message to warn players with.</p>
<h6>Default</h6>
<pre><code>Please apologise for ALL TKs in ALL chat!</code></pre></li></ul>
</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):
<ul><li> <a href="https://grafana.com/">Grafana</a> is a cool way of viewing server statistics stored in the database.</li>
<li>Install Grafana.</li>
<li>Add your database as a datasource named <code>SquadJS</code>.</li>
<li>Import the <a href="https://github.com/Thomas-Smyth/SquadJS/blob/master/plugins/mysql-log/SquadJS-Dashboard.json">SquadJS Dashboard</a> to get a preconfigured MySQL only Grafana dashboard.</li>
<li>Install any missing Grafana plugins.</li></ul></p>
<h3>Options</h3>
<ul><li><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></li>
<li><h4>overrideServerID</h4>
<h6>Description</h6>
<p>A overridden server ID.</p>
<h6>Default</h6>
<pre><code>null</code></pre></li></ul>
</details>
<details>
<summary>DiscordRoundWinner</summary>
<h2>DiscordRoundWinner</h2>
<p>The <code>DiscordRoundWinner</code> plugin will send the round winner to a Discord channel.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>The ID of the channel to log admin broadcasts to.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>color</h4>
<h6>Description</h6>
<p>The color of the embed.</p>
<h6>Default</h6>
<pre><code>16761867</code></pre></li></ul>
</details>
<details>
<summary>ChatCommands</summary>
<h2>ChatCommands</h2>
<p>The <code>ChatCommands</code> plugin can be configured to make chat commands that broadcast or warn the caller with present messages.</p>
<h3>Options</h3>
<ul><li><h4>commands</h4>
<h6>Description</h6>
<p>An array of objects containing the following properties: <ul><li><code>command</code> - The command that initiates the message.</li><li><code>type</code> - Either <code>warn</code> or <code>broadcast</code>.</li><li><code>response</code> - The message to respond with.</li><li><code>ignoreChats</code> - A list of chats to ignore the commands in. Use this to limit it to admins.</li></ul></p>
<h6>Default</h6>
<pre><code>[
{
"command": "squadjs",
"type": "warn",
"response": "This server is powered by SquadJS.",
"ignoreChats": []
}
]</code></pre></li></ul>
</details>
<details>
<summary>DiscordAdminCamLogs</summary>
<h2>DiscordAdminCamLogs</h2>
<p>The <code>DiscordAdminCamLogs</code> plugin will log in game admin camera usage to a Discord channel.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>The ID of the channel to log admin camera usage to.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>color</h4>
<h6>Description</h6>
<p>The color of the embed.</p>
<h6>Default</h6>
<pre><code>16761867</code></pre></li></ul>
</details>
<details>
<summary>DiscordRcon</summary>
<h2>DiscordRcon</h2>
<p>The <code>DiscordRcon</code> plugin allows a specified Discord channel to be used as a RCON console to run RCON commands.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>ID of channel to turn into RCON console.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>permissions</h4>
<h6>Description</h6>
<p><ul><li>Dictionary of roles and a list of the permissions they are allowed to use.<li>If dictionary is empty (<code>{}</code>) permissions will be disabled</li><li>A list of available RCON commands can be found here <a>https://squad.gamepedia.com/Server_Administration#Admin_Console_Commands</a>.</ul></p>
<h6>Default</h6>
<pre><code>{}</code></pre></li><h6>Example</h6>
<pre><code>{
"123456789123456789": [
"AdminBroadcast",
"AdminForceTeamChange",
"AdminDemoteCommander"
]
}</code></pre>
<li><h4>prependAdminNameInBroadcast</h4>
<h6>Description</h6>
<p>Prepend admin names when making announcements.</p>
<h6>Default</h6>
<pre><code>false</code></pre></li></ul>
</details>
<details>
<summary>TeamRandomizer</summary>
<h2>TeamRandomizer</h2>
@ -763,6 +740,29 @@ Grafana (NOT YET WORKING WITH V2):
<pre><code>randomize</code></pre></li></ul>
</details>
<details>
<summary>SCBLInfo</summary>
<h2>SCBLInfo</h2>
<p>The <code>SCBLInfo</code> plugin alerts admins when a harmful player is detected joining their server based on data from the <a href="https://squad-community-ban-list.com/">Squad Community Ban List</a>.</p>
<h3>Options</h3>
<ul><li><h4>discordClient (Required)</h4>
<h6>Description</h6>
<p>Discord connector name.</p>
<h6>Default</h6>
<pre><code>discord</code></pre></li>
<li><h4>channelID (Required)</h4>
<h6>Description</h6>
<p>The ID of the channel to alert admins through.</p>
<h6>Default</h6>
<pre><code></code></pre></li><h6>Example</h6>
<pre><code>667741905228136459</code></pre>
<li><h4>threshold</h4>
<h6>Description</h6>
<p>Admins will be alerted when a player has this or more reputation points. For more information on reputation points, see the <a href="https://squad-community-ban-list.com/faq">Squad Community Ban List's FAQ</a></p>
<h6>Default</h6>
<pre><code>6</code></pre></li></ul>
</details>
<br>
## Statement on Accuracy

View File

@ -22,43 +22,6 @@
},
"connectors": {
"discord": "Discord Login Token",
"squadlayerpool": {
"type": "buildPoolFromFilter",
"filter": {
"whitelistedLayers": null,
"blacklistedLayers": null,
"whitelistedMaps": null,
"blacklistedMaps": null,
"whitelistedGamemodes": null,
"blacklistedGamemodes": [
"Training"
],
"flagCountMin": null,
"flagCountMax": null,
"hasCommander": null,
"hasTanks": null,
"hasHelicopters": null
},
"activeLayerFilter": {
"historyResetTime": 18000000,
"layerHistoryTolerance": 8,
"mapHistoryTolerance": 4,
"gamemodeHistoryTolerance": {
"Invasion": 4
},
"gamemodeRepetitiveTolerance": {
"Invasion": 4
},
"playerCountComplianceEnabled": true,
"factionComplianceEnabled": true,
"factionHistoryTolerance": {
"RUS": 4
},
"factionRepetitiveTolerance": {
"RUS": 4
}
}
},
"mysql": {
"host": "host",
"port": 3306,
@ -70,40 +33,22 @@
},
"plugins": [
{
"plugin": "AutoKickUnassigned",
"enabled": true,
"warningMessage": "Join a squad, you are are unassigned and will be kicked",
"kickMessage": "Unassigned - automatically removed",
"frequencyOfWarnings": 30,
"unassignedTimer": 360,
"playerThreshold": 93,
"roundStartDelay": 900,
"ignoreAdmins": false,
"ignoreWhitelist": false
"plugin": "DiscordSubsystemRestarter",
"enabled": false,
"discordClient": "discord",
"role": ""
},
{
"plugin": "AutoTKWarn",
"plugin": "DiscordChat",
"enabled": true,
"message": "Please apologise for ALL TKs in ALL chat!"
},
{
"plugin": "ChatCommands",
"enabled": true,
"commands": [
{
"command": "squadjs",
"type": "warn",
"response": "This server is powered by SquadJS.",
"ignoreChats": []
}
"discordClient": "discord",
"channelID": "",
"chatColors": {},
"color": 16761867,
"ignoreChats": [
"ChatSquad"
]
},
{
"plugin": "DBLog",
"enabled": false,
"database": "mysql",
"overrideServerID": null
},
{
"plugin": "DiscordAdminBroadcast",
"enabled": false,
@ -111,13 +56,6 @@
"channelID": "",
"color": 16761867
},
{
"plugin": "DiscordAdminCamLogs",
"enabled": false,
"discordClient": "discord",
"channelID": "",
"color": 16761867
},
{
"plugin": "DiscordAdminRequest",
"enabled": true,
@ -131,15 +69,24 @@
"color": 16761867
},
{
"plugin": "DiscordChat",
"enabled": true,
"plugin": "DiscordServerStatus",
"enabled": false,
"discordClient": "discord",
"channelID": "",
"chatColors": {},
"color": 16761867,
"ignoreChats": [
"ChatSquad"
]
"messageIDs": [],
"updateInterval": 60000,
"disableStatus": false
},
{
"plugin": "AutoKickUnassigned",
"enabled": true,
"warningMessage": "Join a squad, you are are unassigned and will be kicked",
"kickMessage": "Unassigned - automatically removed",
"frequencyOfWarnings": 30,
"unassignedTimer": 360,
"playerThreshold": 93,
"roundStartDelay": 900,
"ignoreAdmins": false,
"ignoreWhitelist": false
},
{
"plugin": "DiscordDebug",
@ -149,33 +96,10 @@
"events": []
},
{
"plugin": "DiscordRcon",
"plugin": "IntervalledBroadcasts",
"enabled": false,
"discordClient": "discord",
"channelID": "",
"permissions": {},
"prependAdminNameInBroadcast": false
},
{
"plugin": "DiscordRoundWinner",
"enabled": true,
"discordClient": "discord",
"channelID": "",
"color": 16761867
},
{
"plugin": "DiscordServerStatus",
"enabled": false,
"discordClient": "discord",
"messageIDs": [],
"updateInterval": 60000,
"disableStatus": false
},
{
"plugin": "DiscordSubsystemRestarter",
"enabled": false,
"discordClient": "discord",
"role": ""
"broadcasts": [],
"interval": 300000
},
{
"plugin": "DiscordTeamkill",
@ -185,19 +109,6 @@
"color": 16761867,
"disableSCBL": false
},
{
"plugin": "IntervalledBroadcasts",
"enabled": false,
"broadcasts": [],
"interval": 300000
},
{
"plugin": "SCBLInfo",
"enabled": true,
"discordClient": "discord",
"channelID": "",
"threshold": 6
},
{
"plugin": "SeedingMode",
"enabled": true,
@ -208,10 +119,62 @@
"liveThreshold": 52,
"liveMessage": "Live!"
},
{
"plugin": "AutoTKWarn",
"enabled": true,
"message": "Please apologise for ALL TKs in ALL chat!"
},
{
"plugin": "DBLog",
"enabled": false,
"database": "mysql",
"overrideServerID": null
},
{
"plugin": "DiscordRoundWinner",
"enabled": true,
"discordClient": "discord",
"channelID": "",
"color": 16761867
},
{
"plugin": "ChatCommands",
"enabled": true,
"commands": [
{
"command": "squadjs",
"type": "warn",
"response": "This server is powered by SquadJS.",
"ignoreChats": []
}
]
},
{
"plugin": "DiscordAdminCamLogs",
"enabled": false,
"discordClient": "discord",
"channelID": "",
"color": 16761867
},
{
"plugin": "DiscordRcon",
"enabled": false,
"discordClient": "discord",
"channelID": "",
"permissions": {},
"prependAdminNameInBroadcast": false
},
{
"plugin": "TeamRandomizer",
"enabled": true,
"command": "randomize"
},
{
"plugin": "SCBLInfo",
"enabled": true,
"discordClient": "discord",
"channelID": "",
"threshold": 6
}
],
"logger": {

View File

@ -1,6 +1,6 @@
{
"name": "SquadJS",
"version": "2.0.0-beta1",
"version": "2.0.1",
"repository": "https://github.com/Thomas-Smyth/SquadJS.git",
"author": "Thomas Smyth <https://github.com/Thomas-Smyth>",
"license": "BSL-1.0",

View File

@ -8,7 +8,7 @@ import sequelize from 'sequelize';
import Logger from 'core/logger';
import SquadServer from './index.js';
import plugins from './plugins/index.js';
import Plugins from './plugins/index.js';
const { Sequelize } = sequelize;
@ -16,6 +16,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default class SquadServerFactory {
static async buildFromConfig(config) {
const plugins = await Plugins.getPlugins();
for (const plugin of Object.keys(plugins)) {
Logger.setColor(plugin, 'magentaBright');
}
@ -33,9 +35,6 @@ export default class SquadServerFactory {
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 = {};
@ -100,13 +99,6 @@ export default class SquadServerFactory {
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);
@ -160,7 +152,9 @@ export default class SquadServerFactory {
return SquadServerFactory.buildFromConfigString(SquadServerFactory.readConfigFile(configPath));
}
static buildConfig() {
static async buildConfig() {
const plugins = await Plugins.getPlugins();
const templatePath = path.resolve(__dirname, './templates/config-template.json');
const templateString = fs.readFileSync(templatePath, 'utf8');
const template = SquadServerFactory.parseConfig(templateString);
@ -183,13 +177,17 @@ export default class SquadServerFactory {
return template;
}
static buildConfigFile() {
static async buildConfigFile() {
const configPath = path.resolve(__dirname, '../config.json');
const config = JSON.stringify(SquadServerFactory.buildConfig(), null, 2);
fs.writeFileSync(configPath, config);
const config = await SquadServerFactory.buildConfig();
const configString = JSON.stringify(config, null, 2);
fs.writeFileSync(configPath, configString);
}
static buildReadmeFile() {
static async buildReadmeFile() {
const plugins = await Plugins.getPlugins();
const pluginKeys = Object.keys(plugins).sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
);

View File

@ -6,11 +6,12 @@ import Gamedig from 'gamedig';
import Logger from 'core/logger';
import { SQUADJS_API_DOMAIN } from 'core/constants';
import { Layers } from './layers/index.js';
import LogParser from './log-parser/index.js';
import Rcon from './rcon.js';
import { SQUADJS_VERSION } from './utils/constants.js';
import { SquadLayers } from './utils/squad-layers.js';
import fetchAdminLists from './utils/admin-lists.js';
@ -30,11 +31,10 @@ export default class SquadServer extends EventEmitter {
this.players = [];
this.admins = {};
this.adminsInAdminCam = {};
this.plugins = [];
this.squadLayers = new SquadLayers(options.squadLayersSource);
this.setupRCON();
this.setupLogParser();
@ -61,7 +61,9 @@ export default class SquadServer extends EventEmitter {
1,
`Beginning to watch ${this.options.host}:${this.options.queryPort}...`
);
await this.squadLayers.pull();
await Layers.pull();
this.admins = await fetchAdminLists(this.options.adminLists);
await this.rcon.connect();
@ -101,6 +103,23 @@ export default class SquadServer extends EventEmitter {
});
});
this.rcon.on('POSSESSED_ADMIN_CAMERA', async (data) => {
data.player = await this.getPlayerBySteamID(data.steamID);
this.adminsInAdminCam[data.steamID] = data.time;
this.emit('POSSESSED_ADMIN_CAMERA', data);
});
this.rcon.on('UNPOSSESSED_ADMIN_CAMERA', async (data) => {
data.player = await this.getPlayerBySteamID(data.steamID);
data.duration = data.time.getTime() - this.adminsInAdminCam[data.steamID].getTime();
delete this.adminsInAdminCam[data.steamID];
this.emit('UNPOSSESSED_ADMIN_CAMERA', data);
});
this.rcon.on('RCON_ERROR', (data) => {
this.emit('RCON_ERROR', data);
});
@ -131,12 +150,21 @@ export default class SquadServer extends EventEmitter {
this.emit('ADMIN_BROADCAST', data);
});
this.logParser.on('NEW_GAME', (data) => {
data.layer = this.squadLayers.getLayerByLayerClassname(data.layerClassname);
this.logParser.on('DEPLOYABLE_DAMAGED', async (data) => {
data.player = await this.getPlayerByNameSuffix(data.playerSuffix);
this.layerHistory.unshift({ ...data.layer, time: data.time });
delete data.playerSuffix;
this.emit('DEPLOYABLE_DAMAGED', data);
});
this.logParser.on('NEW_GAME', async (data) => {
data.layer = await Layers.getLayerByClassname(data.layerClassname);
this.layerHistory.unshift({ layer: data.layer, time: data.time });
this.layerHistory = this.layerHistory.slice(0, this.layerHistoryMaxLength);
this.currentLayer = data.layer;
this.emit('NEW_GAME', data);
});
@ -301,16 +329,21 @@ export default class SquadServer extends EventEmitter {
Logger.verbose('SquadServer', 1, `Updating layer information...`);
try {
const layerInfo = await this.rcon.getLayerInfo();
const currentMap = await this.rcon.getCurrentMap();
const nextMap = await this.rcon.getNextMap();
const nextMapToBeVoted = nextMap === 'To be voted';
const currentLayer = await Layers.getLayerByName(currentMap.layer);
const nextLayer = nextMapToBeVoted ? null : await Layers.getLayerByName(nextMap.layer);
if (this.layerHistory.length === 0) {
const layer = this.squadLayers.getLayerByLayerName(layerInfo.currentLayer);
this.layerHistory.unshift({ ...layer, time: Date.now() });
this.layerHistory.unshift({ layer: currentLayer, time: Date.now() });
this.layerHistory = this.layerHistory.slice(0, this.layerHistoryMaxLength);
}
this.nextLayer = layerInfo.nextLayer;
this.currentLayer = currentLayer;
this.nextLayer = nextLayer;
this.nextLayerToBeVoted = nextMapToBeVoted;
this.emit('UPDATED_LAYER_INFORMATION');
} catch (err) {

View File

@ -0,0 +1,4 @@
import Layer from './layer.js';
import Layers from './layers.js';
export { Layer, Layers };

View File

@ -0,0 +1,47 @@
export default class Layer {
constructor(data) {
this.name = data.Name;
this.classname = data.rawName;
this.map = {
name: data.mapName
};
this.gamemode = data.gamemode;
this.gamemodeType = data.type;
this.version = data.layerVersion;
this.size = data.mapSize;
this.sizeType = data.mapSizeType;
this.numberOfCapturePoints = parseInt(data.capturePoints);
this.lighting = {
name: data.lighting,
classname: data.lightingLevel
};
this.teams = [
{
faction: data.team1.faction,
name: data.team1.teamSetupName,
tickets: data.team1.tickets,
commander: data.team1.commander,
vehicles: (data.team1.vehicles || []).map((vehicle) => ({
name: vehicle.type,
classname: vehicle.rawType,
count: vehicle.count,
spawnDelay: vehicle.delay,
respawnDelay: vehicle.respawnTime
}))
},
{
faction: data.team2.faction,
name: data.team2.teamSetupName,
tickets: data.team2.tickets,
commander: data.team2.commander,
vehicles: (data.team2.vehicles || []).map((vehicle) => ({
name: vehicle.type,
classname: vehicle.rawType,
count: vehicle.count,
spawnDelay: vehicle.delay,
respawnDelay: vehicle.respawnTime
}))
}
];
}
}

View File

@ -0,0 +1,53 @@
import axios from 'axios';
import Logger from 'core/logger';
import Layer from './layer.js';
class Layers {
constructor() {
this.layers = [];
this.pulled = false;
}
async pull(force = false) {
if (this.pulled && !force) {
Logger.verbose('Layers', 1, 'Already pulled layers.');
return;
}
Logger.verbose('Layers', 1, 'Pulling layers...');
const response = await axios.get(
'https://raw.githubusercontent.com/Squad-Wiki-Editorial/squad-wiki-pipeline-map-data/dev/completed_output/2.0/finished_2.0.json'
);
this.layers = [];
for (const layer of response.data.Maps) {
this.layers.push(new Layer(layer));
}
Logger.verbose('Layers', 1, `Pulled ${this.layers.length} layers.`);
return this.layers;
}
async getLayerByCondition(condition) {
await this.pull();
const matches = this.layers.filter(condition);
if (matches.length === 1) return matches[0];
return null;
}
getLayerByName(name) {
return this.getLayerByCondition((layer) => layer.name === name);
}
getLayerByClassname(classname) {
return this.getLayerByCondition((layer) => layer.classname === classname);
}
}
export default new Layers();

View File

@ -0,0 +1,20 @@
export default {
regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQDeployable::)?TakeDamage\(\): ([A-z0-9_]+)_C_[0-9]+: ([0-9.]+) damage attempt by causer ([A-z0-9_]+)_C_[0-9]+ instigator (.+) with damage type ([A-z0-9_]+)_C health remaining ([0-9.]+)/,
onMatch: (args, logParser) => {
const data = {
raw: args[0],
time: args[1],
chainID: args[2],
deployable: args[3],
damage: parseFloat(args[4]),
weapon: args[5],
playerSuffix: args[6],
damageType: args[7],
healthRemaining: args[8]
};
logParser.eventStore[args[3]] = data;
logParser.emit('DEPLOYABLE_DAMAGED', data);
}
};

View File

@ -1,6 +1,7 @@
import LogParser from 'core/log-parser';
import AdminBroadcast from './admin-broadcast.js';
import DeployableDamaged from './deployable-damaged.js';
import NewGame from './new-game.js';
import PlayerConnected from './player-connected.js';
import PlayerDamaged from './player-damaged.js';
@ -21,6 +22,7 @@ export default class SquadLogParser extends LogParser {
getRules() {
return [
AdminBroadcast,
DeployableDamaged,
NewGame,
PlayerConnected,
PlayerDamaged,

View File

@ -443,8 +443,8 @@ export default class DBLog extends BasePlugin {
dlc: info.dlc,
mapClassname: info.mapClassname,
layerClassname: info.layerClassname,
map: info.layer ? info.layer.map : null,
layer: info.layer ? info.layer.layer : null,
map: info.layer ? info.layer.map.name : null,
layer: info.layer ? info.layer.name : null,
startTime: info.time
});
}

View File

@ -31,25 +31,21 @@ export default class DiscordAdminCamLogs extends DiscordBasePlugin {
this.adminsInCam = {};
this.onPlayerPossess = this.onPlayerPossess.bind(this);
this.onPlayerUnPossess = this.onPlayerUnPossess.bind(this);
this.onEntry = this.onEntry.bind(this);
this.onExit = this.onExit.bind(this);
}
async mount() {
this.server.on('PLAYER_POSSESS', this.onPlayerPossess);
this.server.on('PLAYER_UNPOSSESS', this.onPlayerUnPossess);
this.server.on('POSSESSED_ADMIN_CAMERA', this.onEntry);
this.server.on('UNPOSSESSED_ADMIN_CAMERA', this.onExit);
}
async unmount() {
this.server.removeEventListener('PLAYER_POSSESS', this.onPlayerPossess);
this.server.removeEventListener('PLAYER_UNPOSSESS', this.onPlayerUnPossess);
this.server.removeEventListener('POSSESSED_ADMIN_CAMERA', this.onEntry);
this.server.removeEventListener('UNPOSSESSED_ADMIN_CAMERA', this.onExit);
}
async onPlayerPossess(info) {
if (info.player === null || info.possessClassname !== 'CameraMan') return;
this.adminsInCam[info.player.steamID] = info.time;
async onEntry(info) {
await this.sendDiscordMessage({
embed: {
title: `Admin Entered Admin Camera`,
@ -71,14 +67,7 @@ export default class DiscordAdminCamLogs extends DiscordBasePlugin {
});
}
async onPlayerUnPossess(info) {
if (
info.player === null ||
info.switchPossess === true ||
!(info.player.steamID in this.adminsInCam)
)
return;
async onExit(info) {
await this.sendDiscordMessage({
embed: {
title: `Admin Left Admin Camera`,
@ -96,15 +85,11 @@ export default class DiscordAdminCamLogs extends DiscordBasePlugin {
},
{
name: 'Time in Admin Camera',
value: `${Math.round(
(info.time.getTime() - this.adminsInCam[info.player.steamID].getTime()) / 60000
)} mins`
value: `${Math.round(info.duration / 60000)} mins`
}
],
timestamp: info.time.toISOString()
}
});
delete this.adminsInCam[info.player.steamID];
}
}

View File

@ -48,7 +48,7 @@ export default class DiscordRoundWinner extends DiscordBasePlugin {
fields: [
{
name: 'Message',
value: `${info.winner} won on ${info.layer}.`
value: `${info.winner} won on ${this.server.layerHistory[1].layer.name}.`
}
],
timestamp: info.time.toISOString()

View File

@ -69,7 +69,7 @@ export default class DiscordServerStatus extends BasePlugin {
await this.options.discordClient.user.setActivity(
`(${this.server.a2sPlayerCount}/${this.server.publicSlots}) ${
this.server.layerHistory[0].layer || 'Unknown'
this.server.layerHistory[0].layer.name || 'Unknown'
}`,
{ type: 'WATCHING' }
);
@ -92,12 +92,15 @@ export default class DiscordServerStatus extends BasePlugin {
},
{
name: 'Current Layer',
value: `\`\`\`${this.server.layerHistory[0].layer || 'Unknown'}\`\`\``,
value: `\`\`\`${this.server.currentLayer.name || 'Unknown'}\`\`\``,
inline: true
},
{
name: 'Next Layer',
value: `\`\`\`${this.server.nextLayer || 'Unknown'}\`\`\``,
value: `\`\`\`${
this.server.nextLayer?.name ||
(this.server.nextLayerToBeVoted ? 'To be voted' : 'Unknown')
}\`\`\``,
inline: true
}
];

View File

@ -1,46 +1,41 @@
import AutoKickUnassigned from './auto-kick-unassigned.js';
import AutoTKWarn from './auto-tk-warn.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 DiscordRcon from './discord-rcon.js';
import DiscordRoundWinner from './discord-round-winner.js';
import DiscordServerStatus from './discord-server-status.js';
import DiscordSubsystemRestarter from './discord-subsystem-restarter.js';
import DiscordTeamkill from './discord-teamkill.js';
import IntervalledBroadcasts from './intervalled-broadcasts.js';
import SCBLInfo from './scbl-info.js';
import SeedingMode from './seeding-mode.js';
import TeamRandomizer from './team-randomizer.js';
import fs from 'fs';
const plugins = [
AutoKickUnassigned,
AutoTKWarn,
ChatCommands,
DBLog,
DiscordAdminBroadcast,
DiscordAdminCamLogs,
DiscordAdminRequest,
DiscordChat,
DiscordDebug,
DiscordRcon,
DiscordRoundWinner,
DiscordServerStatus,
DiscordSubsystemRestarter,
DiscordTeamkill,
IntervalledBroadcasts,
SCBLInfo,
SeedingMode,
TeamRandomizer
];
import Logger from 'core/logger';
import path from 'path';
import { fileURLToPath } from 'url';
const pluginsByName = {};
for (const plugin of plugins) {
pluginsByName[plugin.name] = plugin;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
class Plugins {
constructor() {
this.plugins = null;
}
async getPlugins(force = false) {
if (this.plugins && !force) return this.plugins;
this.plugins = {};
const dir = await fs.promises.opendir(path.join(__dirname, './'));
const pluginFilenames = [];
for await (const dirent of dir) {
if (!dirent.isFile()) continue;
if (
['index.js', 'base-plugin.js', 'discord-base-plugin.js', 'readme.md'].includes(dirent.name)
)
continue;
pluginFilenames.push(dirent.name);
}
for (const pluginFilename of pluginFilenames) {
Logger.verbose('Plugins', 1, `Loading plugin file ${pluginFilename}...`);
const { default: Plugin } = await import(`./${pluginFilename}`);
this.plugins[Plugin.name] = Plugin;
}
return this.plugins;
}
}
export default pluginsByName;
export default new Plugins();

View File

@ -88,11 +88,13 @@ export default class SCBLInfo extends DiscordBasePlugin {
{ id: info.player.steamID }
);
if (!data.steamUser)
if (!data.steamUser) {
this.verbose(
2,
`Player ${info.name} (Steam ID: ${info.steamID}) is not listed in the Squad Community Ban List.`
);
return;
}
if (data.steamUser.reputationPoints < this.options.threshold) {
this.verbose(

View File

@ -1,29 +1,68 @@
import Logger from 'core/logger';
import Rcon from 'core/rcon';
export default class SquadRcon extends Rcon {
processChatPacket(decodedPacket) {
const match = decodedPacket.body.match(
const matchChat = decodedPacket.body.match(
/\[(ChatAll|ChatTeam|ChatSquad|ChatAdmin)] \[SteamID:([0-9]{17})] (.+?) : (.*)/
);
if (matchChat) {
Logger.verbose('SquadRcon', 2, `Matched chat message: ${decodedPacket.body}`);
this.emit('CHAT_MESSAGE', {
raw: decodedPacket.body,
chat: match[1],
steamID: match[2],
name: match[3],
message: match[4],
time: new Date()
});
this.emit('CHAT_MESSAGE', {
raw: decodedPacket.body,
chat: matchChat[1],
steamID: matchChat[2],
name: matchChat[3],
message: matchChat[4],
time: new Date()
});
return;
}
const matchPossessedAdminCam = decodedPacket.body.match(
/\[SteamID:([0-9]{17})] (.+?) has possessed admin camera./
);
if (matchPossessedAdminCam) {
Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`);
this.emit('POSSESSED_ADMIN_CAMERA', {
raw: decodedPacket.body,
steamID: matchPossessedAdminCam[1],
name: matchPossessedAdminCam[2],
time: new Date()
});
return;
}
const matchUnpossessedAdminCam = decodedPacket.body.match(
/\[SteamID:([0-9]{17})] (.+?) has unpossessed admin camera./
);
if (matchUnpossessedAdminCam) {
Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`);
this.emit('UNPOSSESSED_ADMIN_CAMERA', {
raw: decodedPacket.body,
steamID: matchUnpossessedAdminCam[1],
name: matchUnpossessedAdminCam[2],
time: new Date()
});
}
}
async broadcast(message) {
await this.execute(`AdminBroadcast ${message}`);
async getCurrentMap() {
const response = await this.execute('ShowCurrentMap');
const match = response.match(/^Current level is (.*), layer is (.*)/);
return { level: match[1], layer: match[2] };
}
async getLayerInfo() {
async getNextMap() {
const response = await this.execute('ShowNextMap');
const match = response.match(/^Current map is (.+), Next map is (.*)/);
return { currentLayer: match[1], nextLayer: match[2].length === 0 ? null : match[2] };
const match = response.match(/^Next level is (.*), layer is (.*)/);
return {
level: match[1] !== '' ? match[1] : null,
layer: match[2] !== 'To be voted' ? match[2] : null
};
}
async getListPlayers() {
@ -49,6 +88,10 @@ export default class SquadRcon extends Rcon {
return players;
}
async broadcast(message) {
await this.execute(`AdminBroadcast ${message}`);
}
async warn(steamID, message) {
await this.execute(`AdminWarn "${steamID}" ${message}`);
}

View File

@ -1,5 +1,8 @@
import SquadServerFactory from '../factory.js';
console.log('Building config...');
SquadServerFactory.buildConfigFile();
console.log('Done.');
SquadServerFactory.buildConfigFile()
.then(() => {
console.log('Done.');
})
.catch(console.log);

View File

@ -1,5 +1,8 @@
import SquadServerFactory from '../factory.js';
console.log('Building readme...');
SquadServerFactory.buildReadmeFile();
console.log('Done.');
SquadServerFactory.buildReadmeFile()
.then(() => {
console.log('Done.');
})
.catch(console.log);

View File

@ -22,41 +22,6 @@
},
"connectors": {
"discord": "Discord Login Token",
"squadlayerpool": {
"type": "buildPoolFromFilter",
"filter": {
"whitelistedLayers": null,
"blacklistedLayers": null,
"whitelistedMaps": null,
"blacklistedMaps": null,
"whitelistedGamemodes": null,
"blacklistedGamemodes": ["Training"],
"flagCountMin": null,
"flagCountMax": null,
"hasCommander": null,
"hasTanks": null,
"hasHelicopters": null
},
"activeLayerFilter": {
"historyResetTime": 18000000,
"layerHistoryTolerance": 8,
"mapHistoryTolerance": 4,
"gamemodeHistoryTolerance": {
"Invasion": 4
},
"gamemodeRepetitiveTolerance": {
"Invasion": 4
},
"playerCountComplianceEnabled": true,
"factionComplianceEnabled": true,
"factionHistoryTolerance": {
"RUS": 4
},
"factionRepetitiveTolerance": {
"RUS": 4
}
}
},
"mysql": {
"host": "host",
"port": 3306,

View File

@ -8,7 +8,7 @@ import Logger from 'core/logger';
const __dirname = fileURLToPath(import.meta.url);
export default async function fetchAdminLists(adminLists) {
Logger.verbose('SquadServer', 2, `Fetching Admin Lists...`);
Logger.verbose('SquadServer', 1, `Fetching Admin Lists...`);
const groups = {};
const admins = {};

View File

@ -1,274 +0,0 @@
import axios from 'axios';
import didYouMean from 'didyoumean';
import fs from 'fs';
class SquadLayersBase {
get layerNames() {
return this.layers.map((layer) => layer.name);
}
getLayerByCondition(condition) {
const results = this.layers.filter(condition);
return results.length === 1 ? results[0] : null;
}
getLayerByLayerName(layerName) {
return this.getLayerByCondition((layer) => layer.layer === layerName);
}
getLayerByLayerClassname(layerClassname) {
return this.getLayerByCondition((layer) => layer.layerClassname === layerClassname);
}
getLayerByLayerNameAutoCorrection(layerName) {
return this.getLayerByLayerName(didYouMean(layerName, this.layerNames()));
}
getLayerByNumber(layerNumber) {
return this.getLayerByCondition((layer) => layer.layerNumber === layerNumber);
}
}
class SquadLayers extends SquadLayersBase {
constructor(source) {
super();
this.source =
source || 'https://raw.githubusercontent.com/Thomas-Smyth/squad-layers/master/layers.json';
this.pulled = false;
}
async pull(force = false) {
if (this.pulled && !force) return;
this.layers = (await axios.get(this.source)).data;
for (let i = 0; i < this.layers.length; i++) this.layers[i].layerNumber = i + 1;
}
buildPoolFromLayerNames(layerNames, activeFilter) {
return new SquadLayersPool(
this.layers.filter((layer) => layerNames.includes(layer.layer)),
activeFilter
);
}
buildPoolFromLayerNamesAutoCorrection(layerNames, activeFilter) {
return this.buildPoolFromLayerNames(
layerNames.map((layerName) => this.getLayerByLayerNameAutoCorrection(layerName)),
activeFilter
);
}
buildPoolFromFile(path, activeFilter, delimiter = '\n') {
return this.buildPoolFromLayerNames(
fs.readFileSync(path, 'utf8').split(delimiter),
activeFilter
);
}
buildPoolFromFilter(filter, activeFilter) {
const whitelistedLayers = filter.whitelistedLayers || null;
const blacklistedLayers = filter.blacklistedLayers || null;
const whitelistedMaps = filter.whitelistedMaps || null;
const blacklistedMaps = filter.blacklistedMaps || null;
const whitelistedGamemodes = filter.whitelistedGamemodes || null;
const blacklistedGamemodes = filter.blacklistedGamemodes || ['Training'];
const flagCountMin = filter.flagCountMin || null;
const flagCountMax = filter.flagCountMax || null;
const hasCommander = filter.hasCommander || null;
const hasTanks = filter.hasTanks || null;
const hasHelicopters = filter.hasHelicopters || null;
const layers = [];
for (const layer of this.layers) {
// Whitelist / Blacklist Layers
if (whitelistedLayers !== null && !whitelistedLayers.includes(layer.layer)) continue;
if (blacklistedLayers !== null && blacklistedLayers.includes(layer.layer)) continue;
// Whitelist / Blacklist Maps
if (whitelistedMaps !== null && !whitelistedMaps.includes(layer.map)) continue;
if (blacklistedMaps !== null && blacklistedMaps.includes(layer.map)) continue;
// Whitelist / Blacklist Gamemodes
if (whitelistedGamemodes !== null && !whitelistedGamemodes.includes(layer.gamemode)) continue;
if (blacklistedGamemodes !== null && blacklistedGamemodes.includes(layer.gamemode)) continue;
// Flag Count
if (flagCountMin !== null && layer.flagCount < flagCountMin) continue;
if (flagCountMax !== null && layer.flagCount > flagCountMax) continue;
// Other Properties
if (hasCommander !== null && layer.commander !== hasCommander) continue;
if (hasTanks !== null && (layer.tanks !== 'N/A') !== hasTanks) continue;
if (hasHelicopters !== null && (layer.helicopters !== 'N/A') !== hasHelicopters) continue;
layers.push(layer);
}
return new SquadLayersPool(layers, activeFilter);
}
}
class SquadLayersPool extends SquadLayersBase {
constructor(layers, activeFilter = null) {
super();
this.layers = layers;
for (let i = 0; i < this.layers.length; i++) this.layers[i].layerNumber = i + 1;
this.activeFilter = activeFilter;
}
inPool(layer) {
if (typeof layer === 'object') layer = layer.layer;
return super.layerNames.includes(layer);
}
isHistoryCompliant(layerHistory, layer) {
if (this.activeFilter === null) return true;
if (typeof layer === 'object') layer = layer.layer;
for (
let i = 0;
i < Math.min(layerHistory.length, this.activeFilter.layerHistoryTolerance);
i++
) {
if (new Date() - layerHistory[i].time > this.activeFilter.historyResetTime) return true;
if (layerHistory[i].layer === layer) return false;
}
return true;
}
isMapHistoryCompliant(layerHistory, layer) {
if (this.activeFilter === null) return true;
if (typeof layer === 'string') layer = this.getLayerByLayerName(layer);
for (let i = 0; i < Math.min(layerHistory.length, this.activeFilter.mapHistoryTolerance); i++) {
if (new Date() - layerHistory[i].time > this.activeFilter.historyResetTime) return true;
if (layerHistory[i].map === layer.map) return false;
}
return true;
}
isGamemodeHistoryCompliant(layerHistory, layer) {
if (this.activeFilter === null) return true;
if (typeof layer === 'string') layer = this.getLayerByLayerName(layer);
const gamemodeHistoryTolerance = this.activeFilter.gamemodeHistoryTolerance[layer.gamemode];
if (!gamemodeHistoryTolerance) return true;
for (let i = 0; i < Math.min(layerHistory.length, gamemodeHistoryTolerance); i++) {
if (new Date() - layerHistory[i].time > this.activeFilter.historyResetTime) return true;
if (layerHistory[i].gamemode === layer.gamemode) return false;
}
return true;
}
isGamemodeRepetitiveCompliant(layerHistory, layer) {
if (this.activeFilter === null) return true;
if (typeof layer === 'string') layer = this.getLayerByLayerName(layer);
const gamemodeRepetitiveTolerance = this.activeFilter.gamemodeRepetitiveTolerance[
layer.gamemode
];
if (!gamemodeRepetitiveTolerance) return true;
for (let i = 0; i < Math.min(layerHistory.length, gamemodeRepetitiveTolerance); i++) {
if (new Date() - layerHistory[i].time > this.activeFilter.historyResetTime) return true;
if (layerHistory[i].gamemode.gamemode !== layer.gamemode) return true;
}
return false;
}
isFactionCompliant(layerHistory, layer) {
if (this.activeFilter === null || this.activeFilter.factionComplianceEnabled === false)
return true;
if (layerHistory.length === 0) return true;
if (typeof layer === 'string') layer = this.getLayerByLayerName(layer);
return (
!layerHistory[0] ||
(layerHistory[0].teamOne.faction !== layer.teamTwo.faction &&
layerHistory[0].teamTwo.faction !== layer.teamOne.faction)
);
}
isFactionHistoryCompliant(layerHistory, layer, faction = null) {
if (this.activeFilter === null) return true;
if (typeof layer === 'string') layer = SquadLayers.getLayerByLayerName(layer);
if (faction === null) {
return (
this.isFactionHistoryCompliant(layerHistory, layer, layer.teamOne.faction) &&
this.isFactionHistoryCompliant(layerHistory, layer, layer.teamTwo.faction)
);
} else {
const factionThreshold = this.activeFilter.factionHistoryTolerance[faction];
if (!factionThreshold) return true;
for (let i = 0; i < Math.min(layerHistory.length, factionThreshold); i++) {
if (new Date() - layerHistory[i].time > this.activeFilter.historyResetTime) return true;
if (
layerHistory[i].teamOne.faction === faction ||
layerHistory[i].teamTwo.faction === faction
)
return false;
}
return true;
}
}
isFactionRepetitiveCompliant(layerHistory, layer, faction = null) {
if (this.activeFilter === null) return true;
if (typeof layer === 'string') layer = SquadLayers.getLayerByLayerName(layer);
if (faction === null) {
return (
this.isFactionRepetitiveCompliant(layerHistory, layer, layer.teamOne.faction) &&
this.isFactionRepetitiveCompliant(layerHistory, layer, layer.teamTwo.faction)
);
} else {
const factionThreshold = this.activeFilter.factionRepetitiveTolerance[faction];
if (!factionThreshold) return true;
for (let i = 0; i < Math.min(layerHistory.length, factionThreshold); i++) {
if (new Date() - layerHistory[i].time > this.activeFilter.historyResetTime) return true;
if (
layerHistory[i].teamOne.faction !== faction &&
layerHistory[i].teamTwo.faction !== faction
)
return true;
}
return false;
}
}
isPlayerCountCompliant(server, layer) {
if (this.activeFilter === null || this.activeFilter.playerCountComplianceEnabled === false)
return true;
if (typeof layer === 'string') layer = this.getLayerByLayerName(layer);
return !(
server.players.length > layer.estimatedSuitablePlayerCount.max ||
server.players.length < layer.estimatedSuitablePlayerCount.min
);
}
}
export { SquadLayers, SquadLayersPool };