From 8ed6fb07355682bed65c55adbb2477a3edbaaf19 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Thu, 1 Aug 2024 16:49:52 +0100 Subject: [PATCH 01/28] Remove existing plugin system --- .husky/pre-commit | 1 - README.md | 774 +--- config.json | 229 +- index.js => index.ts | 17 +- package.json | 7 +- squad-server/factory.js | 218 +- squad-server/index.js | 2 - squad-server/plugins/auto-kick-unassigned.js | 245 -- squad-server/plugins/auto-tk-warn.js | 49 - squad-server/plugins/base-plugin.js | 51 - squad-server/plugins/cbl-info.js | 163 - squad-server/plugins/chat-commands.js | 52 - squad-server/plugins/db-log.js | 760 ---- .../plugins/discord-admin-broadcast.js | 61 - .../plugins/discord-admin-cam-logs.js | 105 - squad-server/plugins/discord-admin-request.js | 191 - .../plugins/discord-base-message-updater.js | 188 - squad-server/plugins/discord-base-plugin.js | 41 - squad-server/plugins/discord-chat.js | 90 - squad-server/plugins/discord-debug.js | 40 - .../discord-fob-hab-explosion-damage.js | 86 - squad-server/plugins/discord-killfeed.js | 108 - squad-server/plugins/discord-placeholder.js | 59 - squad-server/plugins/discord-rcon.js | 119 - squad-server/plugins/discord-round-winner.js | 58 - squad-server/plugins/discord-roundended.js | 79 - squad-server/plugins/discord-server-status.js | 133 - squad-server/plugins/discord-squad-created.js | 79 - .../plugins/discord-subsystem-restarter.js | 74 - squad-server/plugins/discord-teamkill.js | 106 - squad-server/plugins/fog-of-war.js | 46 - squad-server/plugins/index.js | 50 - .../plugins/intervalled-broadcasts.js | 49 - squad-server/plugins/readme.md | 45 - squad-server/plugins/seeding-mode.js | 103 - squad-server/plugins/socket-io-api.js | 181 - squad-server/plugins/team-randomizer.js | 65 - squad-server/scripts/build-config-file.js | 8 - squad-server/scripts/build-readme.js | 8 - .../templates/SquadJS-Dashboard-v2.json | 3623 ---------------- .../templates/SquadJS-Dashboard-v3.json | 3636 ----------------- squad-server/templates/config-template.json | 55 - squad-server/templates/readme-template.md | 289 -- tsconfig.json | 5 + 44 files changed, 32 insertions(+), 12316 deletions(-) rename index.js => index.ts (52%) delete mode 100644 squad-server/plugins/auto-kick-unassigned.js delete mode 100644 squad-server/plugins/auto-tk-warn.js delete mode 100644 squad-server/plugins/base-plugin.js delete mode 100644 squad-server/plugins/cbl-info.js delete mode 100644 squad-server/plugins/chat-commands.js delete mode 100644 squad-server/plugins/db-log.js delete mode 100644 squad-server/plugins/discord-admin-broadcast.js delete mode 100644 squad-server/plugins/discord-admin-cam-logs.js delete mode 100644 squad-server/plugins/discord-admin-request.js delete mode 100644 squad-server/plugins/discord-base-message-updater.js delete mode 100644 squad-server/plugins/discord-base-plugin.js delete mode 100644 squad-server/plugins/discord-chat.js delete mode 100644 squad-server/plugins/discord-debug.js delete mode 100644 squad-server/plugins/discord-fob-hab-explosion-damage.js delete mode 100644 squad-server/plugins/discord-killfeed.js delete mode 100644 squad-server/plugins/discord-placeholder.js delete mode 100644 squad-server/plugins/discord-rcon.js delete mode 100644 squad-server/plugins/discord-round-winner.js delete mode 100644 squad-server/plugins/discord-roundended.js delete mode 100644 squad-server/plugins/discord-server-status.js delete mode 100644 squad-server/plugins/discord-squad-created.js delete mode 100644 squad-server/plugins/discord-subsystem-restarter.js delete mode 100644 squad-server/plugins/discord-teamkill.js delete mode 100644 squad-server/plugins/fog-of-war.js delete mode 100644 squad-server/plugins/index.js delete mode 100644 squad-server/plugins/intervalled-broadcasts.js delete mode 100644 squad-server/plugins/readme.md delete mode 100644 squad-server/plugins/seeding-mode.js delete mode 100644 squad-server/plugins/socket-io-api.js delete mode 100644 squad-server/plugins/team-randomizer.js delete mode 100644 squad-server/scripts/build-config-file.js delete mode 100644 squad-server/scripts/build-readme.js delete mode 100644 squad-server/templates/SquadJS-Dashboard-v2.json delete mode 100644 squad-server/templates/SquadJS-Dashboard-v3.json delete mode 100644 squad-server/templates/config-template.json delete mode 100644 squad-server/templates/readme-template.md create mode 100644 tsconfig.json diff --git a/.husky/pre-commit b/.husky/pre-commit index 739630db1..c43ea2578 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,6 +2,5 @@ . "$(dirname "$0")/_/husky.sh" yarn run lint-staged -yarn run build-all git add README.md git add config.json diff --git a/README.md b/README.md index 41c9cddcc..0358e3ad9 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ SquadJS relies on being able to access the Squad server log directory in order t 2. Open the unzipped folder in your terminal. 3. Install the dependencies by running `yarn install` in your terminal. Due to the use of Yarn Workspaces it is important to use `yarn install` and **not** `npm install` as this will not work and will break stuff. 4. Configure the `config.json` file. See below for more details. -5. Start SquadJS by running `node index.js` in your terminal. +5. Start SquadJS by running `node index.ts` in your terminal. **Note** - If you are interested in testing versions of SquadJS not yet released please download/clone the `master` branch. Please also see [here](#versions-and-releases) for more information on our versions and release procedures. @@ -103,76 +103,6 @@ The following section of the configuration contains information about your Squad --- - -
- Connectors - -## Connector Configuration - -Connectors allow SquadJS to communicate with external resources. - ```json - "connectors": { - "discord": "Discord Login Token", - }, - ``` -Connectors should be named, for example the above is named `discord`, and should have the associated config against it. Configs can be specified by name in plugin options. Should a connector not be needed by any plugin then the default values can be left or you can remove it from your config file. - -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. - - -##### 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" - } - ``` - -See [Sequelize's documentation](https://sequelize.org/master/manual/getting-started.html#connecting-to-a-database) for more details. - - --- -
- -
- Plugins - -## Plugin Configuration - -The `plugins` section in your config file lists all plugins built into SquadJS - ```json - "plugins": [ - { - "plugin": "auto-tk-warn", - "disabled": false, - "message": "Please apologise for ALL TKs in ALL chat!" - } - ] - ``` - -The `disabled` field can be toggled between `true`/ `false` to enabled/disable the plugin. - -Plugin options are also specified. A full list of plugin options can be seen below. - - --- -
-
Verboseness @@ -204,707 +134,9 @@ The larger the number set in the `verboseness` section for a specified module th ## **Plugins** The following is a list of plugins built into SquadJS, you can click their title for more information: -Interested in creating your own plugin? [See more here](./squad-server/plugins/readme.md) - -
- AutoKickUnassigned -

AutoKickUnassigned

-

The AutoKickUnassigned plugin will automatically kick players that are not in a squad after a specified ammount of time.

-

Options

-
  • warningMessage

    -
    Description
    -

    Message SquadJS will send to players warning them they will be kicked

    -
    Default
    -
    Join a squad, you are unassigned and will be kicked
  • -
  • kickMessage

    -
    Description
    -

    Message to send to players when they are kicked

    -
    Default
    -
    Unassigned - automatically removed
  • -
  • frequencyOfWarnings

    -
    Description
    -

    How often in Seconds should we warn the player about being unassigned?

    -
    Default
    -
    30
  • -
  • unassignedTimer

    -
    Description
    -

    How long in Seconds to wait before a unassigned player is kicked

    -
    Default
    -
    360
  • -
  • playerThreshold

    -
    Description
    -

    Player count required for AutoKick to start kicking players, set to -1 to disable

    -
    Default
    -
    93
  • -
  • roundStartDelay

    -
    Description
    -

    Time delay in Seconds from start of the round before AutoKick starts kicking again

    -
    Default
    -
    900
  • -
  • ignoreAdmins

    -
    Description
    -

    • true: Admins will NOT be kicked
    • false: Admins WILL be kicked

    -
    Default
    -
    false
  • -
  • ignoreWhitelist

    -
    Description
    -

    • true: Reserve slot players will NOT be kicked
    • false: Reserve slot players WILL be kicked

    -
    Default
    -
    false
-
- -
- AutoTKWarn -

AutoTKWarn

-

The AutoTkWarn plugin will automatically warn players with a message when they teamkill.

-

Options

-
  • attackerMessage

    -
    Description
    -

    The message to warn attacking players with.

    -
    Default
    -
    Please apologise for ALL TKs in ALL chat!
  • -
  • victimMessage

    -
    Description
    -

    The message that will be sent to the victim.

    -
    Default
    -
    null
-
- -
- CBLInfo -

CBLInfo

-

The CBLInfo plugin alerts admins when a harmful player is detected joining their server based on data from the Community Ban List.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to alert admins through.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • threshold

    -
    Description
    -

    Admins will be alerted when a player has this or more reputation points. For more information on reputation points, see the Community Ban List's FAQ

    -
    Default
    -
    6
-
- -
- ChatCommands -

ChatCommands

-

The ChatCommands plugin can be configured to make chat commands that broadcast or warn the caller with present messages.

-

Options

-
  • commands

    -
    Description
    -

    An array of objects containing the following properties:

    • command - The command that initiates the message.
    • type - Either warn or broadcast.
    • response - The message to respond with.
    • ignoreChats - A list of chats to ignore the commands in. Use this to limit it to admins.

    -
    Default
    -
    [
    -  {
    -    "command": "squadjs",
    -    "type": "warn",
    -    "response": "This server is powered by SquadJS.",
    -    "ignoreChats": []
    -  }
    -]
-
- -
- DBLog -

DBLog

-

The mysql-log plugin will log various server statistics and events to a database. This is great for server performance monitoring and/or player stat tracking. - -Grafana: -

  • Grafana is a cool way of viewing server statistics stored in the database.
  • -
  • Install Grafana.
  • -
  • Add your database as a datasource named SquadJS.
  • -
  • Import the SquadJS Dashboard to get a preconfigured MySQL only Grafana dashboard.
  • -
  • Install any missing Grafana plugins.

-

Options

-
  • database (Required)

    -
    Description
    -

    The Sequelize connector to log server information to.

    -
    Default
    -
    mysql
  • -
  • overrideServerID

    -
    Description
    -

    A overridden server ID.

    -
    Default
    -
    null
-
- -
- DiscordAdminBroadcast -

DiscordAdminBroadcast

-

The DiscordAdminBroadcast plugin will send a copy of admin broadcasts made in game to a Discord channel.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log admin broadcasts to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • color

    -
    Description
    -

    The color of the embed.

    -
    Default
    -
    16761867
-
- -
- DiscordAdminCamLogs -

DiscordAdminCamLogs

-

The DiscordAdminCamLogs plugin will log in game admin camera usage to a Discord channel.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log admin camera usage to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • color

    -
    Description
    -

    The color of the embed.

    -
    Default
    -
    16761867
-
- -
- DiscordAdminRequest -

DiscordAdminRequest

-

The DiscordAdminRequest plugin will ping admins in a Discord channel when a player requests an admin via the !admin command in in-game chat.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log admin broadcasts to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • ignoreChats

    -
    Description
    -

    A list of chat names to ignore.

    -
    Default
    -
    []
  • Example
    -
    [
    -  "ChatSquad"
    -]
    -
  • ignorePhrases

    -
    Description
    -

    A list of phrases to ignore.

    -
    Default
    -
    []
  • Example
    -
    [
    -  "switch"
    -]
    -
  • command

    -
    Description
    -

    The command that calls an admin.

    -
    Default
    -
    admin
  • -
  • pingGroups

    -
    Description
    -

    A list of Discord role IDs to ping.

    -
    Default
    -
    []
  • Example
    -
    [
    -  "500455137626554379"
    -]
    -
  • pingHere

    -
    Description
    -

    Ping @here. Great if Admin Requests are posted to a Squad Admin ONLY channel, allows pinging only Online Admins.

    -
    Default
    -
    false
  • -
  • pingDelay

    -
    Description
    -

    Cooldown for pings in milliseconds.

    -
    Default
    -
    60000
  • -
  • color

    -
    Description
    -

    The color of the embed.

    -
    Default
    -
    16761867
  • -
  • warnInGameAdmins

    -
    Description
    -

    Should in-game admins be warned after a players uses the command and should we tell how much admins are active in-game right now.

    -
    Default
    -
    false
  • -
  • showInGameAdmins

    -
    Description
    -

    Should players know how much in-game admins there are active/online?

    -
    Default
    -
    true
-
- -
- DiscordChat -

DiscordChat

-

The DiscordChat plugin will log in-game chat to a Discord channel.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log admin broadcasts to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • chatColors

    -
    Description
    -

    The color of the embed for each chat.

    -
    Default
    -
    {}
  • Example
    -
    {
    -  "ChatAll": 16761867
    -}
    -
  • color

    -
    Description
    -

    The color of the embed.

    -
    Default
    -
    16761867
  • -
  • ignoreChats

    -
    Description
    -

    A list of chat names to ignore.

    -
    Default
    -
    [
    -  "ChatSquad"
    -]
-
- -
- DiscordDebug -

DiscordDebug

-

The DiscordDebug plugin can be used to help debug SquadJS by dumping SquadJS events to a Discord channel.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log events to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • events (Required)

    -
    Description
    -

    A list of events to dump.

    -
    Default
    -
    []
  • Example
    -
    [
    -  "PLAYER_DIED"
    -]
-
- -
- DiscordFOBHABExplosionDamage -

DiscordFOBHABExplosionDamage

-

The DiscordFOBHABExplosionDamage plugin logs damage done to FOBs and HABs by explosions to help identify engineers blowing up friendly FOBs and HABs.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log FOB/HAB explosion damage to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • color

    -
    Description
    -

    The color of the embeds.

    -
    Default
    -
    16761867
-
- -
- DiscordKillFeed -

DiscordKillFeed

-

The DiscordKillFeed plugin logs all wounds and related information to a Discord channel for admins to review.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log teamkills to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • color

    -
    Description
    -

    The color of the embeds.

    -
    Default
    -
    16761867
  • -
  • disableCBL

    -
    Description
    -

    Disable Community Ban List information.

    -
    Default
    -
    false
-
- -
- DiscordPlaceholder -

DiscordPlaceholder

-

The DiscordPlaceholder plugin allows you to make your bot create placeholder messages that can be used when configuring other plugins.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • command

    -
    Description
    -

    Command to create Discord placeholder.

    -
    Default
    -
    !placeholder
  • -
  • channelID (Required)

    -
    Description
    -

    The bot will only answer with a placeholder on this channel

    -
    Default
    -
-
- -
- DiscordRcon -

DiscordRcon

-

The DiscordRcon plugin allows a specified Discord channel to be used as a RCON console to run RCON commands.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    ID of channel to turn into RCON console.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • permissions

    -
    Description
    -

    -
    Default
    -
    {}
  • Example
    -
    {
    -  "123456789123456789": [
    -    "AdminBroadcast",
    -    "AdminForceTeamChange",
    -    "AdminDemoteCommander"
    -  ]
    -}
    -
  • prependAdminNameInBroadcast

    -
    Description
    -

    Prepend admin names when making announcements.

    -
    Default
    -
    false
-
- -
- DiscordRoundWinner -

DiscordRoundWinner

-

The DiscordRoundWinner plugin will send the round winner to a Discord channel.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log admin broadcasts to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • color

    -
    Description
    -

    The color of the embed.

    -
    Default
    -
    16761867
-
- -
- DiscordRoundEnded -

DiscordRoundEnded

-

The DiscordRoundEnded plugin will send the round winner to a Discord channel.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log round end events to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • color

    -
    Description
    -

    The color of the embed.

    -
    Default
    -
    16761867
-
- -
- DiscordServerStatus -

DiscordServerStatus

-

The DiscordServerStatus plugin can be used to get the server status in Discord.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • messageStore (Required)

    -
    Description
    -

    Sequelize connector name.

    -
    Default
    -
    sqlite
  • -
  • command

    -
    Description
    -

    Command name to get message.

    -
    Default
    -
    !status
  • -
  • disableSubscriptions

    -
    Description
    -

    Whether to allow messages to be subscribed to automatic updates.

    -
    Default
    -
    false
  • -
  • updateInterval

    -
    Description
    -

    How frequently to update the time in Discord.

    -
    Default
    -
    60000
  • -
  • setBotStatus

    -
    Description
    -

    Whether to update the bot's status with server information.

    -
    Default
    -
    true
-
- -
- DiscordSquadCreated -

DiscordSquadCreated

-

The SquadCreated plugin will log Squad Creation events to a Discord channel.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log Squad Creation events to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • color

    -
    Description
    -

    The color of the embed.

    -
    Default
    -
    16761867
  • -
  • useEmbed

    -
    Description
    -

    Send message as Embed

    -
    Default
    -
    true
-
- -
- DiscordSubsystemRestarter -

DiscordSubsystemRestarter

-

The DiscordSubSystemRestarter plugin allows you to manually restart SquadJS subsystems in case an issues arises with them.

  • !squadjs restartsubsystem rcon
  • !squadjs restartsubsystem logparser

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • role (Required)

    -
    Description
    -

    ID of role required to run the sub system restart commands.

    -
    Default
    -
  • Example
    -
    667741905228136459
-
+Placeholder. -
- DiscordTeamkill -

DiscordTeamkill

-

The DiscordTeamkill plugin logs teamkills and related information to a Discord channel for admins to review.

-

Options

-
  • discordClient (Required)

    -
    Description
    -

    Discord connector name.

    -
    Default
    -
    discord
  • -
  • channelID (Required)

    -
    Description
    -

    The ID of the channel to log teamkills to.

    -
    Default
    -
  • Example
    -
    667741905228136459
    -
  • color

    -
    Description
    -

    The color of the embeds.

    -
    Default
    -
    16761867
  • -
  • disableCBL

    -
    Description
    -

    Disable Community Ban List information.

    -
    Default
    -
    false
-
- -
- FogOfWar -

FogOfWar

-

The FogOfWar plugin can be used to automate setting fog of war mode.

-

Options

-
  • mode

    -
    Description
    -

    Fog of war mode to set.

    -
    Default
    -
    1
  • -
  • delay

    -
    Description
    -

    Delay before setting fog of war mode.

    -
    Default
    -
    10000
-
- -
- IntervalledBroadcasts -

IntervalledBroadcasts

-

The IntervalledBroadcasts plugin allows you to set broadcasts, which will be broadcasted at preset intervals

-

Options

-
  • broadcasts

    -
    Description
    -

    Messages to broadcast.

    -
    Default
    -
    []
  • Example
    -
    [
    -  "This server is powered by SquadJS."
    -]
    -
  • interval

    -
    Description
    -

    Frequency of the broadcasts in milliseconds.

    -
    Default
    -
    300000
-
- -
- SeedingMode -

SeedingMode

-

The SeedingMode plugin broadcasts seeding rule messages to players at regular intervals when the server is below a specified player count. It can also be configured to display "Live" messages when the server goes live.

-

Options

-
  • interval

    -
    Description
    -

    Frequency of seeding messages in milliseconds.

    -
    Default
    -
    150000
  • -
  • seedingThreshold

    -
    Description
    -

    Player count required for server not to be in seeding mode.

    -
    Default
    -
    50
  • -
  • seedingMessage

    -
    Description
    -

    Seeding message to display.

    -
    Default
    -
    Seeding Rules Active! Fight only over the middle flags! No FOB Hunting!
  • -
  • liveEnabled

    -
    Description
    -

    Enable "Live" messages for when the server goes live.

    -
    Default
    -
    true
  • -
  • liveThreshold

    -
    Description
    -

    Player count required for "Live" messages to not bee displayed.

    -
    Default
    -
    52
  • -
  • liveMessage

    -
    Description
    -

    "Live" message to display.

    -
    Default
    -
    Live!
  • -
  • waitOnNewGames

    -
    Description
    -

    Should the plugin wait to be executed on NEW_GAME event.

    -
    Default
    -
    true
  • -
  • waitTimeOnNewGame

    -
    Description
    -

    The time to wait before check player counts in seconds.

    -
    Default
    -
    30
-
- -
- SocketIOAPI -

SocketIOAPI

-

The SocketIOAPI plugin allows remote access to a SquadJS instance via Socket.IO
As a client example you can use this to connect to the socket.io server;


-      const socket = io.connect('ws://IP:PORT', {
-        auth: {
-          token: "MySecretPassword"
-        }
-      })
-    
If you need more documentation about socket.io please go ahead and read the following;
General Socket.io documentation: Socket.io Docs
Authentication and securing your websocket: Sending-credentials
How to use, install and configure a socketIO-client: Usage Guide with Examples

-

Options

-
  • websocketPort (Required)

    -
    Description
    -

    The port for the websocket.

    -
    Default
    -
  • Example
    -
    3000
    -
  • securityToken (Required)

    -
    Description
    -

    Your secret token/password for connecting.

    -
    Default
    -
  • Example
    -
    MySecretPassword
-
- -
- TeamRandomizer -

TeamRandomizer

-

The TeamRandomizer can be used to randomize teams. It's great for destroying clan stacks or for social events. It can be run by typing, by default, !randomize into in-game admin chat

-

Options

-
  • command

    -
    Description
    -

    The command used to randomize the teams.

    -
    Default
    -
    randomize
-
- -
+Interested in creating your own plugin? [See more here](./squad-server/plugins/readme.md) ## Statement on Accuracy Some information SquadJS collects from Squad servers was never intended or designed to be collected. As a result, it is impossible for any framework to collect the same information with 100% accuracy. SquadJS aims to get as close as possible to that figure, however, it acknowledges that this is not possible in some specific scenarios. diff --git a/config.json b/config.json index cee564284..cde0fc34d 100644 --- a/config.json +++ b/config.json @@ -18,235 +18,8 @@ "username": "SFTP Username", "password": "SFTP Password" }, - "adminLists": [ - { - "type": "", - "source": "" - } - ] + "adminLists": [] }, - "connectors": { - "discord": "Discord Login Token", - "awnAPI": { - "orgID": "YourOrgID", - "creds": { - "username": "AwnUsername", - "password": "AwnPassword" - } - }, - "mysql": { - "host": "host", - "port": 3306, - "username": "squadjs", - "password": "password", - "database": "squadjs", - "dialect": "mysql" - }, - "sqlite": "sqlite:database.sqlite" - }, - "plugins": [ - { - "plugin": "AutoKickUnassigned", - "enabled": true, - "warningMessage": "Join a squad, you are unassigned and will be kicked", - "kickMessage": "Unassigned - automatically removed", - "frequencyOfWarnings": 30, - "unassignedTimer": 360, - "playerThreshold": 93, - "roundStartDelay": 900, - "ignoreAdmins": false, - "ignoreWhitelist": false - }, - { - "plugin": "AutoTKWarn", - "enabled": true, - "attackerMessage": "Please apologise for ALL TKs in ALL chat!", - "victimMessage": null - }, - { - "plugin": "CBLInfo", - "enabled": true, - "discordClient": "discord", - "channelID": "", - "threshold": 6 - }, - { - "plugin": "ChatCommands", - "enabled": true, - "commands": [ - { - "command": "squadjs", - "type": "warn", - "response": "This server is powered by SquadJS.", - "ignoreChats": [] - } - ] - }, - { - "plugin": "DBLog", - "enabled": false, - "database": "mysql", - "overrideServerID": null - }, - { - "plugin": "DiscordAdminBroadcast", - "enabled": false, - "discordClient": "discord", - "channelID": "", - "color": 16761867 - }, - { - "plugin": "DiscordAdminCamLogs", - "enabled": false, - "discordClient": "discord", - "channelID": "", - "color": 16761867 - }, - { - "plugin": "DiscordAdminRequest", - "enabled": true, - "discordClient": "discord", - "channelID": "", - "ignoreChats": [], - "ignorePhrases": [], - "command": "admin", - "pingGroups": [], - "pingHere": false, - "pingDelay": 60000, - "color": 16761867, - "warnInGameAdmins": false, - "showInGameAdmins": true - }, - { - "plugin": "DiscordChat", - "enabled": true, - "discordClient": "discord", - "channelID": "", - "chatColors": {}, - "color": 16761867, - "ignoreChats": [ - "ChatSquad" - ] - }, - { - "plugin": "DiscordDebug", - "enabled": false, - "discordClient": "discord", - "channelID": "", - "events": [] - }, - { - "plugin": "DiscordFOBHABExplosionDamage", - "enabled": true, - "discordClient": "discord", - "channelID": "", - "color": 16761867 - }, - { - "plugin": "DiscordKillFeed", - "enabled": false, - "discordClient": "discord", - "channelID": "", - "color": 16761867, - "disableCBL": false - }, - { - "plugin": "DiscordPlaceholder", - "enabled": true, - "discordClient": "discord", - "command": "!placeholder", - "channelID": "" - }, - { - "plugin": "DiscordRcon", - "enabled": false, - "discordClient": "discord", - "channelID": "", - "permissions": {}, - "prependAdminNameInBroadcast": false - }, - { - "plugin": "DiscordRoundWinner", - "enabled": true, - "discordClient": "discord", - "channelID": "", - "color": 16761867 - }, - { - "plugin": "DiscordRoundEnded", - "enabled": true, - "discordClient": "discord", - "channelID": "", - "color": 16761867 - }, - { - "plugin": "DiscordServerStatus", - "enabled": true, - "discordClient": "discord", - "messageStore": "sqlite", - "command": "!status", - "disableSubscriptions": false, - "updateInterval": 60000, - "setBotStatus": true - }, - { - "plugin": "DiscordSquadCreated", - "enabled": false, - "discordClient": "discord", - "channelID": "", - "color": 16761867, - "useEmbed": true - }, - { - "plugin": "DiscordSubsystemRestarter", - "enabled": false, - "discordClient": "discord", - "role": "" - }, - { - "plugin": "DiscordTeamkill", - "enabled": true, - "discordClient": "discord", - "channelID": "", - "color": 16761867, - "disableCBL": false - }, - { - "plugin": "FogOfWar", - "enabled": false, - "mode": 1, - "delay": 10000 - }, - { - "plugin": "IntervalledBroadcasts", - "enabled": false, - "broadcasts": [], - "interval": 300000 - }, - { - "plugin": "SeedingMode", - "enabled": true, - "interval": 150000, - "seedingThreshold": 50, - "seedingMessage": "Seeding Rules Active! Fight only over the middle flags! No FOB Hunting!", - "liveEnabled": true, - "liveThreshold": 52, - "liveMessage": "Live!", - "waitOnNewGames": true, - "waitTimeOnNewGame": 30 - }, - { - "plugin": "SocketIOAPI", - "enabled": false, - "websocketPort": "", - "securityToken": "" - }, - { - "plugin": "TeamRandomizer", - "enabled": true, - "command": "randomize" - } - ], "logger": { "verboseness": { "SquadServer": 1, diff --git a/index.js b/index.ts similarity index 52% rename from index.js rename to index.ts index 14b378219..34457a4e2 100644 --- a/index.js +++ b/index.ts @@ -2,22 +2,25 @@ import SquadServerFactory from 'squad-server/factory'; import printLogo from 'squad-server/logo'; async function main() { + // Print the SquadJS logo. await printLogo(); - const config = process.env.config; - const configPath = process.argv[2]; + // Get the config from the environment variables (if applicable). + const config: string = process.env.config; + + // Get the config path from the arguments (if applicable. + const configPath: string = process.argv[2]; + + // Throw an error if a config is specified through both environmental variables and arguments. if (config && configPath) throw new Error('Cannot accept both a config and config path.'); - // create a SquadServer instance + // Create a SquadServer instance. const server = config ? await SquadServerFactory.buildFromConfigString(config) : await SquadServerFactory.buildFromConfigFile(configPath || './config.json'); - // watch the server + // Watch the server. await server.watch(); - - // now mount the plugins - await Promise.all(server.plugins.map(async (plugin) => await plugin.mount())); } main(); diff --git a/package.json b/package.json index ae90fe462..27150ff68 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,12 @@ "prepare": "husky install", "lint": "eslint --fix . && prettier --write \"./**/*.js\"", "lint-staged": "lint-staged", - "build-config": "node squad-server/scripts/build-config-file.js", - "build-readme": "node squad-server/scripts/build-readme.js", - "build-all": "node squad-server/scripts/build-config-file.js && node squad-server/scripts/build-readme.js" + "run": "tsx index.ts" }, "type": "module", "dependencies": { - "squad-server": "1.0.0" + "squad-server": "1.0.0", + "tsx": "^4.16.5" }, "devDependencies": { "eslint": "^7.17.0", diff --git a/squad-server/factory.js b/squad-server/factory.js index a8865a5a9..0a17b3e64 100644 --- a/squad-server/factory.js +++ b/squad-server/factory.js @@ -2,16 +2,9 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; -import Discord from 'discord.js'; -import sequelize from 'sequelize'; -import AwnAPI from './utils/awn-api.js'; - 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)); @@ -19,12 +12,6 @@ export default class SquadServerFactory { static async buildFromConfig(config) { Logger.setTimeStamps(config.logger.timestamps ? config.logger.timestamps : false); - const plugins = await Plugins.getPlugins(); - - for (const plugin of Object.keys(plugins)) { - Logger.setColor(plugin, 'magentaBright'); - } - // setup logging levels for (const [module, verboseness] of Object.entries(config.logger.verboseness)) { Logger.setVerboseness(module, verboseness); @@ -36,109 +23,7 @@ export default class SquadServerFactory { // create SquadServer Logger.verbose('SquadServerFactory', 1, 'Creating SquadServer...'); - const server = new SquadServer(config.server); - - // initialise connectors - Logger.verbose('SquadServerFactory', 1, 'Preparing connectors...'); - const connectors = {}; - for (const pluginConfig of config.plugins) { - if (!pluginConfig.enabled) continue; - - if (!plugins[pluginConfig.plugin]) - throw new Error(`Plugin ${pluginConfig.plugin} does not exist.`); - - const Plugin = plugins[pluginConfig.plugin]; - - for (const [optionName, option] of Object.entries(Plugin.optionsSpecification)) { - // 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; - - // create the connector - connectors[connectorName] = await SquadServerFactory.createConnector( - server, - option.connector, - connectorName, - config.connectors[connectorName] - ); - } - } - - // initialise plugins - Logger.verbose('SquadServerFactory', 1, 'Initialising plugins...'); - - for (const pluginConfig of config.plugins) { - if (!pluginConfig.enabled) continue; - - if (!plugins[pluginConfig.plugin]) - throw new Error(`Plugin ${pluginConfig.plugin} does not exist.`); - - const Plugin = plugins[pluginConfig.plugin]; - - Logger.verbose('SquadServerFactory', 1, `Initialising ${Plugin.name}...`); - - const plugin = new Plugin(server, pluginConfig, connectors); - - // allow the plugin to do any asynchronous work needed before it can be mounted - await plugin.prepareToMount(); - - server.plugins.push(plugin); - } - - return server; - } - - static async createConnector(server, type, connectorName, connectorConfig) { - Logger.verbose('SquadServerFactory', 1, `Starting ${type} connector ${connectorName}...`); - - if (type === 'discord') { - const connector = new Discord.Client(); - await connector.login(connectorConfig); - return connector; - } - - if (type === 'sequelize') { - let connector; - - if (typeof connectorConfig === 'string') { - connector = new Sequelize(connectorConfig, { - define: { - charset: 'utf8mb4', - collate: 'utf8mb4_unicode_ci' - }, - logging: (msg) => Logger.verbose('Sequelize', 3, msg) - }); - } else if (typeof connectorConfig === 'object') { - connector = new Sequelize({ - ...connectorConfig, - logging: (msg) => Logger.verbose('Sequelize', 3, msg) - }); - } else { - throw new Error('Unknown sequelize connector config type.'); - } - - await connector.authenticate(); - return connector; - } - - if (type === 'awnAPI') { - const awn = new AwnAPI(connectorConfig); - await awn.auth(connectorConfig); - return awn; - } - - throw new Error(`${type.connector} is an unsupported connector type.`); + return new SquadServer(config.server); } static parseConfig(configString) { @@ -154,104 +39,21 @@ export default class SquadServerFactory { return SquadServerFactory.buildFromConfig(SquadServerFactory.parseConfig(configString)); } - static readConfigFile(configPath = './config.json') { - configPath = path.resolve(__dirname, '../', configPath); - if (!fs.existsSync(configPath)) throw new Error('Config file does not exist.'); - return fs.readFileSync(configPath, 'utf8'); - } - static buildFromConfigFile(configPath) { Logger.verbose('SquadServerFactory', 1, 'Reading config file...'); - return SquadServerFactory.buildFromConfigString(SquadServerFactory.readConfigFile(configPath)); - } - - 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); - - const pluginKeys = Object.keys(plugins).sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0 - ); - - for (const pluginKey of pluginKeys) { - const Plugin = plugins[pluginKey]; - - const pluginConfig = { plugin: Plugin.name, enabled: Plugin.defaultEnabled }; - for (const [optionName, option] of Object.entries(Plugin.optionsSpecification)) { - pluginConfig[optionName] = option.default; - } - - template.plugins.push(pluginConfig); - } - - return template; - } - - static async buildConfigFile() { - const configPath = path.resolve(__dirname, '../config.json'); - const config = await SquadServerFactory.buildConfig(); - - const configString = JSON.stringify(config, null, 2); - fs.writeFileSync(configPath, configString); - } - - 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 - ); - const pluginInfo = []; - - for (const pluginName of pluginKeys) { - const Plugin = plugins[pluginName]; - - const options = []; - for (const [optionName, option] of Object.entries(Plugin.optionsSpecification)) { - let optionInfo = `
  • ${optionName}${option.required ? ' (Required)' : ''}

    -
    Description
    -

    ${option.description}

    -
    Default
    -
    ${
    -             typeof option.default === 'object'
    -               ? JSON.stringify(option.default, null, 2)
    -               : option.default
    -           }
  • `; - - if (option.example) - optionInfo += `
    Example
    -
    ${
    -             typeof option.example === 'object'
    -               ? JSON.stringify(option.example, null, 2)
    -               : option.example
    -           }
    `; - - options.push(optionInfo); - } + // Make the config path relevant to root directory. + configPath = path.resolve(__dirname, '../', configPath); - pluginInfo.push( - `
    - ${Plugin.name} -

    ${Plugin.name}

    -

    ${Plugin.description}

    -

    Options

    -
      ${options.join('\n')}
    -
    ` - ); + // Check the config file exists. + if (!fs.readFileSync(configPath)) { + throw new Error('Config file does not exist.'); } - const pluginInfoText = pluginInfo.join('\n\n'); - - const templatePath = path.resolve(__dirname, './templates/readme-template.md'); - const template = fs.readFileSync(templatePath, 'utf8'); - - const readmePath = path.resolve(__dirname, '../README.md'); - const readme = template.replace(/\/\/PLUGIN-INFO\/\//, pluginInfoText); + // Read the config file. + const configString = fs.readFileSync(configPath, 'utf-8'); - fs.writeFileSync(readmePath, readme); + // Build the SquadServer from the config string. + return SquadServerFactory.buildFromConfigString(configString); } } diff --git a/squad-server/index.js b/squad-server/index.js index 49f0ea937..8f93f5c7a 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -36,8 +36,6 @@ export default class SquadServer extends EventEmitter { this.admins = {}; this.adminsInAdminCam = {}; - this.plugins = []; - this.setupRCON(); this.setupLogParser(); diff --git a/squad-server/plugins/auto-kick-unassigned.js b/squad-server/plugins/auto-kick-unassigned.js deleted file mode 100644 index 23ecb7d9c..000000000 --- a/squad-server/plugins/auto-kick-unassigned.js +++ /dev/null @@ -1,245 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class AutoKickUnassigned extends BasePlugin { - static get description() { - return ( - 'The AutoKickUnassigned plugin will automatically kick players that are not in a squad after a ' + - 'specified ammount of time.' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - warningMessage: { - required: false, - description: 'Message SquadJS will send to players warning them they will be kicked', - default: 'Join a squad, you are unassigned and will be kicked' - }, - kickMessage: { - required: false, - description: 'Message to send to players when they are kicked', - default: 'Unassigned - automatically removed' - }, - frequencyOfWarnings: { - required: false, - description: - 'How often in Seconds should we warn the player about being unassigned?', - default: 30 - }, - unassignedTimer: { - required: false, - description: 'How long in Seconds to wait before a unassigned player is kicked', - default: 360 - }, - playerThreshold: { - required: false, - description: - 'Player count required for AutoKick to start kicking players, set to -1 to disable', - default: 93 - }, - roundStartDelay: { - required: false, - description: - 'Time delay in Seconds from start of the round before AutoKick starts kicking again', - default: 900 - }, - ignoreAdmins: { - required: false, - description: - '', - default: false - }, - ignoreWhitelist: { - required: false, - description: - '', - default: false - } - }; - } - - /** - * trackedPlayers[] = - * - * = { - * player: - * warnings: - * startTime: - * warnTimerID: - * kickTimerID: - * } - */ - constructor(server, options, connectors) { - super(server, options, connectors); - - this.adminPermission = 'canseeadminchat'; - this.whitelistPermission = 'reserve'; - - this.kickTimeout = options.unassignedTimer * 1000; - this.warningInterval = options.frequencyOfWarnings * 1000; - this.gracePeriod = options.roundStartDelay * 1000; - - this.trackingListUpdateFrequency = 1 * 60 * 1000; // 1min - this.cleanUpFrequency = 20 * 60 * 1000; // 20min - - this.betweenRounds = false; - - this.trackedPlayers = {}; - - this.onNewGame = this.onNewGame.bind(this); - this.onPlayerSquadChange = this.onPlayerSquadChange.bind(this); - this.updateTrackingList = this.updateTrackingList.bind(this); - this.clearDisconnectedPlayers = this.clearDisconnectedPlayers.bind(this); - } - - async mount() { - this.server.on('NEW_GAME', this.onNewGame); - this.server.on('PLAYER_SQUAD_CHANGE', this.onPlayerSquadChange); - this.updateTrackingListInterval = setInterval( - this.updateTrackingList, - this.trackingListUpdateFrequency - ); - this.clearDisconnectedPlayersInterval = setInterval( - this.clearDisconnectedPlayers, - this.cleanUpFrequency - ); - } - - async unmount() { - this.server.removeEventListener('NEW_GAME', this.onNewGame); - this.server.removeEventListener('PLAYER_SQUAD_CHANGE', this.onPlayerSquadChange); - clearInterval(this.updateTrackingListInterval); - clearInterval(this.clearDisconnectedPlayersInterval); - } - - async onNewGame() { - this.betweenRounds = true; - await this.updateTrackingList(); - setTimeout(() => { - this.betweenRounds = false; - }, this.gracePeriod); - } - - async onPlayerSquadChange(player) { - if (player.eosID in this.trackedPlayers && player.squadID !== null) - this.untrackPlayer(player.eosID); - } - - async updateTrackingList(forceUpdate = false) { - const run = !(this.betweenRounds || this.server.players.length < this.options.playerThreshold); - - this.verbose( - 3, - `Update Tracking List? ${run} (Between rounds: ${ - this.betweenRounds - }, Below player threshold: ${this.server.players.length < this.options.playerThreshold})` - ); - - if (!run) { - for (const eosID of Object.keys(this.trackedPlayers)) this.untrackPlayer(eosID); - return; - } - - if (forceUpdate) await this.server.updatePlayerList(); - - const admins = this.server.getAdminsWithPermission(this.adminPermission, 'eosID'); - const whitelist = this.server.getAdminsWithPermission(this.whitelistPermission, 'eosID'); - - // loop through players on server and start tracking players not in a squad - for (const player of this.server.players) { - const isTracked = player.eosID in this.trackedPlayers; - const isUnassigned = player.squadID === null; - const isAdmin = admins.includes(player.eosID); - const isWhitelist = whitelist.includes(player.eosID); - - // tracked player joined a squad remove them (redundant afer adding PLAYER_SQUAD_CHANGE, keeping for now) - if (!isUnassigned && isTracked) this.untrackPlayer(player.eosID); - - if (!isUnassigned) continue; - - if (isAdmin) this.verbose(2, `Admin is Unassigned: ${player.name}`); - if (isAdmin && this.options.ignoreAdmins) continue; - - if (isWhitelist) this.verbose(2, `Whitelist player is Unassigned: ${player.name}`); - if (isWhitelist && this.options.ignoreWhitelist) continue; - - // start tracking player - if (!isTracked) this.trackedPlayers[player.eosID] = this.trackPlayer({ player }); - } - } - - async clearDisconnectedPlayers() { - for (const eosID of Object.keys(this.trackedPlayers)) // TRACK - if (!(eosID in this.server.players.map((p) => p.eosID))) this.untrackPlayer(eosID); - } - - msFormat(ms) { - // take in generic # of ms and return formatted MM:SS - let min = Math.floor((ms / 1000 / 60) << 0); - let sec = Math.floor((ms / 1000) % 60); - min = ('' + min).padStart(2, '0'); - sec = ('' + sec).padStart(2, '0'); - return `${min}:${sec}`; - } - - trackPlayer(info) { - this.verbose(2, `Tracking: ${info.player.name}`); - - const tracker = { - player: info.player, - warnings: 0, - startTime: Date.now() - }; - - // continuously warn player at rate set in options - tracker.warnTimerID = setInterval(async () => { - const msLeft = this.kickTimeout - this.warningInterval * (tracker.warnings + 1); - - // clear on last warning - if (msLeft < this.warningInterval + 1) clearInterval(tracker.warnTimerID); - - const timeLeft = this.msFormat(msLeft); - this.server.rcon.warn(tracker.player.eosID, `${this.options.warningMessage} - ${timeLeft}`); - this.verbose(2, `Warning: ${tracker.player.name} (${timeLeft})`); - tracker.warnings++; - }, this.warningInterval); - - // set timeout to kick player - tracker.kickTimerID = setTimeout(async () => { - // ensures player is still Unassigned - await this.updateTrackingList(true); - - // return if player in tracker was removed from list - if (!(tracker.player.eosID in this.trackedPlayers)) return; - - this.server.rcon.kick(info.player.eosID, this.options.kickMessage); - this.server.emit('PLAYER_AUTO_KICKED', { - player: tracker.player, - warnings: tracker.warnings, - startTime: tracker.startTime - }); - this.verbose(1, `Kicked: ${tracker.player.name}`); - this.untrackPlayer(tracker.player.eosID); - }, this.kickTimeout); - - return tracker; - } - - untrackPlayer(eosID) { - const tracker = this.trackedPlayers[eosID]; - clearInterval(tracker.warnTimerID); - clearTimeout(tracker.kickTimerID); - delete this.trackedPlayers[eosID]; - this.verbose(2, `unTrack: ${tracker.player.name}`); - } -} diff --git a/squad-server/plugins/auto-tk-warn.js b/squad-server/plugins/auto-tk-warn.js deleted file mode 100644 index 0f0664b7a..000000000 --- a/squad-server/plugins/auto-tk-warn.js +++ /dev/null @@ -1,49 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class AutoTKWarn extends BasePlugin { - static get description() { - return 'The AutoTkWarn plugin will automatically warn players with a message when they teamkill.'; - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - attackerMessage: { - required: false, - description: 'The message to warn attacking players with.', - default: 'Please apologise for ALL TKs in ALL chat!' - }, - victimMessage: { - required: false, - description: 'The message that will be sent to the victim.', - default: null // 'You were killed by your own team.' - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - 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) { - if (info.attacker && this.options.attackerMessage) { - this.server.rcon.warn(info.attacker.eosID, this.options.attackerMessage); - } - if (info.victim && this.options.victimMessage) { - this.server.rcon.warn(info.victim.eosID, this.options.victimMessage); - } - } -} diff --git a/squad-server/plugins/base-plugin.js b/squad-server/plugins/base-plugin.js deleted file mode 100644 index 4a8e58c6f..000000000 --- a/squad-server/plugins/base-plugin.js +++ /dev/null @@ -1,51 +0,0 @@ -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] = - typeof this.rawOptions[optionName] !== 'undefined' - ? this.rawOptions[optionName] - : option.default; - } - } - } - - async prepareToMount() {} - - async mount() {} - - async unmount() {} - - static get description() { - throw new Error('Plugin missing "static get description()" method.'); - } - - static get defaultEnabled() { - throw new Error('Plugin missing "static get defaultEnabled()" method.'); - } - - static get optionsSpecification() { - throw new Error('Plugin missing "static get optionSpecification()" method.'); - } - - verbose(...args) { - Logger.verbose(this.constructor.name, ...args); - } -} diff --git a/squad-server/plugins/cbl-info.js b/squad-server/plugins/cbl-info.js deleted file mode 100644 index 1818ef951..000000000 --- a/squad-server/plugins/cbl-info.js +++ /dev/null @@ -1,163 +0,0 @@ -import GraphQLRequest from 'graphql-request'; - -import DiscordBasePlugin from './discord-base-plugin.js'; - -const { request, gql } = GraphQLRequest; - -export default class CBLInfo extends DiscordBasePlugin { - static get description() { - return ( - 'The CBLInfo plugin alerts admins when a harmful player is detected joining their server based ' + - 'on data from the Community Ban List.' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to alert admins through.', - default: '', - example: '667741905228136459' - }, - threshold: { - required: false, - description: - 'Admins will be alerted when a player has this or more reputation points. For more information on ' + - 'reputation points, see the ' + - 'Community Ban List\'s FAQ', - default: 6 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onPlayerConnected = this.onPlayerConnected.bind(this); - } - - async mount() { - this.server.on('PLAYER_CONNECTED', this.onPlayerConnected); - } - - async unmount() { - this.server.removeEventListener('PLAYER_CONNECTED', this.onPlayerConnected); - } - - async onPlayerConnected(info) { - try { - const data = await request( - 'https://communitybanlist.com/graphql', - gql` - query Search($id: String!) { - steamUser(id: $id) { - id - name - avatarFull - reputationPoints - riskRating - reputationRank - lastRefreshedInfo - lastRefreshedReputationPoints - lastRefreshedReputationRank - activeBans: bans(orderBy: "created", orderDirection: DESC, expired: false) { - edges { - cursor - node { - id - } - } - } - expiredBans: bans(orderBy: "created", orderDirection: DESC, expired: true) { - edges { - cursor - node { - id - } - } - } - } - } - `, - { id: info.player.steamID } - ); - - if (!data.steamUser) { - this.verbose( - 2, - `Player ${info.player.name} (Steam ID: ${info.player.steamID}) is not listed in the Community Ban List.` - ); - return; - } - - if (data.steamUser.reputationPoints < this.options.threshold) { - this.verbose( - 2, - `Player ${info.player.name} (Steam ID: ${info.player.steamID}) has a reputation below the threshold.` - ); - return; - } - - await this.sendDiscordMessage({ - embed: { - title: `${info.player.name} is a potentially harmful player!`, - author: { - name: 'Community Ban List', - url: 'https://communitybanlist.com/', - icon_url: 'https://communitybanlist.com/static/media/cbl-logo.caf6584e.png' - }, - thumbnail: { - url: data.steamUser.avatarFull - }, - description: `[${info.player.name}](https://communitybanlist.com/search/${info.player.steamID}) has ${data.steamUser.reputationPoints} reputation points on the Community Ban List and is therefore a potentially harmful player.`, - fields: [ - { - name: 'Reputation Points', - value: `${data.steamUser.reputationPoints} (${ - data.steamUser.reputationPointsMonthChange || 0 - } from this month)`, - inline: true - }, - { - name: 'Risk Rating', - value: `${data.steamUser.riskRating} / 10`, - inline: true - }, - { - name: 'Reputation Rank', - value: `#${data.steamUser.reputationRank}`, - inline: true - }, - { - name: 'Active Bans', - value: `${data.steamUser.activeBans.edges.length}`, - inline: true - }, - { - name: 'Expired Bans', - value: `${data.steamUser.expiredBans.edges.length}`, - inline: true - } - ], - color: '#ffc40b', - timestamp: info.time.toISOString(), - footer: { - text: 'Powered by SquadJS and the Community Ban List' - } - } - }); - } catch (err) { - this.verbose( - 1, - `Failed to fetch Community Ban List data for player ${info.name} (Steam ID: ${info.steamID}): `, - err - ); - } - } -} diff --git a/squad-server/plugins/chat-commands.js b/squad-server/plugins/chat-commands.js deleted file mode 100644 index bb94bb545..000000000 --- a/squad-server/plugins/chat-commands.js +++ /dev/null @@ -1,52 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class ChatCommands extends BasePlugin { - static get description() { - return ( - 'The ChatCommands plugin can be configured to make chat commands that broadcast or warn the ' + - 'caller with present messages.' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - commands: { - required: false, - description: - 'An array of objects containing the following properties: ' + - '
      ' + - '
    • command - The command that initiates the message.
    • ' + - '
    • type - Either warn or broadcast.
    • ' + - '
    • response - The message to respond with.
    • ' + - '
    • ignoreChats - A list of chats to ignore the commands in. Use this to limit it to admins.
    • ' + - '
    ', - default: [ - { - command: 'squadjs', - type: 'warn', - response: 'This server is powered by SquadJS.', - ignoreChats: [] - } - ] - } - }; - } - - async mount() { - for (const command of this.options.commands) { - this.server.on(`CHAT_COMMAND:${command.command.toLowerCase()}`, async (data) => { - if (command.ignoreChats.includes(data.chat)) return; - - if (command.type === 'broadcast') { - await this.server.rcon.broadcast(command.response); - } else if (command.type === 'warn') { - await this.server.rcon.warn(data.player.eosID, command.response); - } - }); - } - } -} diff --git a/squad-server/plugins/db-log.js b/squad-server/plugins/db-log.js deleted file mode 100644 index 2e6816320..000000000 --- a/squad-server/plugins/db-log.js +++ /dev/null @@ -1,760 +0,0 @@ -import Sequelize from 'sequelize'; - -import BasePlugin from './base-plugin.js'; - -const { DataTypes, QueryTypes } = Sequelize; - -export default class DBLog extends BasePlugin { - static get description() { - return ( - 'The mysql-log 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:\n' + - '
    • Grafana is a cool way of viewing server statistics stored in the database.
    • \n' + - '
    • Install Grafana.
    • \n' + - '
    • Add your database as a datasource named SquadJS.
    • \n' + - '
    • Import the SquadJS Dashboard 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('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('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( - 'SteamUser', - { - steamID: { - type: DataTypes.STRING, - primaryKey: true - }, - lastName: { - type: DataTypes.STRING - } - }, - { - charset: 'utf8mb4', - collate: 'utf8mb4_unicode_ci' - } - ); - - this.createModel( - 'Player', - { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - eosID: { - type: DataTypes.STRING, - unique: true - }, - steamID: { - type: DataTypes.STRING, - notNull: true, - unique: true - }, - lastName: { - type: DataTypes.STRING - }, - lastIP: { - type: DataTypes.STRING - } - }, - { - charset: 'utf8mb4', - collate: 'utf8mb4_unicode_ci', - indexes: [ - { - fields: ['eosID'] - }, - { - fields: ['steamID'] - } - ] - } - ); - - 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 - } - }, - { - charset: 'utf8mb4', - collate: 'utf8mb4_unicode_ci' - } - ); - - 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 - } - }, - { - charset: 'utf8mb4', - collate: 'utf8mb4_unicode_ci' - } - ); - - 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.STRING - }, - reviverTeamID: { - type: DataTypes.INTEGER - }, - reviverSquadID: { - type: DataTypes.INTEGER - } - }, - { - charset: 'utf8mb4', - collate: 'utf8mb4_unicode_ci' - } - ); - - 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.Player.hasMany(this.models.Wound, { - sourceKey: 'steamID', - foreignKey: { name: 'attacker' }, - onDelete: 'CASCADE' - }); - - this.models.Player.hasMany(this.models.Wound, { - sourceKey: 'steamID', - foreignKey: { name: 'victim' }, - onDelete: 'CASCADE' - }); - - this.models.Player.hasMany(this.models.Death, { - sourceKey: 'steamID', - foreignKey: { name: 'attacker' }, - onDelete: 'CASCADE' - }); - - this.models.Player.hasMany(this.models.Death, { - sourceKey: 'steamID', - foreignKey: { name: 'victim' }, - onDelete: 'CASCADE' - }); - - this.models.Player.hasMany(this.models.Revive, { - sourceKey: 'steamID', - foreignKey: { name: 'attacker' }, - onDelete: 'CASCADE' - }); - - this.models.Player.hasMany(this.models.Revive, { - sourceKey: 'steamID', - foreignKey: { name: 'victim' }, - onDelete: 'CASCADE' - }); - - this.models.Player.hasMany(this.models.Revive, { - sourceKey: 'steamID', - foreignKey: { name: 'reviver' }, - onDelete: 'CASCADE' - }); - - this.models.Match.hasMany(this.models.TickRate, { - foreignKey: { name: 'match' }, - onDelete: 'CASCADE' - }); - - this.models.Match.hasMany(this.models.PlayerCount, { - foreignKey: { name: 'match' }, - 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.onPlayerConnected = this.onPlayerConnected.bind(this); - this.onPlayerWounded = this.onPlayerWounded.bind(this); - this.onPlayerDied = this.onPlayerDied.bind(this); - this.onPlayerRevived = this.onPlayerRevived.bind(this); - this.migrateSteamUsersIntoPlayers = this.migrateSteamUsersIntoPlayers.bind(this); - this.dropAllForeignKeys = this.dropAllForeignKeys.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.Match.sync(); - await this.models.TickRate.sync(); - await this.models.PlayerCount.sync(); - await this.models.SteamUser.sync(); - await this.models.Player.sync(); - await this.models.Wound.sync(); - await this.models.Death.sync(); - await this.models.Revive.sync(); - } - - async mount() { - await this.migrateSteamUsersIntoPlayers(); - - 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_CONNECTED', this.onPlayerConnected); - 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_CONNECTED', this.onPlayerConnected); - 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, - match: this.match ? this.match.id : null, - time: info.time, - tickRate: info.tickRate - }); - } - - async onUpdatedA2SInformation(info) { - await this.models.PlayerCount.create({ - server: this.options.overrideServerID || this.server.id, - match: this.match ? this.match.id : null, - players: info.a2sPlayerCount, - publicQueue: info.publicQueue, - reserveQueue: info.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.name : null, - layer: info.layer ? info.layer.name : null, - startTime: info.time - }); - } - - async onPlayerWounded(info) { - if (info.attacker) - await this.models.Player.upsert( - { - eosID: info.attacker.eosID, - steamID: info.attacker.steamID, - lastName: info.attacker.name - }, - { - conflictFields: ['steamID'] - } - ); - if (info.victim) - await this.models.Player.upsert( - { - eosID: info.victim.eosID, - steamID: info.victim.steamID, - lastName: info.victim.name - }, - { - conflictFields: ['steamID'] - } - ); - - 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.Player.upsert( - { - eosID: info.attacker.eosID, - steamID: info.attacker.steamID, - lastName: info.attacker.name - }, - { - conflictFields: ['steamID'] - } - ); - if (info.victim) - await this.models.Player.upsert( - { - eosID: info.victim.eosID, - steamID: info.victim.steamID, - lastName: info.victim.name - }, - { - conflictFields: ['steamID'] - } - ); - - 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.Player.upsert( - { - eosID: info.attacker.eosID, - steamID: info.attacker.steamID, - lastName: info.attacker.name - }, - { - conflictFields: ['steamID'] - } - ); - if (info.victim) - await this.models.Player.upsert( - { - eosID: info.victim.eosID, - steamID: info.victim.steamID, - lastName: info.victim.name - }, - { - conflictFields: ['steamID'] - } - ); - if (info.reviver) - await this.models.Player.upsert( - { - eosID: info.reviver.eosID, - steamID: info.reviver.steamID, - lastName: info.reviver.name - }, - { - conflictFields: ['steamID'] - } - ); - - 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 - }); - } - - async onPlayerConnected(info) { - await this.models.Player.upsert( - { - eosID: info.eosID, - steamID: info.player.steamID, - lastName: info.player.name, - lastIP: info.ip - }, - { - conflictFields: ['steamID'] - } - ); - } - - async migrateSteamUsersIntoPlayers() { - try { - const steamUsersCount = await this.models.SteamUser.count(); - const playersCount = await this.models.Player.count(); - - if (steamUsersCount < playersCount) { - this.verbose( - 1, - `Skipping migration from SteamUsers to Players due to a previous successful migration.` - ); - return; - } - - await this.dropAllForeignKeys(); - - const steamUsers = (await this.models.SteamUser.findAll()).map((u) => u.dataValues); - await this.models.Player.bulkCreate(steamUsers); - - this.verbose(1, `Migration from SteamUsers to Players successful`); - } catch (error) { - this.verbose(1, `Error during Migration from SteamUsers to Players: ${error}`); - } - } - - async dropAllForeignKeys() { - this.verbose( - 1, - `Starting to drop constraints on DB: ${this.options.database.config.database} related to DBLog_SteamUsers deptecated table.` - ); - for (const modelName in this.models) { - const model = this.models[modelName]; - const tableName = model.tableName; - - try { - const result = await this.options.database.query( - `SELECT * FROM information_schema.key_column_usage WHERE referenced_table_name IS NOT NULL AND table_schema = '${this.options.database.config.database}' AND table_name = '${tableName}';`, - { type: QueryTypes.SELECT } - ); - - for (const r of result) { - if (r.REFERENCED_TABLE_NAME === 'DBLog_SteamUsers') { - this.verbose( - 1, - `Found constraint ${r.COLUMN_NAME} on table ${tableName}, referencing ${r.REFERENCED_COLUMN_NAME} on ${r.REFERENCED_TABLE_NAME}` - ); - - await this.options.database - .query(`ALTER TABLE ${tableName} DROP FOREIGN KEY ${r.CONSTRAINT_NAME}`, { - type: QueryTypes.RAW - }) - .then(() => { - this.verbose(1, `Dropped foreign key ${r.COLUMN_NAME} on table ${tableName}`); - }) - .catch((e) => { - this.verbose( - 1, - `Error dropping foreign key ${r.COLUMN_NAME} on table ${tableName}:`, - e - ); - }); - } - } - } catch (error) { - this.verbose(1, `Error dropping foreign keys for table ${tableName}:`, error); - } finally { - model.sync(); - } - } - await this.models.Player.sync(); - } -} diff --git a/squad-server/plugins/discord-admin-broadcast.js b/squad-server/plugins/discord-admin-broadcast.js deleted file mode 100644 index 050f58758..000000000 --- a/squad-server/plugins/discord-admin-broadcast.js +++ /dev/null @@ -1,61 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordAdminBroadcast extends DiscordBasePlugin { - static get description() { - return ( - 'The DiscordAdminBroadcast plugin will send a copy of admin broadcasts made in game to a Discord ' + - 'channel.' - ); - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log admin broadcasts to.', - default: '', - example: '667741905228136459' - }, - color: { - required: false, - description: 'The color of the embed.', - default: 16761867 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - 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() - } - }); - } -} diff --git a/squad-server/plugins/discord-admin-cam-logs.js b/squad-server/plugins/discord-admin-cam-logs.js deleted file mode 100644 index 4bba3e238..000000000 --- a/squad-server/plugins/discord-admin-cam-logs.js +++ /dev/null @@ -1,105 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordAdminCamLogs extends DiscordBasePlugin { - static get description() { - return 'The DiscordAdminCamLogs plugin will log in game admin camera usage to a Discord channel.'; - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log admin camera usage to.', - default: '', - example: '667741905228136459' - }, - color: { - required: false, - description: 'The color of the embed.', - default: 16761867 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.adminsInCam = {}; - - this.onEntry = this.onEntry.bind(this); - this.onExit = this.onExit.bind(this); - } - - async mount() { - this.server.on('POSSESSED_ADMIN_CAMERA', this.onEntry); - this.server.on('UNPOSSESSED_ADMIN_CAMERA', this.onExit); - } - - async unmount() { - this.server.removeEventListener('POSSESSED_ADMIN_CAMERA', this.onEntry); - this.server.removeEventListener('UNPOSSESSED_ADMIN_CAMERA', this.onExit); - } - - async onEntry(info) { - 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 - }, - { - name: "Admin's EosID", - value: info.player.eosID, - inline: true - } - ], - timestamp: info.time.toISOString() - } - }); - } - - async onExit(info) { - 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: "Admin's EosID", - value: info.player.eosID, - inline: true - }, - { - name: 'Time in Admin Camera', - value: `${Math.round(info.duration / 60000)} mins` - } - ], - timestamp: info.time.toISOString() - } - }); - } -} diff --git a/squad-server/plugins/discord-admin-request.js b/squad-server/plugins/discord-admin-request.js deleted file mode 100644 index 00a56ce6f..000000000 --- a/squad-server/plugins/discord-admin-request.js +++ /dev/null @@ -1,191 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordAdminRequest extends DiscordBasePlugin { - static get description() { - return ( - 'The DiscordAdminRequest plugin will ping admins in a Discord channel when a player requests ' + - 'an admin via the !admin command in in-game chat.' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log admin broadcasts to.', - default: '', - example: '667741905228136459' - }, - ignoreChats: { - required: false, - description: 'A list of chat names to ignore.', - default: [], - example: ['ChatSquad'] - }, - ignorePhrases: { - required: false, - description: 'A list of phrases to ignore.', - default: [], - example: ['switch'] - }, - command: { - required: false, - description: 'The command that calls an admin.', - default: 'admin' - }, - pingGroups: { - required: false, - description: 'A list of Discord role IDs to ping.', - default: [], - example: ['500455137626554379'] - }, - pingHere: { - required: false, - description: - 'Ping @here. Great if Admin Requests are posted to a Squad Admin ONLY channel, allows pinging only Online Admins.', - default: false - }, - pingDelay: { - required: false, - description: 'Cooldown for pings in milliseconds.', - default: 60 * 1000 - }, - color: { - required: false, - description: 'The color of the embed.', - default: 16761867 - }, - warnInGameAdmins: { - required: false, - description: - 'Should in-game admins be warned after a players uses the command and should we tell how much admins are active in-game right now.', - default: false - }, - showInGameAdmins: { - required: false, - description: 'Should players know how much in-game admins there are active/online?', - default: true - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.lastPing = Date.now() - this.options.pingDelay; - - this.onChatCommand = this.onChatCommand.bind(this); - } - - async mount() { - this.server.on(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand); - } - - async unmount() { - this.server.removeEventListener(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand); - } - - async onChatCommand(info) { - if (this.options.ignoreChats.includes(info.chat)) return; - - 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.eosID, - `Please specify what you would like help with when requesting an admin.` - ); - return; - } - - const admins = this.server.getAdminsWithPermission('canseeadminchat', 'eosID'); - let amountAdmins = 0; - for (const player of this.server.players) { - if (!admins.includes(player.eosID)) continue; - amountAdmins++; - if (this.options.warnInGameAdmins) - await this.server.rcon.warn(player.eosID, `[${info.player.name}] - ${info.message}`); - } - - 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: "Player's EosID", - value: info.player.eosID, - inline: true - }, - { - name: 'Team & Squad', - value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}` - }, - { - name: 'Message', - value: info.message - }, - { - name: 'Admins Online', - value: amountAdmins - } - ], - timestamp: info.time.toISOString() - } - }; - - if (this.options.pingGroups.length > 0 && Date.now() - this.options.pingDelay > this.lastPing) { - if (this.options.pingHere === true && this.options.pingGroups.length === 0) { - message.content = `@here - Admin Requested in ${this.server.serverName}`; - } else if (this.options.pingHere === true && this.options.pingGroups.length > 0) { - message.content = `@here - Admin Requested in ${ - this.server.serverName - } - ${this.options.pingGroups.map((groupID) => `<@&${groupID}>`).join(' ')}`; - } else if (this.options.pingHere === false && this.options.pingGroups.length === 0) { - message.content = `Admin Requested in ${this.server.serverName}`; - } else if (this.options.pingHere === false && this.options.pingGroups.length > 0) { - message.content = `Admin Requested in ${this.server.serverName} - ${this.options.pingGroups - .map((groupID) => `<@&${groupID}>`) - .join(' ')}`; - } - this.lastPing = Date.now(); - } - - await this.sendDiscordMessage(message); - - if (amountAdmins === 0 && this.options.showInGameAdmins) - await this.server.rcon.warn( - info.player.eosID, - `There are no in-game admins, however, an admin has been notified via Discord. Please wait for us to get back to you.` - ); - else if (this.options.showInGameAdmins) - await this.server.rcon.warn( - info.player.eosID, - `There ${amountAdmins > 1 ? 'are' : 'is'} ${amountAdmins} in-game admin${ - amountAdmins > 1 ? 's' : '' - }. Please wait for us to get back to you.` - ); - else - await this.server.rcon.warn( - info.player.eosID, - `An admin has been notified. Please wait for us to get back to you.` - ); - } -} diff --git a/squad-server/plugins/discord-base-message-updater.js b/squad-server/plugins/discord-base-message-updater.js deleted file mode 100644 index 66376a62d..000000000 --- a/squad-server/plugins/discord-base-message-updater.js +++ /dev/null @@ -1,188 +0,0 @@ -import Sequelize from 'sequelize'; - -import BasePlugin from './base-plugin.js'; - -const { DataTypes } = Sequelize; - -export default class DiscordBaseMessageUpdater extends BasePlugin { - static get optionsSpecification() { - return { - discordClient: { - required: true, - description: 'Discord connector name.', - connector: 'discord', - default: 'discord' - }, - messageStore: { - required: true, - description: 'Sequelize connector name.', - connector: 'sequelize', - default: 'sqlite' - }, - command: { - required: true, - description: 'Command name to get message.', - default: '', - example: '!command' - }, - disableSubscriptions: { - required: false, - description: 'Whether to allow messages to be subscribed to automatic updates.', - default: false - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - // Setup model to store subscribed messages. - this.SubscribedMessage = this.options.messageStore.define( - `${this.constructor.name}_SubscribedMessage`, - { channelID: DataTypes.STRING, messageID: DataTypes.STRING, server: DataTypes.INTEGER }, - { timestamps: false } - ); - - this.onDiscordMessage = this.onDiscordMessage.bind(this); - } - - async prepareToMount() { - await this.SubscribedMessage.sync(); - } - - async mount() { - this.options.discordClient.on('message', this.onDiscordMessage); - } - - async unmount() { - this.options.discordClient.removeEventListener('message', this.onDiscordMessage); - } - - async generateMessage() { - throw new Error('generateMessage method must be defined.'); - } - - async onDiscordMessage(message) { - // Parse the incoming message. - const commandMatch = message.content.match( - new RegExp(`^${this.options.command}(?: (subscribe)| (unsubscribe) ([0-9]+) ([0-9]+))?$`, 'i') - ); - - // Stop processing the message if it does not match the command. - if (!commandMatch) return; - - // Split message parts. - const [subscribe, unsubscribe, channelID, messageID] = commandMatch.slice(1); - - // Handle non subscription messages. - if (subscribe === undefined && unsubscribe === undefined) { - this.verbose(1, 'Generating message content...'); - const generatedMessage = await this.generateMessage(); - - this.verbose(1, 'Sending non-subscription message...'); - await message.channel.send(generatedMessage); - this.verbose(1, 'Sent non-subscription message.'); - - return; - } - - // Handle subscription message. - if (subscribe !== undefined) { - if (this.options.disableSubscriptions) { - await message.reply('automated updates is disabled.'); - return; - } - - this.verbose(1, 'Generating message content...'); - const generatedMessage = await this.generateMessage(); - - this.verbose(1, 'Sending subscription message...'); - const newMessage = await message.channel.send(generatedMessage); - this.verbose(1, 'Sent subscription message.'); - - // Subscribe the message for automated updates. - const newChannelID = newMessage.channel.id; - const newMessageID = newMessage.id; - - this.verbose( - 1, - `Subscribing message (Channel ID: ${newChannelID}, Message ID: ${newMessageID}) to automated updates...` - ); - await this.SubscribedMessage.create({ - channelID: newChannelID, - messageID: newMessageID, - server: this.server.id - }); - this.verbose( - 1, - `Subscribed message (Channel ID: ${newChannelID}, Message ID: ${newMessageID}) to automated updates.` - ); - - return; - } - - // Handle unsubscription messages. - if (unsubscribe !== undefined) { - this.verbose( - 1, - `Unsubscribing message (Channel ID: ${channelID}, Message ID: ${messageID}) from automated updates...` - ); - await this.SubscribedMessage.destroy({ - where: { - channelID: channelID, - messageID: messageID, - server: this.server.id - } - }); - this.verbose( - 1, - `Unsubscribed message (Channel ID: ${channelID}, Message ID: ${messageID}) from automated updates.` - ); - - this.verbose(1, 'Sending acknowledgement message...'); - await message.reply('unsubscribed message from automated updates.'); - this.verbose(1, 'Sent acknowledgement message.'); - } - } - - async updateMessages() { - this.verbose(1, 'Generating message content for update...'); - // Generate the new message. - const generatedMessage = await this.generateMessage(); - - // Get subscribed messages. - const subscribedMessages = await this.SubscribedMessage.findAll({ - where: { server: this.server.id } - }); - - // Update each message. - this.verbose(1, `Updating ${subscribedMessages.length} messages...`); - for (const subscribedMessage of subscribedMessages) { - const { channelID, messageID } = subscribedMessage; - - try { - this.verbose(1, `Getting message (Channel ID: ${channelID}, Message ID: ${messageID})...`); - const channel = await this.options.discordClient.channels.fetch(channelID); - const message = await channel.messages.fetch(messageID); - - this.verbose(1, `Updating message (Channel ID: ${channelID}, Message ID: ${messageID})...`); - await message.edit(generatedMessage); - this.verbose(1, `Updated message (Channel ID: ${channelID}, Message ID: ${messageID}).`); - } catch (err) { - if (err.code === 10008) { - this.verbose( - 1, - `Message (Channel ID: ${channelID}, Message ID: ${messageID}) was deleted. Removing from automated updates...` - ); - await subscribedMessage.destroy(); - } else { - this.verbose( - 1, - `Message (Channel ID: ${channelID}, Message ID: ${messageID}) could not be updated: `, - err - ); - } - } - } - } -} diff --git a/squad-server/plugins/discord-base-plugin.js b/squad-server/plugins/discord-base-plugin.js deleted file mode 100644 index 26557ed9e..000000000 --- a/squad-server/plugins/discord-base-plugin.js +++ /dev/null @@ -1,41 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -import { COPYRIGHT_MESSAGE } from '../utils/constants.js'; - -export default class DiscordBasePlugin extends BasePlugin { - static get optionsSpecification() { - return { - discordClient: { - required: true, - description: 'Discord connector name.', - connector: 'discord', - default: 'discord' - } - }; - } - - async prepareToMount() { - try { - this.channel = await this.options.discordClient.channels.fetch(this.options.channelID); - } catch (error) { - this.channel = null; - this.verbose( - 1, - `Could not fetch Discord channel with channelID "${this.options.channelID}". Error: ${error.message}` - ); - this.verbose(2, `${error.stack}`); - } - } - - async sendDiscordMessage(message) { - if (!this.channel) { - this.verbose(1, `Could not send Discord Message. Channel not initialized.`); - return; - } - - if (typeof message === 'object' && 'embed' in message) - message.embed.footer = message.embed.footer || { text: COPYRIGHT_MESSAGE }; - - await this.channel.send(message); - } -} diff --git a/squad-server/plugins/discord-chat.js b/squad-server/plugins/discord-chat.js deleted file mode 100644 index fdaa1e300..000000000 --- a/squad-server/plugins/discord-chat.js +++ /dev/null @@ -1,90 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordChat extends DiscordBasePlugin { - static get description() { - return 'The DiscordChat plugin will log in-game chat to a Discord channel.'; - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log admin broadcasts to.', - default: '', - example: '667741905228136459' - }, - chatColors: { - required: false, - description: 'The color of the embed for each chat.', - default: {}, - example: { ChatAll: 16761867 } - }, - color: { - required: false, - description: 'The color of the embed.', - default: 16761867 - }, - ignoreChats: { - required: false, - default: ['ChatSquad'], - description: 'A list of chat names to ignore.' - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onChatMessage = this.onChatMessage.bind(this); - } - - 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: 'EosID', - value: info.player.eosID, - inline: true - }, - { - name: 'Team & Squad', - value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}` - }, - { - name: 'Message', - value: `${info.message}` - } - ], - timestamp: info.time.toISOString() - } - }); - } -} diff --git a/squad-server/plugins/discord-debug.js b/squad-server/plugins/discord-debug.js deleted file mode 100644 index edea4a32d..000000000 --- a/squad-server/plugins/discord-debug.js +++ /dev/null @@ -1,40 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordDebug extends DiscordBasePlugin { - static get description() { - return ( - 'The DiscordDebug plugin can be used to help debug SquadJS by dumping SquadJS events to a ' + - 'Discord channel.' - ); - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log events to.', - default: '', - example: '667741905228136459' - }, - events: { - required: true, - description: 'A list of events to dump.', - default: [], - example: ['PLAYER_DIED'] - } - }; - } - - async mount() { - for (const event of this.options.events) { - this.server.on(event, async (info) => { - await this.sendDiscordMessage(`\`\`\`${JSON.stringify({ ...info, event }, null, 2)}\`\`\``); - }); - } - } -} diff --git a/squad-server/plugins/discord-fob-hab-explosion-damage.js b/squad-server/plugins/discord-fob-hab-explosion-damage.js deleted file mode 100644 index d7619c22a..000000000 --- a/squad-server/plugins/discord-fob-hab-explosion-damage.js +++ /dev/null @@ -1,86 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordFOBHABExplosionDamage extends DiscordBasePlugin { - static get description() { - return ( - 'The DiscordFOBHABExplosionDamage plugin logs damage done to FOBs and HABs by ' + - 'explosions to help identify engineers blowing up friendly FOBs and HABs.' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log FOB/HAB explosion damage to.', - default: '', - example: '667741905228136459' - }, - color: { - required: false, - description: 'The color of the embeds.', - default: 16761867 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onDeployableDamaged = this.onDeployableDamaged.bind(this); - } - - async mount() { - this.server.on('DEPLOYABLE_DAMAGED', this.onDeployableDamaged); - } - - async unmount() { - this.server.removeEventListener('DEPLOYABLE_DAMAGED', this.onDeployableDamaged); - } - - async onDeployableDamaged(info) { - if (!info.deployable.match(/(?:FOBRadio|Hab)_/i)) return; - if (!info.weapon.match(/_Deployable_/i)) return; - if (!info.player) return; - - const fields = [ - { - name: "Player's Name", - value: info.player.name, - inline: true - }, - { - name: "Player's SteamID", - value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`, - inline: true - }, - { - name: "Player's EosID", - value: info.player.eosID, - inline: true - }, - { - name: 'Deployable', - value: info.deployable - }, - { - name: 'Weapon', - value: info.weapon - } - ]; - - await this.sendDiscordMessage({ - embed: { - title: `FOB/HAB Explosion Damage: ${info.player.name}`, - color: this.options.color, - fields: fields, - timestamp: info.time.toISOString() - } - }); - } -} diff --git a/squad-server/plugins/discord-killfeed.js b/squad-server/plugins/discord-killfeed.js deleted file mode 100644 index 1b68f2a8c..000000000 --- a/squad-server/plugins/discord-killfeed.js +++ /dev/null @@ -1,108 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordKillFeed extends DiscordBasePlugin { - static get description() { - return ( - 'The DiscordKillFeed plugin logs all wounds and related information to a Discord channel for ' + - 'admins to review.' - ); - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log teamkills to.', - default: '', - example: '667741905228136459' - }, - color: { - required: false, - description: 'The color of the embeds.', - default: 16761867 - }, - disableCBL: { - required: false, - description: 'Disable Community Ban List information.', - default: false - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onWound = this.onWound.bind(this); - } - - async mount() { - this.server.on('PLAYER_WOUNDED', this.onWound); - } - - async unmount() { - this.server.removeEventListener('PLAYER_WOUNDED', this.onWound); - } - - async onWound(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: "Attacker's EosID", - value: info.attacker.eosID, - inline: true - }, - { - name: 'Weapon', - value: info.weapon - }, - { - name: "Victim's Name", - value: info.victim ? info.victim.name : 'Unknown', - inline: true - }, - { - name: "Victim's SteamID", - value: info.victim - ? `[${info.victim.steamID}](https://steamcommunity.com/profiles/${info.victim.steamID})` - : 'Unknown', - inline: true - }, - { - name: "Victim's EosID", - value: info.victim ? info.victim.eosID : 'Unknown', - inline: true - } - ]; - - if (!this.options.disableCBL) - fields.push({ - name: 'Community Ban List', - value: `[Attacker's Bans](https://communitybanlist.com/search/${info.attacker.steamID})` - }); - - await this.sendDiscordMessage({ - embed: { - title: `KillFeed: ${info.attacker.name}`, - color: this.options.color, - fields: fields, - timestamp: info.time.toISOString() - } - }); - } -} diff --git a/squad-server/plugins/discord-placeholder.js b/squad-server/plugins/discord-placeholder.js deleted file mode 100644 index 495f7c531..000000000 --- a/squad-server/plugins/discord-placeholder.js +++ /dev/null @@ -1,59 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class DiscordPlaceholder extends BasePlugin { - static get description() { - return ( - 'The DiscordPlaceholder plugin allows you to make your bot create placeholder messages that ' + - 'can be used when configuring 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 to create Discord placeholder.', - default: '!placeholder' - }, - channelID: { - required: true, - description: 'The bot will only answer with a placeholder on this channel', - default: '' - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - - this.onMessage = this.onMessage.bind(this); - } - - async mount() { - this.options.discordClient.on('message', this.onMessage); - } - - async unmount() { - this.options.discordClient.removeEventListener('message', this.onMessage); - } - - async onMessage(message) { - if (message.author.bot) return; - if (message.channel.id !== this.options.channelID) return; - const prefixRegex = new RegExp(`^(${this.escapeRegex(this.options.command)})\\s*`); - if (!prefixRegex.test(message.content)) return; - await message.channel.send('Placeholder.'); - } -} diff --git a/squad-server/plugins/discord-rcon.js b/squad-server/plugins/discord-rcon.js deleted file mode 100644 index 5f42e68e3..000000000 --- a/squad-server/plugins/discord-rcon.js +++ /dev/null @@ -1,119 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class DiscordRcon extends BasePlugin { - static get description() { - return ( - 'The DiscordRcon plugin allows a specified Discord channel to be used as a RCON console to ' + - 'run RCON commands.' - ); - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - discordClient: { - required: true, - description: 'Discord connector name.', - connector: 'discord', - default: 'discord' - }, - channelID: { - required: true, - description: 'ID of channel to turn into RCON console.', - default: '', - example: '667741905228136459' - }, - permissions: { - required: false, - description: - '', - default: {}, - example: { - '123456789123456789': ['AdminBroadcast', 'AdminForceTeamChange', 'AdminDemoteCommander'] - } - }, - prependAdminNameInBroadcast: { - required: false, - description: 'Prepend admin names when making announcements.', - default: false - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onMessage = this.onMessage.bind(this); - } - - async mount() { - this.options.discordClient.on('message', this.onMessage); - } - - async unmount() { - this.options.discordClient.removeEventListener('message', this.onMessage); - } - - 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 command = message.content; - - // write admin's name into broadcast command if prependAdminNameInBroadcast is enabled - if (this.options.prependAdminNameInBroadcast) - command = command.replace( - /^AdminBroadcast /i, - `AdminBroadcast ${message.member.displayName}: ` - ); - - // 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; - } - - 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) { - for (const splitResponse of this.splitLongResponse(response)) - await message.channel.send(`\`\`\`${splitResponse}\`\`\``); - } - - splitLongResponse(response) { - const responseMessages = ['']; - - for (const line of response.split('\n')) { - if (responseMessages[responseMessages.length - 1].length + line.length > 1994) { - responseMessages.push(line); - } else { - responseMessages[responseMessages.length - 1] = `${ - responseMessages[responseMessages.length - 1] - }\n${line}`; - } - } - - return responseMessages; - } -} diff --git a/squad-server/plugins/discord-round-winner.js b/squad-server/plugins/discord-round-winner.js deleted file mode 100644 index 151534064..000000000 --- a/squad-server/plugins/discord-round-winner.js +++ /dev/null @@ -1,58 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordRoundWinner extends DiscordBasePlugin { - static get description() { - return 'The DiscordRoundWinner plugin will send the round winner to a Discord channel.'; - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log admin broadcasts to.', - default: '', - example: '667741905228136459' - }, - color: { - required: false, - description: 'The color of the embed.', - default: 16761867 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - 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 ${this.server.layerHistory[1].layer.name}.` - } - ], - timestamp: info.time.toISOString() - } - }); - } -} diff --git a/squad-server/plugins/discord-roundended.js b/squad-server/plugins/discord-roundended.js deleted file mode 100644 index f9757b167..000000000 --- a/squad-server/plugins/discord-roundended.js +++ /dev/null @@ -1,79 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordRoundEnded extends DiscordBasePlugin { - static get description() { - return 'The DiscordRoundEnded plugin will send the round winner to a Discord channel.'; - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log round end events to.', - default: '', - example: '667741905228136459' - }, - color: { - required: false, - description: 'The color of the embed.', - default: 16761867 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onRoundEnd = this.onRoundEnd.bind(this); - } - - async mount() { - this.server.on('ROUND_ENDED', this.onRoundEnd); - } - - async unmount() { - this.server.removeEventListener('ROUND_ENDED', this.onRoundEnd); - } - - async onRoundEnd(info) { - if (!info.winner || !info.loser) { - await this.sendDiscordMessage({ - embed: { - title: 'Round Ended', - description: 'This match Ended in a Draw', - color: this.options.color, - timestamp: info.time.toISOString() - } - }); - return; - } - - await this.sendDiscordMessage({ - embed: { - title: 'Round Ended', - description: `${info.winner.layer} - ${info.winner.level}`, - color: this.options.color, - fields: [ - { - name: `Team ${info.winner.team} Won`, - value: `${info.winner.subfaction}\n ${info.winner.faction}\n won with ${info.winner.tickets} tickets.` - }, - { - name: `Team ${info.loser.team} Lost`, - value: `${info.loser.subfaction}\n ${info.loser.faction}\n lost with ${info.loser.tickets} tickets.` - }, - { - name: 'Ticket Difference', - value: `${info.winner.tickets - info.loser.tickets}.` - } - ], - timestamp: info.time.toISOString() - } - }); - } -} diff --git a/squad-server/plugins/discord-server-status.js b/squad-server/plugins/discord-server-status.js deleted file mode 100644 index a3c9d4a6b..000000000 --- a/squad-server/plugins/discord-server-status.js +++ /dev/null @@ -1,133 +0,0 @@ -import Discord from 'discord.js'; -import tinygradient from 'tinygradient'; - -import { COPYRIGHT_MESSAGE } from '../utils/constants.js'; - -import DiscordBaseMessageUpdater from './discord-base-message-updater.js'; - -export default class DiscordServerStatus extends DiscordBaseMessageUpdater { - static get description() { - return 'The DiscordServerStatus plugin can be used to get the server status in Discord.'; - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - ...DiscordBaseMessageUpdater.optionsSpecification, - command: { - required: false, - description: 'Command name to get message.', - default: '!status' - }, - updateInterval: { - required: false, - description: 'How frequently to update the time in Discord.', - default: 60 * 1000 - }, - setBotStatus: { - required: false, - description: "Whether to update the bot's status with server information.", - default: true - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.updateMessages = this.updateMessages.bind(this); - this.updateStatus = this.updateStatus.bind(this); - } - - async mount() { - await super.mount(); - this.updateInterval = setInterval(this.updateMessages, this.options.updateInterval); - this.updateStatusInterval = setInterval(this.updateStatus, this.options.updateInterval); - } - - async unmount() { - await super.unmount(); - clearInterval(this.updateInterval); - clearInterval(this.updateStatusInterval); - } - - async generateMessage() { - const embed = new Discord.MessageEmbed(); - - // Set embed title. - embed.setTitle(this.server.serverName); - - // Set player embed field. - let players = ''; - - players += `${this.server.a2sPlayerCount}`; - if (this.server.publicQueue + this.server.reserveQueue > 0) - players += ` (+${this.server.publicQueue + this.server.reserveQueue})`; - - players += ` / ${this.server.publicSlots}`; - if (this.server.reserveSlots > 0) players += ` (+${this.server.reserveSlots})`; - - embed.addField('Players', players); - - // Set layer embed fields. - embed.addField( - 'Current Layer', - `\`\`\`${this.server.currentLayer?.name || 'Unknown'}\`\`\``, - true - ); - embed.addField( - 'Next Layer', - `\`\`\`${ - this.server.nextLayer?.name || (this.server.nextLayerToBeVoted ? 'To be voted' : 'Unknown') - }\`\`\``, - true - ); - - // Set layer image. - embed.setImage( - this.server.currentLayer - ? `https://squad-data.nyc3.cdn.digitaloceanspaces.com/main/${this.server.currentLayer.layerid}.jpg` - : undefined - ); - - // Set timestamp. - embed.setTimestamp(new Date()); - - // Set footer. - embed.setFooter(COPYRIGHT_MESSAGE); - - // Clamp the ratio between 0 and 1 to avoid tinygradient errors. - const ratio = this.server.a2sPlayerCount / (this.server.publicSlots + this.server.reserveSlots); - const clampedRatio = Math.min(1, Math.max(0, ratio)); - - // Set gradient embed color. - embed.setColor( - parseInt( - tinygradient([ - { color: '#ff0000', pos: 0 }, - { color: '#ffff00', pos: 0.5 }, - { color: '#00ff00', pos: 1 } - ]) - .rgbAt(clampedRatio) - .toHex(), - 16 - ) - ); - - return embed; - } - - async updateStatus() { - if (!this.options.setBotStatus) return; - - await this.options.discordClient.user.setActivity( - `(${this.server.a2sPlayerCount}/${this.server.publicSlots}) ${ - this.server.currentLayer?.name || 'Unknown' - }`, - { type: 'WATCHING' } - ); - } -} diff --git a/squad-server/plugins/discord-squad-created.js b/squad-server/plugins/discord-squad-created.js deleted file mode 100644 index 1fe83830d..000000000 --- a/squad-server/plugins/discord-squad-created.js +++ /dev/null @@ -1,79 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordSquadCreated extends DiscordBasePlugin { - static get description() { - return 'The SquadCreated plugin will log Squad Creation events to a Discord channel.'; - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log Squad Creation events to.', - default: '', - example: '667741905228136459' - }, - color: { - required: false, - description: 'The color of the embed.', - default: 16761867 - }, - useEmbed: { - required: false, - description: `Send message as Embed`, - default: true - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onSquadCreated = this.onSquadCreated.bind(this); - } - - async mount() { - this.server.on('SQUAD_CREATED', this.onSquadCreated); - } - - async unmount() { - this.server.removeEventListener('SQUAD_CREATED', this.onSquadCreated); - } - - async onSquadCreated(info) { - if (this.options.useEmbed) { - await this.sendDiscordMessage({ - embed: { - title: `Squad Created`, - color: this.options.color, - fields: [ - { - name: 'Player', - value: info.player.name, - inline: true - }, - { - name: 'Team', - value: info.teamName, - inline: true - }, - { - name: 'Squad Number & Squad Name', - value: `${info.player.squadID} : ${info.squadName}` - } - ], - timestamp: info.time.toISOString() - } - }); - } else { - await this.sendDiscordMessage( - ` \`\`\`Player: ${info.player.name}\n created Squad ${info.player.squadID} : ${info.squadName}\n on ${info.teamName}\`\`\` ` - ); - } - } -} diff --git a/squad-server/plugins/discord-subsystem-restarter.js b/squad-server/plugins/discord-subsystem-restarter.js deleted file mode 100644 index e718e7575..000000000 --- a/squad-server/plugins/discord-subsystem-restarter.js +++ /dev/null @@ -1,74 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class DiscordSubsystemRestarter extends BasePlugin { - static get description() { - return ( - 'The DiscordSubSystemRestarter plugin allows you to manually restart SquadJS subsystems in case ' + - 'an issues arises with them.' + - '
      ' + - '
    • !squadjs restartsubsystem rcon
    • ' + - '
    • !squadjs restartsubsystem logparser
    • ' + - '
    ' - ); - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - discordClient: { - required: true, - description: 'Discord connector name.', - connector: 'discord', - default: 'discord' - }, - role: { - required: true, - description: 'ID of role required to run the sub system restart commands.', - default: '', - example: '667741905228136459' - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onMessage = this.onMessage.bind(this); - } - - async mount() { - this.options.discordClient.on('message', this.onMessage); - } - - 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.'); - return; - } - - await this.server.restartRCON(); - message.reply('restarted the SquadJS RCON subsystem.'); - } - - if (message.content.match(/!squadjs restartsubsystem logparser/i)) { - if (!message.member._roles.includes(this.options.role)) { - message.reply('you do not have permission to do that.'); - return; - } - - await this.server.restartLogParser(); - message.reply('restarted the SquadJS LogParser subsystem.'); - } - } -} diff --git a/squad-server/plugins/discord-teamkill.js b/squad-server/plugins/discord-teamkill.js deleted file mode 100644 index 3f3d11aa6..000000000 --- a/squad-server/plugins/discord-teamkill.js +++ /dev/null @@ -1,106 +0,0 @@ -import DiscordBasePlugin from './discord-base-plugin.js'; - -export default class DiscordTeamkill extends DiscordBasePlugin { - static get description() { - return ( - 'The DiscordTeamkill plugin logs teamkills and related information to a Discord channel for ' + - 'admins to review.' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - ...DiscordBasePlugin.optionsSpecification, - channelID: { - required: true, - description: 'The ID of the channel to log teamkills to.', - default: '', - example: '667741905228136459' - }, - color: { - required: false, - description: 'The color of the embeds.', - default: 16761867 - }, - disableCBL: { - required: false, - description: 'Disable Community Ban List information.', - default: false - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - 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) { - 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: "Attacker's EosID", - value: info.attacker.eosID, - 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 - }, - { - name: "Victim's EosID", - value: info.victim.eosID, - inline: true - } - ]; - - if (!this.options.disableCBL) - fields.push({ - name: 'Community Ban List', - value: `[Attacker's Bans](https://communitybanlist.com/search/${info.attacker.steamID})` - }); - - await this.sendDiscordMessage({ - embed: { - title: `Teamkill: ${info.attacker.name}`, - color: this.options.color, - fields: fields, - timestamp: info.time.toISOString() - } - }); - } -} diff --git a/squad-server/plugins/fog-of-war.js b/squad-server/plugins/fog-of-war.js deleted file mode 100644 index 2db57283d..000000000 --- a/squad-server/plugins/fog-of-war.js +++ /dev/null @@ -1,46 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class FogOfWar extends BasePlugin { - static get description() { - return 'The FogOfWar plugin can be used to automate setting fog of war mode.'; - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - mode: { - required: false, - description: 'Fog of war mode to set.', - default: 1 - }, - delay: { - required: false, - description: 'Delay before setting fog of war mode.', - default: 10 * 1000 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - 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() { - setTimeout(() => { - this.server.rcon.setFogOfWar(this.options.mode); - }, this.options.delay); - } -} diff --git a/squad-server/plugins/index.js b/squad-server/plugins/index.js deleted file mode 100644 index a6a1a9540..000000000 --- a/squad-server/plugins/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import fs from 'fs'; - -import Logger from 'core/logger'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -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) { - // Check for non .js file type - if (!dirent.isFile() || !dirent.name.endsWith('.js')) { - continue; - } - if ( - [ - 'index.js', - 'base-plugin.js', - 'discord-base-message-updater.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 new Plugins(); diff --git a/squad-server/plugins/intervalled-broadcasts.js b/squad-server/plugins/intervalled-broadcasts.js deleted file mode 100644 index bb47f87c2..000000000 --- a/squad-server/plugins/intervalled-broadcasts.js +++ /dev/null @@ -1,49 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class IntervalledBroadcasts extends BasePlugin { - static get description() { - return ( - 'The IntervalledBroadcasts plugin allows you to set broadcasts, which will be broadcasted at ' + - 'preset intervals' - ); - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - broadcasts: { - required: false, - description: 'Messages to broadcast.', - default: [], - example: ['This server is powered by SquadJS.'] - }, - interval: { - required: false, - description: 'Frequency of the broadcasts in milliseconds.', - default: 5 * 60 * 1000 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - 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.options.broadcasts.push(this.options.broadcasts.shift()); - } -} diff --git a/squad-server/plugins/readme.md b/squad-server/plugins/readme.md deleted file mode 100644 index cd4f0bdf5..000000000 --- a/squad-server/plugins/readme.md +++ /dev/null @@ -1,45 +0,0 @@ -## Creating Your Own Plugins -To create your own plugin you need a basic knowledge of JavaScript. - -Typical plugins are functions that take the server as an argument in order to allow the plugin to access information about the server or manipulate it in some way: -```js -function aPluginToLogServerID(server){ - console.log(server.id); -} -``` - -Stored in the server object are a range of different properties that store information about the server. - * `id` - ID of the server. - * `serverName` - Name of the server. - * `maxPlayers` - Maximum number of players on the server. - * `publicSlots` - Maximum number of public slots. - * `reserveSlots` - Maximum number of reserved slots. - * `publicQueue` - Length of the public queue. - * `reserveQueue` - Length of the reserved queue. - * `matchTimeout` - Time until match ends? - * `gameVersion` - Game version. - * `layerHistory` - Array history of layers used with most recent at the start. Each entry is an object with layer info in. - * `currentLayer` - The current layer. - * `nextLayer` - The next layer. - * `players` - Array of players. Each entry is a PlayerObject with various bits of info in. - -One approach to making a plugin would be to run an action periodically, in the style of the original SquadJS: -```js -function aPluginToLogPlayerCountEvery60Seconds(server){ - setInterval(() => { - console.log(server.players.length); - }, 60 * 1000); -} -``` - -A more common approach in this version of SquadJS is to react to an event happening: -```js -function aPluginToLogTeamkills(server){ - server.on('TEAMKILL', info => { - console.log(info); - }); -} -``` -Various actions can be completed in a plugin. Most of these will involve outside system, e.g. Discord.js to run a Discord bot, so they are not documented here. However, you may run RCON commands using `server.rcon.execute("Command");`. - -If you're struggling to create a plugin, the existing [`plugins`](https://github.com/Team-Silver-Sphere/SquadJS/tree/master/plugins) are a good place to go for examples or feel free to ask for help in the Squad RCON Discord. \ No newline at end of file diff --git a/squad-server/plugins/seeding-mode.js b/squad-server/plugins/seeding-mode.js deleted file mode 100644 index 5aa67fbe0..000000000 --- a/squad-server/plugins/seeding-mode.js +++ /dev/null @@ -1,103 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class SeedingMode extends BasePlugin { - static get description() { - return ( - 'The SeedingMode plugin broadcasts seeding rule messages to players at regular intervals ' + - 'when the server is below a specified player count. It can also be configured to display "Live" messages when ' + - 'the server goes live.' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - interval: { - required: false, - description: 'Frequency of seeding messages in milliseconds.', - default: 2.5 * 60 * 1000 - }, - seedingThreshold: { - required: false, - description: 'Player count required for server not to be in seeding mode.', - default: 50 - }, - seedingMessage: { - required: false, - description: 'Seeding message to display.', - default: 'Seeding Rules Active! Fight only over the middle flags! No FOB Hunting!' - }, - liveEnabled: { - required: false, - description: 'Enable "Live" messages for when the server goes live.', - default: true - }, - liveThreshold: { - required: false, - description: 'Player count required for "Live" messages to not bee displayed.', - default: 52 - }, - liveMessage: { - required: false, - description: '"Live" message to display.', - default: 'Live!' - }, - waitOnNewGames: { - required: false, - description: 'Should the plugin wait to be executed on NEW_GAME event.', - default: true - }, - waitTimeOnNewGame: { - required: false, - description: 'The time to wait before check player counts in seconds.', - default: 30 - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.stop = false; - this.broadcast = this.broadcast.bind(this); - this.onNewGame = this.onNewGame.bind(this); - } - - async mount() { - if (this.options.waitOnNewGames) { - this.server.on('NEW_GAME', this.onNewGame); - } - - this.interval = setInterval(this.broadcast, this.options.interval); - } - - async unmount() { - clearInterval(this.interval); - this.server.removeEventListener('NEW_GAME', this.onNewGame); - } - - onNewGame() { - this.stop = true; - setTimeout(() => { - this.stop = false; - }, 30 * 1000); - } - - async broadcast() { - if (this.stop) return; - if ( - this.server.a2sPlayerCount !== 0 && - this.server.a2sPlayerCount < this.options.seedingThreshold - ) - await this.server.rcon.broadcast(this.options.seedingMessage); - else if ( - this.server.a2sPlayerCount !== 0 && - this.options.liveEnabled && - this.server.a2sPlayerCount < this.options.liveThreshold - ) - await this.server.rcon.broadcast(this.options.liveMessage); - } -} diff --git a/squad-server/plugins/socket-io-api.js b/squad-server/plugins/socket-io-api.js deleted file mode 100644 index 64a09a358..000000000 --- a/squad-server/plugins/socket-io-api.js +++ /dev/null @@ -1,181 +0,0 @@ -import { createServer } from 'http'; -import { Server } from 'socket.io'; - -import BasePlugin from './base-plugin.js'; - -const eventsToBroadcast = [ - 'CHAT_MESSAGE', - 'POSSESSED_ADMIN_CAMERA', - 'UNPOSSESSED_ADMIN_CAMERA', - 'RCON_ERROR', - 'ADMIN_BROADCAST', - 'DEPLOYABLE_DAMAGED', - 'NEW_GAME', - 'PLAYER_CONNECTED', - 'PLAYER_DISCONNECTED', - 'PLAYER_DAMAGED', - 'PLAYER_WOUNDED', - 'PLAYER_DIED', - 'PLAYER_REVIVED', - 'TEAMKILL', - 'PLAYER_POSSESS', - 'PLAYER_UNPOSSESS', - 'TICK_RATE', - 'PLAYER_TEAM_CHANGE', - 'PLAYER_SQUAD_CHANGE', - 'UPDATED_PLAYER_INFORMATION', - 'UPDATED_LAYER_INFORMATION', - 'UPDATED_A2S_INFORMATION', - 'PLAYER_AUTO_KICKED', - 'PLAYER_WARNED', - 'PLAYER_KICKED', - 'PLAYER_BANNED', - 'SQUAD_CREATED' -]; - -export default class SocketIOAPI extends BasePlugin { - static get description() { - return ( - 'The SocketIOAPI plugin allows remote access to a SquadJS instance via Socket.IO' + - '
    As a client example you can use this to connect to the socket.io server;' + - `
    
    -      const socket = io.connect('ws://IP:PORT', {
    -        auth: {
    -          token: "MySecretPassword"
    -        }
    -      })
    -    
    ` + - 'If you need more documentation about socket.io please go ahead and read the following;' + - '
    General Socket.io documentation: Socket.io Docs' + - '
    Authentication and securing your websocket: Sending-credentials' + - '
    How to use, install and configure a socketIO-client: Usage Guide with Examples' - ); - } - - static get defaultEnabled() { - return false; - } - - static get optionsSpecification() { - return { - websocketPort: { - required: true, - description: 'The port for the websocket.', - default: '', - example: '3000' - }, - securityToken: { - required: true, - description: 'Your secret token/password for connecting.', - default: '', - example: 'MySecretPassword' - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.httpServer = createServer(); - - this.io = new Server(this.httpServer, { - cors: { - origin: 'http://localhost:3000', - methods: ['GET', 'POST'] - } - }); - - this.io.use((socket, next) => { - if (socket.handshake.auth && socket.handshake.auth.token === this.options.securityToken) { - next(); - } else { - next(new Error('Invalid token.')); - } - }); - - this.io.on('connection', (socket) => { - this.verbose(1, 'New Connection Made.'); - this.bindListeners(socket, this.server); - this.bindListeners(socket, this.server.rcon, 'rcon.'); - // Events to broadcast - for (const eventToBroadcast of eventsToBroadcast) { - this.server.on(eventToBroadcast, (...args) => { - socket.emit(eventToBroadcast, ...args); - }); - } - }); - } - - async mount() { - this.httpServer.listen(this.options.websocketPort); - } - - async unmount() { - this.httpServer.close(); - } - - bindListeners(socket, obj, prefix = '') { - const ignore = [ - 'options', - 'constructor', - 'watch', - 'unwatch', - 'setupRCON', - 'setupLogParser', - 'getPlayerByCondition', - 'pingSquadJSAPI', - '_events', - '_eventsCount', - '_maxListeners', - 'plugins', - 'rcon', - 'logParser', - 'updatePlayerListInterval', - 'updatePlayerListTimeout', - 'updateLayerInformationInterval', - 'updateLayerInformationTimeout', - 'updateA2SInformationInterval', - 'updateA2SInformationTimeout', - 'pingSquadJSAPIInterval', - 'pingSquadJSAPI', - 'pingSquadJSAPITimeout', - 'rcon.constructor', - 'rcon.processChatPacket', - 'rcon._events', - 'rcon._eventsCount', - 'rcon._maxListeners', - 'rcon.password', - 'rcon.connect', - 'rcon.onData', - 'rcon.onClose', - 'rcon.onError', - 'rcon.client', - 'rcon.autoReconnect', - 'rcon.autoReconnectTimeout', - 'rcon.incomingData', - 'rcon.incomingResponse', - 'rcon.responseCallbackQueue' - ]; - for (const key of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) { - if (ignore.includes(`${prefix}${key}`)) continue; - this.verbose(1, `Setting method listener for ${prefix}${key}...`); - socket.on(`${prefix}${key}`, async (...rawArgs) => { - const args = rawArgs.slice(0, rawArgs.length - 1); - const callback = rawArgs[rawArgs.length - 1]; - this.verbose(1, `Call to ${prefix}${key}(${args.join(', ')})`); - const reponse = await obj[key](...args); - callback(reponse); - }); - } - - for (const key of Object.getOwnPropertyNames(obj)) { - if (ignore.includes(`${prefix}${key}`)) continue; - this.verbose(1, `Setting properties listener for ${prefix}${key}...`); - socket.on(`${prefix}${key}`, (callback) => { - this.verbose(1, `Call to ${prefix}${key}...`); - const reponse = obj[key]; - callback(reponse); - }); - } - } -} diff --git a/squad-server/plugins/team-randomizer.js b/squad-server/plugins/team-randomizer.js deleted file mode 100644 index e677b84ab..000000000 --- a/squad-server/plugins/team-randomizer.js +++ /dev/null @@ -1,65 +0,0 @@ -import BasePlugin from './base-plugin.js'; - -export default class TeamRandomizer extends BasePlugin { - static get description() { - return ( - "The TeamRandomizer can be used to randomize teams. It's great for destroying clan stacks or for " + - 'social events. It can be run by typing, by default, !randomize into in-game admin chat' - ); - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - command: { - required: false, - description: 'The command used to randomize the teams.', - default: 'randomize' - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.onChatCommand = this.onChatCommand.bind(this); - } - - async mount() { - this.server.on(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand); - } - - async unmount() { - this.server.removeEventListener(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand); - } - - async onChatCommand(info) { - if (info.chat !== 'ChatAdmin') return; - - const players = this.server.players.slice(0); - - let currentIndex = players.length; - let temporaryValue; - let randomIndex; - - while (currentIndex !== 0) { - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 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.eosID); - - team = team === '1' ? '2' : '1'; - } - } -} diff --git a/squad-server/scripts/build-config-file.js b/squad-server/scripts/build-config-file.js deleted file mode 100644 index e6cfc7b08..000000000 --- a/squad-server/scripts/build-config-file.js +++ /dev/null @@ -1,8 +0,0 @@ -import SquadServerFactory from '../factory.js'; - -console.log('Building config...'); -SquadServerFactory.buildConfigFile() - .then(() => { - console.log('Done.'); - }) - .catch(console.log); diff --git a/squad-server/scripts/build-readme.js b/squad-server/scripts/build-readme.js deleted file mode 100644 index f471c91bd..000000000 --- a/squad-server/scripts/build-readme.js +++ /dev/null @@ -1,8 +0,0 @@ -import SquadServerFactory from '../factory.js'; - -console.log('Building readme...'); -SquadServerFactory.buildReadmeFile() - .then(() => { - console.log('Done.'); - }) - .catch(console.log); diff --git a/squad-server/templates/SquadJS-Dashboard-v2.json b/squad-server/templates/SquadJS-Dashboard-v2.json deleted file mode 100644 index ed8b8ab7b..000000000 --- a/squad-server/templates/SquadJS-Dashboard-v2.json +++ /dev/null @@ -1,3623 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 2, - "id": 7, - "iteration": 1615585228169, - "links": [], - "panels": [ - { - "columnAliases": [], - "columnFiltersEnabled": false, - "columnWidthHints": [], - "columns": [], - "compactRowsEnabled": false, - "datasource": "MySQL", - "datatablePagingType": "simple_numbers", - "datatableTheme": "basic_theme", - "description": "This panel does not sort top scores. ", - "emptyData": true, - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 24, - "x": 0, - "y": 0 - }, - "hoverEnabled": true, - "id": 35, - "infoEnabled": true, - "lengthChangeEnabled": true, - "orderColumnEnabled": true, - "pagingTypes": [ - { - "text": "Page number buttons only", - "value": "numbers" - }, - { - "text": "'Previous' and 'Next' buttons only", - "value": "simple" - }, - { - "text": "'Previous' and 'Next' buttons, plus page numbers", - "value": "simple_numbers" - }, - { - "text": "'First', 'Previous', 'Next' and 'Last' buttons", - "value": "full" - }, - { - "text": "'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers", - "value": "full_numbers" - }, - { - "text": "'First' and 'Last' buttons, plus page numbers", - "value": "first_last_numbers" - } - ], - "panelHeight": null, - "pluginVersion": "7.1.1", - "rowNumbersEnabled": false, - "rowsPerPage": 5, - "scroll": false, - "scrollHeight": "default", - "searchEnabled": true, - "showCellBorders": false, - "showHeader": true, - "showRowBorders": true, - "sort": { - "col": 0, - "desc": true - }, - "sortByColumns": [ - { - "columnData": "Kills", - "sortMethod": "desc" - } - ], - "sortByColumnsData": [ - [ - 3, - "desc" - ] - ], - "stripedRowsEnabled": true, - "styles": [ - { - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "splitPattern": "/ /", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\r\n m.attacker AS \"Steam ID\",\r\n m.attackerName AS \"Name\",\r\n `Wounds`,\r\n `Kills`,\r\n `Deaths`,\r\n `Kills`/`Deaths` AS `K/D`,\r\n `Revives`\r\nFROM `DBLog_Wounds` m\r\nLEFT JOIN (\r\n SELECT\r\n attacker,\r\n COUNT(*) AS `Wounds`\r\n FROM `DBLog_Wounds`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY attacker\r\n) w ON w.attacker = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n attacker,\r\n COUNT(*) AS `Kills`\r\n FROM `DBLog_Deaths`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY attacker\r\n) k ON k.attacker = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n victim,\r\n COUNT(*) AS `Deaths`\r\n FROM `DBLog_Deaths`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY victim\r\n) d ON d.victim = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n reviver,\r\n COUNT(*) AS `Revives`\r\n FROM `DBLog_Revives`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY reviver\r\n) r ON r.reviver = m.attacker\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID \r\nGROUP BY m.attacker\r\nHAVING `K/D` IS NOT NULL;", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "DBLog_TickRates", - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "themeOptions": { - "dark": "./styles/dark.scss", - "light": "./styles/light.scss" - }, - "themes": [ - { - "disabled": false, - "text": "Basic", - "value": "basic_theme" - }, - { - "disabled": true, - "text": "Bootstrap", - "value": "bootstrap_theme" - }, - { - "disabled": true, - "text": "Foundation", - "value": "foundation_theme" - }, - { - "disabled": true, - "text": "ThemeRoller", - "value": "themeroller_theme" - } - ], - "timeFrom": null, - "timeShift": null, - "title": " Only for search in the top scores - This panel does not sort top scores. ", - "transform": "table", - "type": "briangann-datatable-panel" - }, - { - "columns": [], - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "100%", - "gridPos": { - "h": 14, - "w": 24, - "x": 0, - "y": 9 - }, - "id": 32, - "pageSize": null, - "showHeader": true, - "sort": { - "col": 3, - "desc": true - }, - "styles": [], - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\r\n m.attacker AS \"Steam ID\",\r\n m.attackerName AS \"Name\",\r\n `Wounds`,\r\n `Kills`,\r\n `Deaths`,\r\n `Kills`/`Deaths` AS `K/D`,\r\n `Revives`\r\nFROM `DBLog_Wounds` m\r\nLEFT JOIN (\r\n SELECT\r\n attacker,\r\n COUNT(*) AS `Wounds`\r\n FROM `DBLog_Wounds`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY attacker\r\n) w ON w.attacker = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n attacker,\r\n COUNT(*) AS `Kills`\r\n FROM `DBLog_Deaths`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY attacker\r\n) k ON k.attacker = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n victim,\r\n COUNT(*) AS `Deaths`\r\n FROM `DBLog_Deaths`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY victim\r\n) d ON d.victim = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n reviver,\r\n COUNT(*) AS `Revives`\r\n FROM `DBLog_Revives`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY reviver\r\n) r ON r.reviver = m.attacker\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID \r\nGROUP BY m.attacker\r\nHAVING `K/D` IS NOT NULL;", - "refId": "B", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Top Scorers", - "transform": "table", - "type": "table-old" - }, - { - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": {}, - "displayName": "", - "mappings": [], - "max": 60, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "dark-red", - "value": null - }, - { - "color": "yellow", - "value": 15 - }, - { - "color": "dark-green", - "value": 20 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 23 - }, - "id": 16, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true - }, - "pluginVersion": "7.1.4", - "targets": [ - { - "format": "time_series", - "group": [], - "groupBy": [], - "measurement": "tick_rate", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT LAST(\"tick_rate\") FROM \"DBLog_TickRates\" WHERE server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n time AS \"time\",\n tickRate\nFROM DBLog_TickRates\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nORDER BY time DESC", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "tick_rate" - ], - "type": "column" - } - ] - ], - "table": "DBLog_TickRates", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "server", - "=", - "$SERVER" - ], - "type": "expression" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Current Tick Rate", - "type": "gauge" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 18, - "x": 6, - "y": 23 - }, - "hiddenSeries": false, - "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "7.1.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "5m" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "tick_rate", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n mean(\"tick_rate\") \nFROM \"DBLog_TickRates\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID'\nGROUP BY time($INTERVAL) fill(null)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n avg(tickRate) AS \"tick_rate\"\nFROM DBLog_TickRates\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "tick_rate" - ], - "type": "column" - }, - { - "params": [ - "avg" - ], - "type": "aggregate" - }, - { - "params": [ - "tick_rate" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_TickRates", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [ - { - "colorMode": "custom", - "fill": false, - "fillColor": "rgba(50, 116, 217, 0.2)", - "line": true, - "lineColor": "#F2495C", - "op": "lt", - "value": 15, - "yaxis": "left" - }, - { - "colorMode": "custom", - "fill": false, - "fillColor": "rgba(50, 116, 217, 0.2)", - "line": true, - "lineColor": "#FADE2A", - "op": "lt", - "value": 20, - "yaxis": "left" - } - ], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Tick Rate", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": "60", - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": {}, - "displayName": "", - "mappings": [], - "max": 80, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "orange", - "value": null - }, - { - "color": "green", - "value": 50 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 31 - }, - "id": 27, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true - }, - "pluginVersion": "7.1.4", - "targets": [ - { - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [], - "measurement": "tick_rate", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT LAST(\"player_count\") FROM \"DBLog_PlayerCounts\" WHERE server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n players AS \"Player Count\"\nFROM DBLog_PlayerCounts\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval) DESC\nLIMIT 1", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "player_count" - ], - "type": "column" - }, - { - "params": [ - "Player Count" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_PlayerCounts", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Current Player Count", - "type": "gauge" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 18, - "x": 6, - "y": 31 - }, - "hiddenSeries": false, - "id": 28, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "7.1.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "5m" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "tick_rate", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n MEAN(\"player_count\") \nFROM \"DBLog_PlayerCounts\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID'\nGROUP BY time($INTERVAL) fill(null)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n avg(players) AS \"player_count\"\nFROM DBLog_PlayerCounts\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "player_count" - ], - "type": "column" - }, - { - "params": [ - "avg" - ], - "type": "aggregate" - }, - { - "params": [ - "player_count" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_PlayerCounts", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [ - { - "colorMode": "custom", - "fill": false, - "fillColor": "rgba(50, 116, 217, 0.2)", - "line": true, - "lineColor": "#FF9830", - "op": "lt", - "value": 50, - "yaxis": "left" - } - ], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Player Count", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": "80", - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 0, - "y": 39 - }, - "id": 12, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": " Wounds", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true, - "ymax": null, - "ymin": null - }, - "tableColumn": "", - "targets": [ - { - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT COUNT(\"victimName\") \nFROM DBLog_Wounds\nWHERE \n $timeFilter AND\n server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"id\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "id" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": "", - "timeFrom": null, - "timeShift": null, - "title": "Wounded in Time Period", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 6, - "y": 39 - }, - "id": 13, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": " Deaths", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true, - "ymax": null, - "ymin": null - }, - "tableColumn": "", - "targets": [ - { - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n count(\"victimName\") \nFROM \"DBLog_Deaths\"\nWHERE \n $timeFilter AND\n server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"id\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "id" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": "", - "timeFrom": null, - "timeShift": null, - "title": "Deaths in Time Period", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 12, - "y": 39 - }, - "id": 10, - "interval": "", - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": " TKs", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true, - "ymax": null, - "ymin": null - }, - "tableColumn": "", - "targets": [ - { - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [], - "measurement": "player_wound", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n count(\"victimName\") \nFROM \"DBLog_Wounds\"\nWHERE \n teamkill = true AND\n $timeFilter AND\n server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"id\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n teamkill = '1' AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "id" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "tinyint", - "name": "", - "params": [ - "teamkill", - "=", - "'1'" - ], - "type": "expression" - } - ] - } - ], - "thresholds": "", - "timeFrom": null, - "timeShift": null, - "title": "TKs in Time Period", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 18, - "y": 39 - }, - "id": 14, - "interval": "", - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": " Revives", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true, - "ymax": null, - "ymin": null - }, - "tableColumn": "", - "targets": [ - { - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [], - "measurement": "revive", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Revives\" WHERE $timeFilter AND server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"id\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "id" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": "", - "timeFrom": null, - "timeShift": null, - "title": "Revives in Time Period", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "columns": [], - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "100%", - "gridPos": { - "h": 13, - "w": 24, - "x": 0, - "y": 43 - }, - "id": 4, - "pageSize": null, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "", - "align": "auto", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Time", - "thresholds": [], - "type": "date", - "unit": "short" - } - ], - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n a.time AS \"Time\",\n a.attacker AS \"Attacker\",\n a.attackerName AS \"Attacker Name\",\n a.weapon AS \"Weapon\",\n a.victim AS \"Victim\",\n a.victimName AS \"Victim Name\",\n Total AS \"Total in Time Frame\"\nFROM DBLog_Wounds a\nJOIN\n (\n SELECT\n attacker,\n COUNT(*) AS \"Total\"\n FROM DBLog_Wounds\n WHERE \n $__timeFilter(time) AND\n server = $SERVER_ID AND\n teamkill = true\n GROUP BY attacker\n ) AS b\n ON a.attacker = b.attacker\nWHERE \n $__timeFilter(time) AND\n server = $SERVER_ID AND\n teamkill = true AND\n server = $SERVER_ID\nORDER BY time DESC\nLIMIT 30;", - "refId": "B", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Recent Teamkills", - "transform": "table", - "type": "table-old" - }, - { - "aliasColors": {}, - "breakPoint": "50%", - "cacheTimeout": null, - "combine": { - "label": "Others", - "threshold": 0 - }, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "80%", - "format": "short", - "gridPos": { - "h": 9, - "w": 6, - "x": 0, - "y": 56 - }, - "id": 20, - "interval": null, - "legend": { - "percentage": true, - "show": true, - "values": true - }, - "legendType": "Under graph", - "links": [], - "maxDataPoints": 3, - "nullPointMode": "connected", - "pieType": "pie", - "strokeWidth": 1, - "targets": [ - { - "alias": "Non-Teamkills", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Wounds\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID' AND\n teamkill = false", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Non-Teamkill\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n teamkill = 0 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Non-Teamkill" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "tinyint", - "name": "", - "params": [ - "teamkill", - "=", - "0" - ], - "type": "expression" - } - ] - }, - { - "alias": "Teamkills", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Wounds\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID' AND\n teamkill = true", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Teamkill\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n teamkill = '1' AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Teamkill" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "tinyint", - "name": "", - "params": [ - "teamkill", - "=", - "'1'" - ], - "type": "expression" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Non-Teamkills vs Teamkills (Wounds)", - "type": "grafana-piechart-panel", - "valueName": "current" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 18, - "x": 6, - "y": 56 - }, - "hiddenSeries": false, - "id": 29, - "interval": "2m", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "7.1.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "alias": "Players Wounded", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_wound", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Wounds\" WHERE $timeFilter AND server = '$SERVER_ID' GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"All\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "All" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "Players Wounded By Team 1", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Wounds\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=1 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 1\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 1 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 1" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "1" - ], - "type": "expression" - } - ] - }, - { - "alias": "Players Wounded By Team 2", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Wounds\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=2 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 2\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 2 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "C", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 2" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "2" - ], - "type": "expression" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Wounds Per Minutes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "breakPoint": "50%", - "cacheTimeout": null, - "combine": { - "label": "Others", - "threshold": 0 - }, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "80%", - "format": "short", - "gridPos": { - "h": 9, - "w": 6, - "x": 0, - "y": 65 - }, - "id": 18, - "interval": null, - "legend": { - "header": "", - "percentage": true, - "show": true, - "sideWidth": null, - "values": true - }, - "legendType": "Under graph", - "links": [], - "maxDataPoints": 3, - "nullPointMode": "connected", - "pieType": "pie", - "strokeWidth": 1, - "targets": [ - { - "alias": "Deaths", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Deaths\" \nWHERE \n $timeFilter ", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(woundTime,$__interval),\n count(id) AS \"Deaths\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(woundTime) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(woundTime,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Deaths" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "woundTime", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "Revives", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Revives\" \nWHERE \n $timeFilter ", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(woundTime,$__interval),\n count(id) AS \"Revives\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(woundTime) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(woundTime,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Revives" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "woundTime", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Deaths vs Revives", - "type": "grafana-piechart-panel", - "valueName": "current" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 18, - "x": 6, - "y": 65 - }, - "hiddenSeries": false, - "id": 6, - "interval": "2m", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "7.1.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "alias": "Players Killed", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_wound", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"All\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "All" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "Players Killed By Team 1", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=1 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 1\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 1 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 1" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "1" - ], - "type": "expression" - } - ] - }, - { - "alias": "Players Killed By Team 2", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=2 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 2\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 2 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "C", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 2" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "2" - ], - "type": "expression" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Kills Per Minutes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "breakPoint": "50%", - "cacheTimeout": null, - "combine": { - "label": "Others", - "threshold": 0 - }, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "80%", - "format": "short", - "gridPos": { - "h": 9, - "w": 6, - "x": 0, - "y": 74 - }, - "id": 31, - "interval": null, - "legend": { - "percentage": true, - "show": true, - "values": true - }, - "legendType": "Under graph", - "links": [], - "maxDataPoints": 3, - "nullPointMode": "connected", - "pieType": "pie", - "strokeWidth": 1, - "targets": [ - { - "alias": "Team 1 Kills", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=1", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 1\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 1 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 1" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "1" - ], - "type": "expression" - } - ] - }, - { - "alias": "Team 2 Kills", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=2", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 2\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 2" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Team 1 vs Team 2", - "type": "grafana-piechart-panel", - "valueName": "current" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 18, - "x": 6, - "y": 74 - }, - "hiddenSeries": false, - "id": 8, - "interval": "2m", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "7.1.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "alias": "Revives", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "revive", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Revives\" WHERE $timeFilter AND server = '$SERVER_ID' GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"All\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "All" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "Revives", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "revive", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Revives\" WHERE $timeFilter AND server = '$SERVER_ID' AND reviverTeamID = 1 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 1\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(time) AND\n victimTeamID = 1 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 1" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "victimTeamID", - "=", - "1" - ], - "type": "expression" - } - ] - }, - { - "alias": "Revives", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "revive", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Revives\" WHERE $timeFilter AND server = '$SERVER_ID' AND reviverTeamID = 2 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 2\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(time) AND\n victimTeamID = 2 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "C", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 2" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "victimTeamID", - "=", - "2" - ], - "type": "expression" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Revives Per Minutes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "columns": [], - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "100%", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 83 - }, - "id": 22, - "pageSize": null, - "showHeader": true, - "sort": { - "col": 2, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "align": "auto", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "", - "align": "auto", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n layerClassname as \"Layer\",\n startTime AS \"Start Time\",\n endTime AS \"End Time\"\nFROM `DBLog_Matches`\nWHERE \n (\n $__timeFilter(startTime) OR \n $__timeFilter(endTime) OR\n endTime IS NULL\n ) AND \n server = $SERVER_ID \nORDER BY startTime DESC;", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Matches", - "transform": "table", - "type": "table-old" - }, - { - "aliasColors": {}, - "breakPoint": "50%", - "cacheTimeout": null, - "combine": { - "label": "Others", - "threshold": 0 - }, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "80%", - "format": "short", - "gridPos": { - "h": 8, - "w": 6, - "x": 12, - "y": 83 - }, - "id": 25, - "interval": null, - "legend": { - "percentage": true, - "show": true, - "sort": "current", - "sortDesc": true, - "values": true - }, - "legendType": "Under graph", - "links": [], - "maxDataPoints": 3, - "nullPointMode": "connected", - "pieType": "pie", - "strokeWidth": 1, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "layer", - "rawQuery": true, - "rawSql": "SELECT\n layerClassname AS metric,\n COUNT(*),\n startTime AS time\nFROM `DBLog_Matches`\nWHERE\n (\n $__timeFilter(startTime) OR \n $__timeFilter(endTime) OR\n endTime IS NULL\n ) AND \n server = $SERVER_ID \nGROUP BY layerClassname", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "game", - "timeColumn": "startTime", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Layers", - "type": "grafana-piechart-panel", - "valueName": "current" - }, - { - "aliasColors": {}, - "breakPoint": "50%", - "cacheTimeout": null, - "combine": { - "label": "Others", - "threshold": 0 - }, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "80%", - "format": "short", - "gridPos": { - "h": 8, - "w": 6, - "x": 18, - "y": 83 - }, - "id": 24, - "interval": null, - "legend": { - "percentage": true, - "show": true, - "values": true - }, - "legendType": "Under graph", - "links": [], - "maxDataPoints": 3, - "nullPointMode": "connected", - "pieType": "pie", - "strokeWidth": 1, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "layer", - "rawQuery": true, - "rawSql": "SELECT\n mapClassname AS metric,\n COUNT(*),\n startTime AS time\nFROM `DBLog_Matches`\nWHERE\n (\n $__timeFilter(startTime) OR \n $__timeFilter(endTime) OR\n endTime IS NULL\n ) AND \n server = $SERVER_ID \nGROUP BY mapClassname", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "game", - "timeColumn": "startTime", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Maps", - "type": "grafana-piechart-panel", - "valueName": "current" - }, - { - "aliasColors": {}, - "breakPoint": "50%", - "cacheTimeout": null, - "combine": { - "label": "Others", - "threshold": "0.01" - }, - "datasource": "MySQL", - "fieldConfig": { - "defaults": { - "custom": { - "align": null - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "fontSize": "80%", - "format": "short", - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 91 - }, - "id": 33, - "interval": null, - "legend": { - "percentage": true, - "show": true, - "values": true - }, - "legendType": "Right side", - "links": [], - "maxDataPoints": 3, - "nullPointMode": "connected", - "pieType": "pie", - "pluginVersion": "7.1.0", - "strokeWidth": 1, - "targets": [ - { - "alias": "Non-Teamkills", - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "weapon", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Wounds\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID' AND\n teamkill = false", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n weapon AS metric,\n count(id) AS \"Wounds\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 2\nORDER BY count(id) DESC", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Wounds" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Weapons", - "type": "grafana-piechart-panel", - "valueName": "current" - }, - { - "columnAliases": [], - "columnFiltersEnabled": false, - "columnWidthHints": [], - "columns": [], - "compactRowsEnabled": false, - "datasource": "MySQL", - "datatablePagingType": "simple_numbers", - "datatableTheme": "basic_theme", - "description": "", - "emptyData": false, - "fieldConfig": { - "defaults": { - "custom": {} - }, - "overrides": [] - }, - "fontSize": "100%", - "gridPos": { - "h": 10, - "w": 12, - "x": 12, - "y": 91 - }, - "hoverEnabled": true, - "id": 36, - "infoEnabled": true, - "lengthChangeEnabled": true, - "orderColumnEnabled": true, - "pagingTypes": [ - { - "$$hashKey": "object:227", - "text": "Page number buttons only", - "value": "numbers" - }, - { - "$$hashKey": "object:228", - "text": "'Previous' and 'Next' buttons only", - "value": "simple" - }, - { - "$$hashKey": "object:229", - "text": "'Previous' and 'Next' buttons, plus page numbers", - "value": "simple_numbers" - }, - { - "$$hashKey": "object:230", - "text": "'First', 'Previous', 'Next' and 'Last' buttons", - "value": "full" - }, - { - "$$hashKey": "object:231", - "text": "'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers", - "value": "full_numbers" - }, - { - "$$hashKey": "object:232", - "text": "'First' and 'Last' buttons, plus page numbers", - "value": "first_last_numbers" - } - ], - "panelHeight": null, - "pluginVersion": "7.1.1", - "rowNumbersEnabled": false, - "rowsPerPage": 20, - "scroll": true, - "scrollHeight": "default", - "searchEnabled": true, - "showCellBorders": false, - "showHeader": true, - "showRowBorders": true, - "sort": { - "col": 0, - "desc": true - }, - "sortByColumns": [ - { - "columnData": "Kills", - "sortMethod": "desc" - } - ], - "sortByColumnsData": [ - [ - 3, - "desc" - ] - ], - "stripedRowsEnabled": true, - "styles": [ - { - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "splitPattern": "/ /", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT DISTINCTROW\n layer,\n NTH_VALUE(LW.winner, 1) OVER (PARTITION BY LW.layer) AS \"Most Frequent Winner\",\n NTH_VALUE(LW.wins, 1) OVER (PARTITION BY LW.layer) AS \"Most Frequent Winner Wins\",\n (NTH_VALUE(LW.wins, 1) OVER (PARTITION BY LW.layer) / (NTH_VALUE(LW.wins, 1) OVER (PARTITION BY LW.layer) + NTH_VALUE(LW.wins, 2) OVER (PARTITION BY LW.layer))) * 100 AS \"Most Frequent Winner Win Percentage\",\n NTH_VALUE(LW.winner, 2) OVER (PARTITION BY LW.layer) AS \"Least Frequent Winner\",\n NTH_VALUE(LW.wins, 2) OVER (PARTITION BY LW.layer) AS \"Least Frequent Winner Count\",\n (NTH_VALUE(LW.wins, 2) OVER (PARTITION BY LW.layer) / (NTH_VALUE(LW.wins, 1) OVER (PARTITION BY LW.layer) + NTH_VALUE(LW.wins, 2) OVER (PARTITION BY LW.layer))) * 100 AS \"Least Frequent Winner Win Percentage\"\nFROM (\n SELECT\n M.layer,\n M.winner,\n COUNT(*) AS \"wins\"\n FROM dblog_matches M\n WHERE\n M.layer IS NOT NULL AND\n M.winner IS NOT NULL\n GROUP BY M.layer, M.winner\n ORDER BY wins DESC\n) LW;\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "DBLog_TickRates", - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "themeOptions": { - "dark": "./styles/dark.scss", - "light": "./styles/light.scss" - }, - "themes": [ - { - "$$hashKey": "object:208", - "disabled": false, - "text": "Basic", - "value": "basic_theme" - }, - { - "$$hashKey": "object:209", - "disabled": true, - "text": "Bootstrap", - "value": "bootstrap_theme" - }, - { - "$$hashKey": "object:210", - "disabled": true, - "text": "Foundation", - "value": "foundation_theme" - }, - { - "$$hashKey": "object:211", - "disabled": true, - "text": "ThemeRoller", - "value": "themeroller_theme" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Matches - Win/Loss Comparison", - "transform": "table", - "type": "briangann-datatable-panel" - } - ], - "refresh": false, - "schemaVersion": 26, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": true, - "text": "1", - "value": "1" - }, - "hide": 0, - "includeAll": false, - "label": "Server", - "multi": false, - "name": "SERVER_ID", - "options": [ - { - "selected": true, - "text": "1", - "value": "1" - }, - { - "selected": false, - "text": "2", - "value": "2" - } - ], - "query": "1,2", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - }, - { - "auto": false, - "auto_count": 30, - "auto_min": "10s", - "current": { - "selected": false, - "text": "1m", - "value": "1m" - }, - "hide": 0, - "label": "Interval", - "name": "INTERVAL", - "options": [ - { - "selected": true, - "text": "1m", - "value": "1m" - }, - { - "selected": false, - "text": "2m", - "value": "2m" - }, - { - "selected": false, - "text": "3m", - "value": "3m" - }, - { - "selected": false, - "text": "4m", - "value": "4m" - }, - { - "selected": false, - "text": "5m", - "value": "5m" - }, - { - "selected": false, - "text": "10m", - "value": "10m" - }, - { - "selected": false, - "text": "20m", - "value": "20m" - }, - { - "selected": false, - "text": "30m", - "value": "30m" - } - ], - "query": "1m,2m,3m,4m,5m,10m,20m,30m", - "refresh": 2, - "skipUrlSync": false, - "type": "interval" - } - ] - }, - "time": { - "from": "now-3h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "", - "title": "Server Overview", - "uid": "n7Xl7jEWx", - "version": 13 -} \ No newline at end of file diff --git a/squad-server/templates/SquadJS-Dashboard-v3.json b/squad-server/templates/SquadJS-Dashboard-v3.json deleted file mode 100644 index 188d61a52..000000000 --- a/squad-server/templates/SquadJS-Dashboard-v3.json +++ /dev/null @@ -1,3636 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 2, - "id": 1, - "iteration": 1656601727600, - "links": [], - "liveNow": false, - "panels": [ - { - "alignNumbersToRightEnabled": true, - "columnAliases": [], - "columnFiltersEnabled": false, - "columnWidthHints": [], - "columns": [], - "compactRowsEnabled": false, - "datasource": { - "uid": "MySQL" - }, - "datatablePagingType": "simple_numbers", - "datatableTheme": "basic_theme", - "description": "This panel does not sort top scores. ", - "emptyData": false, - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 24, - "x": 0, - "y": 0 - }, - "hoverEnabled": true, - "id": 35, - "infoEnabled": true, - "lengthChangeEnabled": true, - "orderColumnEnabled": true, - "pagingTypes": [ - { - "text": "Page number buttons only", - "value": "numbers" - }, - { - "text": "'Previous' and 'Next' buttons only", - "value": "simple" - }, - { - "text": "'Previous' and 'Next' buttons, plus page numbers", - "value": "simple_numbers" - }, - { - "text": "'First', 'Previous', 'Next' and 'Last' buttons", - "value": "full" - }, - { - "text": "'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers", - "value": "full_numbers" - }, - { - "text": "'First' and 'Last' buttons, plus page numbers", - "value": "first_last_numbers" - } - ], - "panelHeight": 284, - "pluginVersion": "7.1.1", - "rowNumbersEnabled": false, - "rowsPerPage": 5, - "scroll": false, - "scrollHeight": "default", - "searchEnabled": true, - "searchHighlightingEnabled": false, - "showCellBorders": false, - "showHeader": true, - "showRowBorders": true, - "sort": { - "col": 0, - "desc": true - }, - "sortByColumns": [ - { - "columnData": "Kills", - "sortMethod": "desc" - } - ], - "sortByColumnsData": [ - [ - 3, - "desc" - ] - ], - "stripedRowsEnabled": true, - "styles": [ - { - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "splitPattern": "/ /", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\r\n m.attacker AS \"Steam ID\",\r\n m.attackerName AS \"Name\",\r\n `Wounds`,\r\n `Kills`,\r\n `Deaths`,\r\n `Kills`/`Deaths` AS `K/D`,\r\n `Revives`\r\nFROM `DBLog_Wounds` m\r\nLEFT JOIN (\r\n SELECT\r\n attacker,\r\n COUNT(*) AS `Wounds`\r\n FROM `DBLog_Wounds`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY attacker\r\n) w ON w.attacker = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n attacker,\r\n COUNT(*) AS `Kills`\r\n FROM `DBLog_Deaths`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY attacker\r\n) k ON k.attacker = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n victim,\r\n COUNT(*) AS `Deaths`\r\n FROM `DBLog_Deaths`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY victim\r\n) d ON d.victim = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n reviver,\r\n COUNT(*) AS `Revives`\r\n FROM `DBLog_Revives`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY reviver\r\n) r ON r.reviver = m.attacker\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID \r\nGROUP BY m.attacker\r\nHAVING `K/D` IS NOT NULL;", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "DBLog_TickRates", - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "themeOptions": { - "dark": "./styles/dark.scss", - "light": "./styles/light.scss" - }, - "themes": [ - { - "disabled": false, - "text": "Basic", - "value": "basic_theme" - }, - { - "disabled": true, - "text": "Bootstrap", - "value": "bootstrap_theme" - }, - { - "disabled": true, - "text": "Foundation", - "value": "foundation_theme" - }, - { - "disabled": true, - "text": "ThemeRoller", - "value": "themeroller_theme" - } - ], - "title": " Only for search in the top scores - This panel does not sort top scores. ", - "transform": "table", - "type": "briangann-datatable-panel" - }, - { - "columns": [], - "datasource": { - "uid": "MySQL" - }, - "fontSize": "100%", - "gridPos": { - "h": 14, - "w": 24, - "x": 0, - "y": 9 - }, - "id": 32, - "showHeader": true, - "sort": { - "col": 3, - "desc": true - }, - "styles": [], - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\r\n m.attacker AS \"Steam ID\",\r\n m.attackerName AS \"Name\",\r\n `Wounds`,\r\n `Kills`,\r\n `Deaths`,\r\n `Kills`/`Deaths` AS `K/D`,\r\n `Revives`\r\nFROM `DBLog_Wounds` m\r\nLEFT JOIN (\r\n SELECT\r\n attacker,\r\n COUNT(*) AS `Wounds`\r\n FROM `DBLog_Wounds`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY attacker\r\n) w ON w.attacker = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n attacker,\r\n COUNT(*) AS `Kills`\r\n FROM `DBLog_Deaths`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY attacker\r\n) k ON k.attacker = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n victim,\r\n COUNT(*) AS `Deaths`\r\n FROM `DBLog_Deaths`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY victim\r\n) d ON d.victim = m.attacker\r\nLEFT JOIN (\r\n SELECT\r\n reviver,\r\n COUNT(*) AS `Revives`\r\n FROM `DBLog_Revives`\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID\r\n GROUP BY reviver\r\n) r ON r.reviver = m.attacker\r\n WHERE\r\n $__timeFilter(time) AND\r\n server = $SERVER_ID \r\nGROUP BY m.attacker\r\nHAVING `K/D` IS NOT NULL;", - "refId": "B", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Top Scorers", - "transform": "table", - "type": "table-old" - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "displayName": "", - "mappings": [], - "max": 60, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "dark-red", - "value": null - }, - { - "color": "yellow", - "value": 15 - }, - { - "color": "dark-green", - "value": 20 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 23 - }, - "id": 16, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true - }, - "pluginVersion": "9.0.2", - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [], - "groupBy": [], - "measurement": "tick_rate", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT LAST(\"tick_rate\") FROM \"DBLog_TickRates\" WHERE server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n time AS \"time\",\n tickRate\nFROM DBLog_TickRates\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nORDER BY time DESC", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "tick_rate" - ], - "type": "column" - } - ] - ], - "table": "DBLog_TickRates", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "server", - "=", - "$SERVER" - ], - "type": "expression" - } - ] - } - ], - "title": "Current Tick Rate", - "type": "gauge" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "uid": "MySQL" - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 18, - "x": 6, - "y": 23 - }, - "hiddenSeries": false, - "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "9.0.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "5m" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "tick_rate", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n mean(\"tick_rate\") \nFROM \"DBLog_TickRates\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID'\nGROUP BY time($INTERVAL) fill(null)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n avg(tickRate) AS \"tick_rate\"\nFROM DBLog_TickRates\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "tick_rate" - ], - "type": "column" - }, - { - "params": [ - "avg" - ], - "type": "aggregate" - }, - { - "params": [ - "tick_rate" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_TickRates", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [ - { - "colorMode": "custom", - "fill": false, - "fillColor": "rgba(50, 116, 217, 0.2)", - "line": true, - "lineColor": "#F2495C", - "op": "lt", - "value": 15, - "yaxis": "left" - }, - { - "colorMode": "custom", - "fill": false, - "fillColor": "rgba(50, 116, 217, 0.2)", - "line": true, - "lineColor": "#FADE2A", - "op": "lt", - "value": 20, - "yaxis": "left" - } - ], - "timeRegions": [], - "title": "Tick Rate", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "max": "60", - "min": "0", - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "displayName": "", - "mappings": [], - "max": 80, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "orange", - "value": null - }, - { - "color": "green", - "value": 50 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 31 - }, - "id": 27, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true - }, - "pluginVersion": "9.0.2", - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [], - "measurement": "tick_rate", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT LAST(\"player_count\") FROM \"DBLog_PlayerCounts\" WHERE server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n players AS \"Player Count\"\nFROM DBLog_PlayerCounts\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval) DESC\nLIMIT 1", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "player_count" - ], - "type": "column" - }, - { - "params": [ - "Player Count" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_PlayerCounts", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Current Player Count", - "type": "gauge" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "uid": "MySQL" - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 18, - "x": 6, - "y": 31 - }, - "hiddenSeries": false, - "id": 28, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "9.0.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "5m" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "tick_rate", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n MEAN(\"player_count\") \nFROM \"DBLog_PlayerCounts\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID'\nGROUP BY time($INTERVAL) fill(null)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n avg(players) AS \"player_count\"\nFROM DBLog_PlayerCounts\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "player_count" - ], - "type": "column" - }, - { - "params": [ - "avg" - ], - "type": "aggregate" - }, - { - "params": [ - "player_count" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_PlayerCounts", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [ - { - "colorMode": "custom", - "fill": false, - "fillColor": "rgba(50, 116, 217, 0.2)", - "line": true, - "lineColor": "#FF9830", - "op": "lt", - "value": 50, - "yaxis": "left" - } - ], - "timeRegions": [], - "title": "Player Count", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "max": "80", - "min": "0", - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "rgb(31, 120, 193)", - "mode": "fixed" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 0, - "y": 39 - }, - "id": 12, - "links": [], - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "9.0.2", - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT COUNT(\"victimName\") \nFROM DBLog_Wounds\nWHERE \n $timeFilter AND\n server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"id\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "id" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Wounded in Time Period", - "type": "stat" - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "rgb(31, 120, 193)", - "mode": "fixed" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 6, - "y": 39 - }, - "id": 13, - "links": [], - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "9.0.2", - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n count(\"victimName\") \nFROM \"DBLog_Deaths\"\nWHERE \n $timeFilter AND\n server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"id\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "id" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Deaths in Time Period", - "type": "stat" - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "rgb(31, 120, 193)", - "mode": "fixed" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 12, - "y": 39 - }, - "id": 10, - "interval": "", - "links": [], - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "9.0.2", - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [], - "measurement": "player_wound", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n count(\"victimName\") \nFROM \"DBLog_Wounds\"\nWHERE \n teamkill = true AND\n $timeFilter AND\n server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"id\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n teamkill = '1' AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "id" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "tinyint", - "name": "", - "params": [ - "teamkill", - "=", - "'1'" - ], - "type": "expression" - } - ] - } - ], - "title": "TKs in Time Period", - "type": "stat" - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "rgb(31, 120, 193)", - "mode": "fixed" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 18, - "y": 39 - }, - "id": 14, - "interval": "", - "links": [], - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "9.0.2", - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [], - "measurement": "revive", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Revives\" WHERE $timeFilter AND server = '$SERVER_ID'", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"id\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "id" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Revives in Time Period", - "type": "stat" - }, - { - "columns": [], - "datasource": { - "uid": "MySQL" - }, - "fontSize": "100%", - "gridPos": { - "h": 13, - "w": 24, - "x": 0, - "y": 43 - }, - "id": 4, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "", - "align": "auto", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Time", - "thresholds": [], - "type": "date", - "unit": "short" - } - ], - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n a.time AS \"Time\",\n a.attacker AS \"Attacker\",\n a.attackerName AS \"Attacker Name\",\n a.weapon AS \"Weapon\",\n a.victim AS \"Victim\",\n a.victimName AS \"Victim Name\",\n Total AS \"Total in Time Frame\"\nFROM DBLog_Wounds a\nJOIN\n (\n SELECT\n attacker,\n COUNT(*) AS \"Total\"\n FROM DBLog_Wounds\n WHERE \n $__timeFilter(time) AND\n server = $SERVER_ID AND\n teamkill = true\n GROUP BY attacker\n ) AS b\n ON a.attacker = b.attacker\nWHERE \n $__timeFilter(time) AND\n server = $SERVER_ID AND\n teamkill = true AND\n server = $SERVER_ID\nORDER BY time DESC\nLIMIT 30;", - "refId": "B", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Recent Teamkills", - "transform": "table", - "type": "table-old" - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 0, - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 6, - "x": 0, - "y": 56 - }, - "id": 20, - "links": [], - "maxDataPoints": 3, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "values": [ - "value", - "percent" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "alias": "Non-Teamkills", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Wounds\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID' AND\n teamkill = false", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Non-Teamkill\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n teamkill = 0 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Non-Teamkill" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "tinyint", - "name": "", - "params": [ - "teamkill", - "=", - "0" - ], - "type": "expression" - } - ] - }, - { - "alias": "Teamkills", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Wounds\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID' AND\n teamkill = true", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Teamkill\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n teamkill = '1' AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Teamkill" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "tinyint", - "name": "", - "params": [ - "teamkill", - "=", - "'1'" - ], - "type": "expression" - } - ] - } - ], - "title": "Non-Teamkills vs Teamkills (Wounds)", - "type": "piechart" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "uid": "MySQL" - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 18, - "x": 6, - "y": 56 - }, - "hiddenSeries": false, - "id": 29, - "interval": "2m", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "9.0.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "alias": "Players Wounded", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_wound", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Wounds\" WHERE $timeFilter AND server = '$SERVER_ID' GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"All\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "All" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "Players Wounded By Team 1", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Wounds\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=1 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 1\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 1 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 1" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "1" - ], - "type": "expression" - } - ] - }, - { - "alias": "Players Wounded By Team 2", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Wounds\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=2 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 2\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 2 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "C", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 2" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "2" - ], - "type": "expression" - } - ] - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Wounds Per Minutes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 0, - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 6, - "x": 0, - "y": 65 - }, - "id": 18, - "links": [], - "maxDataPoints": 3, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "values": [ - "value", - "percent" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "alias": "Deaths", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Deaths\" \nWHERE \n $timeFilter ", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(woundTime,$__interval),\n count(id) AS \"Deaths\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(woundTime) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(woundTime,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Deaths" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "woundTime", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "Revives", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Revives\" \nWHERE \n $timeFilter ", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(woundTime,$__interval),\n count(id) AS \"Revives\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(woundTime) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(woundTime,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Revives" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "woundTime", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Deaths vs Revives", - "type": "piechart" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "uid": "MySQL" - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 18, - "x": 6, - "y": 65 - }, - "hiddenSeries": false, - "id": 6, - "interval": "2m", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "9.0.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "alias": "Players Killed", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_wound", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"All\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "All" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "Players Killed By Team 1", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=1 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 1\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 1 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 1" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "1" - ], - "type": "expression" - } - ] - }, - { - "alias": "Players Killed By Team 2", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "player_die", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=2 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 2\"\nFROM DBLog_Deaths\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 2 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "C", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 2" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Deaths", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "2" - ], - "type": "expression" - } - ] - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Kills Per Minutes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "datasource": { - "uid": "MySQL" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 0, - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 6, - "x": 0, - "y": 74 - }, - "id": 31, - "links": [], - "maxDataPoints": 3, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "values": [ - "value", - "percent" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "alias": "Team 1 Kills", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=1", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 1\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n attackerTeamID = 1 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 1" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "attackerTeamID", - "=", - "1" - ], - "type": "expression" - } - ] - }, - { - "alias": "Team 2 Kills", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Deaths\" WHERE $timeFilter AND server = '$SERVER_ID' AND attackerTeamID=2", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 2\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 2" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Team 1 vs Team 2", - "type": "piechart" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "uid": "MySQL" - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 18, - "x": 6, - "y": 74 - }, - "hiddenSeries": false, - "id": 8, - "interval": "2m", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "9.0.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "alias": "Revives", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "revive", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Revives\" WHERE $timeFilter AND server = '$SERVER_ID' GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"All\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "All" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "alias": "Revives", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "revive", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Revives\" WHERE $timeFilter AND server = '$SERVER_ID' AND reviverTeamID = 1 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 1\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(time) AND\n victimTeamID = 1 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 1" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "victimTeamID", - "=", - "1" - ], - "type": "expression" - } - ] - }, - { - "alias": "Revives", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "revive", - "metricColumn": "none", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT count(\"victimName\") FROM \"DBLog_Revives\" WHERE $timeFilter AND server = '$SERVER_ID' AND reviverTeamID = 2 GROUP BY time($INTERVAL) fill(0)", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n count(id) AS \"Team 2\"\nFROM DBLog_Revives\nWHERE\n $__timeFilter(time) AND\n victimTeamID = 2 AND\n server = \"$SERVER_ID\"\nGROUP BY 1\nORDER BY $__timeGroup(time,$__interval)", - "refId": "C", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Team 2" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Revives", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - }, - { - "datatype": "int", - "name": "", - "params": [ - "victimTeamID", - "=", - "2" - ], - "type": "expression" - } - ] - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Revives Per Minutes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "columns": [], - "datasource": { - "uid": "MySQL" - }, - "fontSize": "100%", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 83 - }, - "id": 22, - "showHeader": true, - "sort": { - "col": 2, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "align": "auto", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "", - "align": "auto", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n layerClassname as \"Layer\",\n startTime AS \"Start Time\",\n endTime AS \"End Time\"\nFROM `DBLog_Matches`\nWHERE \n (\n $__timeFilter(startTime) OR \n $__timeFilter(endTime) OR\n endTime IS NULL\n ) AND \n server = $SERVER_ID \nORDER BY startTime DESC;", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Matches", - "transform": "table", - "type": "table-old" - }, - { - "datasource": { - "type": "mysql", - "uid": "thqbOjq7z" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 12, - "y": 83 - }, - "id": 25, - "links": [], - "maxDataPoints": 3, - "options": { - "legend": { - "displayMode": "list", - "placement": "bottom" - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "9.0.2", - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [], - "metricColumn": "layer", - "rawQuery": true, - "rawSql": "SELECT\n layerClassname AS metric,\n COUNT(*),\n startTime AS time\nFROM `DBLog_Matches`\nWHERE\n (\n $__timeFilter(startTime) OR \n $__timeFilter(endTime) OR\n endTime IS NULL\n ) AND \n server = $SERVER_ID \nGROUP BY layerClassname\nORDER BY time ASC", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "game", - "timeColumn": "startTime", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Layers", - "type": "piechart" - }, - { - "datasource": { - "type": "mysql", - "uid": "thqbOjq7z" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 0, - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 18, - "y": 83 - }, - "id": 24, - "links": [], - "maxDataPoints": 3, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "values": [ - "value", - "percent" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [], - "metricColumn": "layer", - "rawQuery": true, - "rawSql": "SELECT\n mapClassname AS metric,\n COUNT(*),\n startTime AS time\nFROM `DBLog_Matches`\nWHERE\n (\n $__timeFilter(startTime) OR \n $__timeFilter(endTime) OR\n endTime IS NULL\n ) AND \n server = $SERVER_ID \nGROUP BY mapClassname\nORDER BY time ASC", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "game", - "timeColumn": "startTime", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Maps", - "type": "piechart" - }, - { - "datasource": { - "type": "mysql", - "uid": "thqbOjq7z" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 0, - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 91 - }, - "id": 33, - "links": [], - "maxDataPoints": 3, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "values": [ - "value", - "percent" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "7.1.0", - "targets": [ - { - "alias": "Non-Teamkills", - "datasource": { - "uid": "MySQL" - }, - "format": "time_series", - "group": [ - { - "params": [ - "$__interval", - "none" - ], - "type": "time" - } - ], - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "null" - ], - "type": "fill" - } - ], - "metricColumn": "weapon", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT \n COUNT(\"victimName\") \nFROM \"DBLog_Wounds\" \nWHERE \n $timeFilter AND\n server = '$SERVER_ID' AND\n teamkill = false", - "rawQuery": true, - "rawSql": "SELECT\n $__timeGroupAlias(time,$__interval),\n weapon AS metric,\n count(id) AS \"Wounds\"\nFROM DBLog_Wounds\nWHERE\n $__timeFilter(time) AND\n server = \"$SERVER_ID\"\nGROUP BY 2\nORDER BY time ASC\n", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - }, - { - "params": [ - "count" - ], - "type": "aggregate" - }, - { - "params": [ - "Wounds" - ], - "type": "alias" - } - ] - ], - "table": "DBLog_Wounds", - "tags": [], - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Weapons", - "type": "piechart" - }, - { - "alignNumbersToRightEnabled": true, - "columnAliases": [], - "columnFiltersEnabled": false, - "columnWidthHints": [], - "columns": [], - "compactRowsEnabled": false, - "datasource": { - "uid": "MySQL" - }, - "datatablePagingType": "simple_numbers", - "datatableTheme": "basic_theme", - "description": "", - "emptyData": false, - "fontSize": "100%", - "gridPos": { - "h": 10, - "w": 12, - "x": 12, - "y": 91 - }, - "hoverEnabled": true, - "id": 36, - "infoEnabled": true, - "lengthChangeEnabled": true, - "orderColumnEnabled": true, - "pagingTypes": [ - { - "$$hashKey": "object:227", - "text": "Page number buttons only", - "value": "numbers" - }, - { - "$$hashKey": "object:228", - "text": "'Previous' and 'Next' buttons only", - "value": "simple" - }, - { - "$$hashKey": "object:229", - "text": "'Previous' and 'Next' buttons, plus page numbers", - "value": "simple_numbers" - }, - { - "$$hashKey": "object:230", - "text": "'First', 'Previous', 'Next' and 'Last' buttons", - "value": "full" - }, - { - "$$hashKey": "object:231", - "text": "'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers", - "value": "full_numbers" - }, - { - "$$hashKey": "object:232", - "text": "'First' and 'Last' buttons, plus page numbers", - "value": "first_last_numbers" - } - ], - "panelHeight": 322, - "pluginVersion": "7.1.1", - "rowNumbersEnabled": false, - "rowsPerPage": 20, - "scroll": true, - "scrollHeight": "default", - "searchEnabled": true, - "searchHighlightingEnabled": false, - "showCellBorders": false, - "showHeader": true, - "showRowBorders": true, - "sort": { - "col": 0, - "desc": true - }, - "sortByColumns": [ - { - "columnData": "Kills", - "sortMethod": "desc" - } - ], - "sortByColumnsData": [ - [ - 3, - "desc" - ] - ], - "stripedRowsEnabled": true, - "styles": [ - { - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "splitPattern": "/ /", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "datasource": { - "uid": "MySQL" - }, - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT DISTINCTROW\n layer,\n NTH_VALUE(LW.winner, 1) OVER (PARTITION BY LW.layer) AS \"Most Frequent Winner\",\n NTH_VALUE(LW.wins, 1) OVER (PARTITION BY LW.layer) AS \"Most Frequent Winner Wins\",\n (NTH_VALUE(LW.wins, 1) OVER (PARTITION BY LW.layer) / (NTH_VALUE(LW.wins, 1) OVER (PARTITION BY LW.layer) + NTH_VALUE(LW.wins, 2) OVER (PARTITION BY LW.layer))) * 100 AS \"Most Frequent Winner Win Percentage\",\n NTH_VALUE(LW.winner, 2) OVER (PARTITION BY LW.layer) AS \"Least Frequent Winner\",\n NTH_VALUE(LW.wins, 2) OVER (PARTITION BY LW.layer) AS \"Least Frequent Winner Count\",\n (NTH_VALUE(LW.wins, 2) OVER (PARTITION BY LW.layer) / (NTH_VALUE(LW.wins, 1) OVER (PARTITION BY LW.layer) + NTH_VALUE(LW.wins, 2) OVER (PARTITION BY LW.layer))) * 100 AS \"Least Frequent Winner Win Percentage\"\nFROM (\n SELECT\n M.layer,\n M.winner,\n COUNT(*) AS \"wins\"\n FROM dblog_matches M\n WHERE\n M.layer IS NOT NULL AND\n M.winner IS NOT NULL\n GROUP BY M.layer, M.winner\n ORDER BY wins DESC\n) LW;\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "DBLog_TickRates", - "timeColumn": "time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "themeOptions": { - "dark": "./styles/dark.scss", - "light": "./styles/light.scss" - }, - "themes": [ - { - "$$hashKey": "object:208", - "disabled": false, - "text": "Basic", - "value": "basic_theme" - }, - { - "$$hashKey": "object:209", - "disabled": true, - "text": "Bootstrap", - "value": "bootstrap_theme" - }, - { - "$$hashKey": "object:210", - "disabled": true, - "text": "Foundation", - "value": "foundation_theme" - }, - { - "$$hashKey": "object:211", - "disabled": true, - "text": "ThemeRoller", - "value": "themeroller_theme" - } - ], - "title": "Matches - Win/Loss Comparison", - "transform": "table", - "type": "briangann-datatable-panel" - } - ], - "refresh": false, - "schemaVersion": 36, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "current": { - "selected": true, - "text": "1", - "value": "1" - }, - "hide": 0, - "includeAll": false, - "label": "Server", - "multi": false, - "name": "SERVER_ID", - "options": [ - { - "selected": true, - "text": "1", - "value": "1" - }, - { - "selected": false, - "text": "2", - "value": "2" - } - ], - "query": "1,2,3,10", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - }, - { - "auto": false, - "auto_count": 30, - "auto_min": "10s", - "current": { - "selected": false, - "text": "1m", - "value": "1m" - }, - "hide": 0, - "label": "Interval", - "name": "INTERVAL", - "options": [ - { - "selected": true, - "text": "1m", - "value": "1m" - }, - { - "selected": false, - "text": "2m", - "value": "2m" - }, - { - "selected": false, - "text": "3m", - "value": "3m" - }, - { - "selected": false, - "text": "4m", - "value": "4m" - }, - { - "selected": false, - "text": "5m", - "value": "5m" - }, - { - "selected": false, - "text": "10m", - "value": "10m" - }, - { - "selected": false, - "text": "20m", - "value": "20m" - }, - { - "selected": false, - "text": "30m", - "value": "30m" - } - ], - "query": "1m,2m,3m,4m,5m,10m,20m,30m", - "refresh": 2, - "skipUrlSync": false, - "type": "interval" - } - ] - }, - "time": { - "from": "now-3h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "", - "title": "Squad Server Overview", - "uid": "n7Xl7jEWx", - "version": 2, - "weekStart": "" -} \ No newline at end of file diff --git a/squad-server/templates/config-template.json b/squad-server/templates/config-template.json deleted file mode 100644 index 6080d98c4..000000000 --- a/squad-server/templates/config-template.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "server": { - "id": 1, - "host": "xxx.xxx.xxx.xxx", - "queryPort": 27165, - "rconPort": 21114, - "rconPassword": "password", - "logReaderMode": "tail", - "logDir": "C:/path/to/squad/log/folder", - "ftp": { - "port": 21, - "user": "FTP Username", - "password": "FTP Password" - }, - "sftp": { - "host": "xxx.xxx.xxx.xxx", - "port": 21, - "username": "SFTP Username", - "password": "SFTP Password" - }, - "adminLists": [ - { - "type": "", - "source": "" - } - ] - }, - "connectors": { - "discord": "Discord Login Token", - "awnAPI": {"orgID":"YourOrgID", "creds": {"username":"AwnUsername", "password":"AwnPassword"}}, - "mysql": { - "host": "host", - "port": 3306, - "username": "squadjs", - "password": "password", - "database": "squadjs", - "dialect": "mysql" - }, - "sqlite": "sqlite:database.sqlite" - }, - "plugins": [], - "logger": { - "verboseness": { - "SquadServer": 1, - "LogParser": 1, - "RCON": 1 - }, - "colors": { - "SquadServer": "yellowBright", - "SquadServerFactory": "yellowBright", - "LogParser": "blueBright", - "RCON": "redBright" - } - } -} diff --git a/squad-server/templates/readme-template.md b/squad-server/templates/readme-template.md deleted file mode 100644 index d5b813170..000000000 --- a/squad-server/templates/readme-template.md +++ /dev/null @@ -1,289 +0,0 @@ -
    - -Logo -Logo - -#### SquadJS - -[![GitHub release](https://img.shields.io/github/release/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/releases) -[![GitHub contributors](https://img.shields.io/github/contributors/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/graphs/contributors) -[![GitHub release](https://img.shields.io/github/license/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/blob/master/LICENSE) - -
    - -[![GitHub issues](https://img.shields.io/github/issues/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/issues) -[![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/pulls) -[![GitHub issues](https://img.shields.io/github/stars/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/stargazers) -[![Discord](https://img.shields.io/discord/266210223406972928.svg?style=flat-square&logo=discord)](https://discord.gg/9F2Ng5C) - -

    -
    - -## **About** -SquadJS is a scripting framework, designed for Squad servers, that aims to handle all communication and data collection to and from the servers. Using SquadJS as the base to any of your scripting projects allows you to easily write complex plugins without having to worry about the hassle of RCON or log parsing. However, for your convenience SquadJS comes shipped with multiple plugins already built for you allowing you to experience the power of SquadJS right away. - -
    - -## **Using SquadJS** -SquadJS relies on being able to access the Squad server log directory in order to parse logs live to collect information. Thus, SquadJS must be hosted on the same server box as your Squad server or be connected to your Squad server via FTP. - -#### Prerequisites -* Git -* [Node.js](https://nodejs.org/en/) (14.x) - [Download](https://nodejs.org/en/) -* [Yarn](https://yarnpkg.com/) (Version 1.22.0+) - [Download](https://classic.yarnpkg.com/en/docs/install) -* Some plugins may have additional requirements. - -#### Installation -1. [Download SquadJS](https://github.com/Team-Silver-Sphere/SquadJS/releases/latest) and unzip the download. -2. Open the unzipped folder in your terminal. -3. Install the dependencies by running `yarn install` in your terminal. Due to the use of Yarn Workspaces it is important to use `yarn install` and **not** `npm install` as this will not work and will break stuff. -4. Configure the `config.json` file. See below for more details. -5. Start SquadJS by running `node index.js` in your terminal. - -**Note** - If you are interested in testing versions of SquadJS not yet released please download/clone the `master` branch. Please also see [here](#versions-and-releases) for more information on our versions and release procedures. - -
    - -## **Configuring SquadJS** -SquadJS can be configured via a JSON configuration file which, by default, is located in the SquadJS and is named [config.json](./config.json). - -The config file needs to be valid JSON syntax. If an error is thrown saying the config cannot be parsed then try putting the config into a JSON syntax checker (there's plenty to choose from that can be found via Google). - -
    - Server - -## Server Configuration - -The following section of the configuration contains information about your Squad server. - - ```json - "server": { - "id": 1, - "host": "xxx.xxx.xxx.xxx", - "queryPort": 27165, - "rconPort": 21114, - "rconPassword": "password", - "logReaderMode": "tail", - "logDir": "C:/path/to/squad/log/folder", - "ftp": { - "port": 21, - "user": "FTP Username", - "password": "FTP Password" - }, - "sftp": { - "host": "xxx.xxx.xxx.xxx", - "port": 21, - "username": "SFTP Username", - "password": "SFTP Password" - }, - "adminLists": [ - { - "type": "local", - "source": "C:/Users/Administrator/Desktop/Servers/sq_arty_party/SquadGame/ServerConfig/Admins.cfg", - }, - { - "type": "remote", - "source": "http://yourWebsite.com/Server1/Admins.cfg", - } - ] - }, - ``` -* `id` - An integer ID to uniquely identify the server. -* `host` - The IP of the server. -* `queryPort` - The query port of the server. -* `rconPort` - The RCON port of the server. -* `rconPassword` - The RCON password of the server. -* `logReaderMode` - `tail` will read from a local log file. `ftp` will read from a remote log file using the FTP protocol. -* `logDir` - The folder where your Squad logs are saved. Most likely will be `C:/servers/squad_server/SquadGame/Saved/Logs`. -* `ftp:port` - The FTP port of the server. Only required for `ftp` `logReaderMode`. -* `ftp:user` - The FTP user of the server. Only required for `ftp` `logReaderMode`. -* `ftp:password` - The FTP password of the server. Only required for `ftp` `logReaderMode`. -* `adminLists` - Sources for identifying an admins on the server, either remote or local. - - --- -
    - - -
    - Connectors - -## Connector Configuration - -Connectors allow SquadJS to communicate with external resources. - ```json - "connectors": { - "discord": "Discord Login Token", - }, - ``` -Connectors should be named, for example the above is named `discord`, and should have the associated config against it. Configs can be specified by name in plugin options. Should a connector not be needed by any plugin then the default values can be left or you can remove it from your config file. - -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. - - -##### 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" - } - ``` - -See [Sequelize's documentation](https://sequelize.org/master/manual/getting-started.html#connecting-to-a-database) for more details. - - --- -
    - -
    - Plugins - -## Plugin Configuration - -The `plugins` section in your config file lists all plugins built into SquadJS - ```json - "plugins": [ - { - "plugin": "auto-tk-warn", - "disabled": false, - "message": "Please apologise for ALL TKs in ALL chat!" - } - ] - ``` - -The `disabled` field can be toggled between `true`/ `false` to enabled/disable the plugin. - -Plugin options are also specified. A full list of plugin options can be seen below. - - --- -
    - -
    - Verboseness - -## Console Output Configuration - -The `logger` section configures how verbose a module of SquadJS will be as well as the displayed color. - ```json - "logger": { - "verboseness": { - "SquadServer": 1, - "LogParser": 1, - "RCON": 1 - }, - "colors": { - "SquadServer": "yellowBright", - "SquadServerFactory": "yellowBright", - "LogParser": "blueBright", - "RCON": "redBright" - } - } - ``` -The larger the number set in the `verboseness` section for a specified module the more it will print to the console. - - --- -
    - -
    - -## **Plugins** -The following is a list of plugins built into SquadJS, you can click their title for more information: - -Interested in creating your own plugin? [See more here](./squad-server/plugins/readme.md) - -//PLUGIN-INFO// - -
    - -## Statement on Accuracy -Some information SquadJS collects from Squad servers was never intended or designed to be collected. As a result, it is impossible for any framework to collect the same information with 100% accuracy. SquadJS aims to get as close as possible to that figure, however, it acknowledges that this is not possible in some specific scenarios. - -Below is a list of scenarios we know may cause some information to be inaccurate: -* Use of Realtime Server and Player Information - We update server and player information periodically every 30 seconds (by default) or when we know that it requires an update. As a result, some information about the server or players may be up to 30 seconds out of date or greater if an error occurs whilst updating this information. -* SquadJS Restarts - If SquadJS is started during an active Squad game some information will be lost or not collected correctly: - - The current state of players will be lost. For example, if a player was wounded prior to the bot starting and then is revived/gives up after the bot is started information regarding who originally wounded them will not be known. - - The accurate collection of some server log events will not occur. SquadJS collects players' "suffix" name, i.e. their Steam name without the clan tag added via the game settings, when they join the server and uses this to identify them in certain logs that do not include their full name. As a result, for players connecting prior to SquadJS starting some log events associated with their actions will show the player as `null`. -* Duplicated Player Names - If two or more players have the same name or suffix name (see above) then SquadJS will be unable to identify them in the logs. When this occurs event logs will show the player as `null`. Be on the watch for groups of players who try to abuse this in order to TK or complete other malicious actions without being detected by SquadJS plugins. - -## SquadJS API -SquadJS pings the following data to the [SquadJS API](https://github.com/Team-Silver-Sphere/SquadJS-API/) at regular intervals to assist with its development: -* Squad server IP, query port, name & player count (including queue size). -* SquadJS version. -* Log reader mode, i.e. `tail` or `ftp`. -* Plugin configuration. - -At this time, this cannot be disabled. - -Please note, plugin configurations do **not** and should **not** contain any sensitive information which allows us to collect this information. Any sensitive information, e.g. Discord login tokens, should be included in the `connectors` section of the config which is not sent to our API. It is important that developers of custom plugins maintain this approach to avoid submitting confidential information to our API. - -## Versions and Releases -Whilst installing SquadJS you may do the following to obtain slightly different versions: -* Download the [latest release](https://github.com/Team-Silver-Sphere/SquadJS/releases/latest) - To get the latest **stable** version of SquadJS. -* Download/clone the [`master` branch](https://github.com/Team-Silver-Sphere/SquadJS/) - To get the most up to date version of SquadJS. - -All changes proposed to SquadJS will be merged into the `master` branch prior to being released in the next stable version to allow for a period of larger-scale testing to occur. Therefore, we only recommend individuals who are willing to update regularly and partake in testing/bug reporting use the `master` branch. Please note, updates to the `master` branch will not be advertised in the SquadJS startup information, however, notifications of merged pull requests into the `master` branch may be found in our [Discord](https://discord.gg/9F2Ng5C). Once the `master` branch is deemed stable a release will be published and advertised via the SquadJS startup information and our [Discord](https://discord.gg/9F2Ng5C). - -Releases will be given a version number with the format `v{major}.{minor}.{patch}`, e.g. `v3.1.4`. Changes to `{major}`/`{minor}`/`{patch}` will imply the following: -* `{major}` - The release contains a new/updated feature that is (potentially) breaking, e.g. changes to event outputs that may cause custom plugins to break. -* `{minor}` - The release contains a new/updated feature. -* `{patch}` - The release contains a bug fix. - -Please note, `{minor}`/`{patch}` releases may still break SquadJS installations, however, this may be prevented with configuration changes and should not require custom plugins to be updated. - -Release version numbers and changelogs are managed by [Release Drafter](https://github.com/marketplace/actions/release-drafter) which relies on the appropriate labels being applied to pull requests. Version numbers are updated in the `package.json` file manually prior to publishing the release draft. - -The above policy was written and put into effect after the release of SquadJS v2.0.5. A major version bump to SquadJS v3.0.0 was made to signify this policy taking affect and to draw a line under the previous poor management of releases and version numbers. - -## Credits -SquadJS would not be possible without the support of so many individuals and organisations. Our thanks goes out to: -* [SquadJS's contributors](https://github.com/Team-Silver-Sphere/SquadJS/graphs/contributors). -* [Thomas Smyth's GitHub sponsors](https://github.com/sponsors/Thomas-Smyth). -* subtlerod for proposing the initial log parsing idea, helping to design the log parsing process and for providing multiple servers to test with. -* Shanomac99 and the rest of the Squad Wiki team for providing us with [layer information](https://github.com/Squad-Wiki-Editorial/squad-wiki-pipeline-map-data). -* Fourleaf, Mex, various members of ToG / ToG-L and others that helped to stage logs and participate in small scale tests. -* Various Squad servers/communities for participating in larger scale tests and for providing feedback on plugins. -* Everyone in the [Squad RCON Discord](https://discord.gg/9F2Ng5C) and others who have submitted bug reports, suggestions, feedback and provided logs. - -## License -``` -Boost Software License - Version 1.0 - August 17th, 2003 - -Copyright (c) 2020 Thomas Smyth - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -``` diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..567aa6ec4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "allowJs": true + } +} \ No newline at end of file From 151d2f8050c976f44ccfa35635795445156491c8 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Thu, 1 Aug 2024 17:23:49 +0100 Subject: [PATCH 02/28] Refactor config system --- core/logger.js | 19 ++++++---- index.ts | 7 ++-- squad-server/factory.js | 59 ------------------------------ src/config-system/config-system.ts | 47 ++++++++++++++++++++++++ src/config-system/index.ts | 3 ++ src/types/config.ts | 10 +++++ src/types/index.ts | 4 ++ src/types/squad-server-config.ts | 1 + tsconfig.json | 3 +- 9 files changed, 82 insertions(+), 71 deletions(-) delete mode 100644 squad-server/factory.js create mode 100644 src/config-system/config-system.ts create mode 100644 src/config-system/index.ts create mode 100644 src/types/config.ts create mode 100644 src/types/index.ts create mode 100644 src/types/squad-server-config.ts diff --git a/core/logger.js b/core/logger.js index 564cf208a..4bae766cc 100644 --- a/core/logger.js +++ b/core/logger.js @@ -20,16 +20,19 @@ class Logger { ); } - setVerboseness(module, verboseness) { - this.verboseness[module] = verboseness; - } + setConfig(config = {}) { + // Set verboseness levels for the different modules. + for (const [module, verboseness] of Object.entries(config.verboseness || {})) { + this.verboseness[module] = verboseness; + } - setColor(module, color) { - this.colors[module] = color; - } + // Set the colours for the different modules. + for (const [module, color] of Object.entries(config.colors || {})) { + this.colors[module] = color; + } - setTimeStamps(option) { - this.includeTimestamps = option; + // Set the timestamp option. + this.includeTimestamps = config.timestamps; } } diff --git a/index.ts b/index.ts index 34457a4e2..1350a353e 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,7 @@ -import SquadServerFactory from 'squad-server/factory'; import printLogo from 'squad-server/logo'; +import { ConfigSystem } from './src/config-system'; + async function main() { // Print the SquadJS logo. await printLogo(); @@ -16,8 +17,8 @@ async function main() { // Create a SquadServer instance. const server = config - ? await SquadServerFactory.buildFromConfigString(config) - : await SquadServerFactory.buildFromConfigFile(configPath || './config.json'); + ? await ConfigSystem.buildFromConfigString(config) + : await ConfigSystem.buildFromConfigFile(configPath || './config.json'); // Watch the server. await server.watch(); diff --git a/squad-server/factory.js b/squad-server/factory.js deleted file mode 100644 index 0a17b3e64..000000000 --- a/squad-server/factory.js +++ /dev/null @@ -1,59 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -import Logger from 'core/logger'; - -import SquadServer from './index.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -export default class SquadServerFactory { - static async buildFromConfig(config) { - Logger.setTimeStamps(config.logger.timestamps ? config.logger.timestamps : false); - - // setup logging levels - for (const [module, verboseness] of Object.entries(config.logger.verboseness)) { - Logger.setVerboseness(module, verboseness); - } - - for (const [module, color] of Object.entries(config.logger.colors)) { - Logger.setColor(module, color); - } - - // create SquadServer - Logger.verbose('SquadServerFactory', 1, 'Creating SquadServer...'); - return new SquadServer(config.server); - } - - static parseConfig(configString) { - try { - return JSON.parse(configString); - } catch (err) { - throw new Error('Unable to parse config file.'); - } - } - - static buildFromConfigString(configString) { - Logger.verbose('SquadServerFactory', 1, 'Parsing config string...'); - return SquadServerFactory.buildFromConfig(SquadServerFactory.parseConfig(configString)); - } - - static buildFromConfigFile(configPath) { - Logger.verbose('SquadServerFactory', 1, 'Reading config file...'); - - // Make the config path relevant to root directory. - configPath = path.resolve(__dirname, '../', configPath); - - // Check the config file exists. - if (!fs.readFileSync(configPath)) { - throw new Error('Config file does not exist.'); - } - - // Read the config file. - const configString = fs.readFileSync(configPath, 'utf-8'); - - // Build the SquadServer from the config string. - return SquadServerFactory.buildFromConfigString(configString); - } -} diff --git a/src/config-system/config-system.ts b/src/config-system/config-system.ts new file mode 100644 index 000000000..12d9ce436 --- /dev/null +++ b/src/config-system/config-system.ts @@ -0,0 +1,47 @@ +import Logger from 'core/logger'; +import fs from 'fs'; + +import SquadServer from '../../squad-server'; +import { Config } from '../types'; + +export class ConfigSystem { + static async buildFromConfig(config: Config): Promise { + Logger.verbose('SquadServerFactory', 1, 'Creating SquadServer...'); + + // Set the logger options. + Logger.setConfig(config.logger); + + // Create the SquadServer. + return new SquadServer(config.server); + } + + static buildFromConfigString(configString: string): Promise { + Logger.verbose('SquadServerFactory', 1, 'Parsing config string...'); + + // Parse the config string. + let config: Config; + try { + config = JSON.parse(configString); + } catch (err) { + throw new Error('Unable to parse config file.'); + } + + // Create the SquadServer. + return ConfigSystem.buildFromConfig(config); + } + + static buildFromConfigFile(configPath: string): Promise { + Logger.verbose('SquadServerFactory', 1, 'Reading config file...'); + + // Check the config file exists. + if (!fs.readFileSync(configPath)) { + throw new Error('Config file does not exist.'); + } + + // Read the config file. + const configString: string = fs.readFileSync(configPath, 'utf-8'); + + // Create the SquadServer. + return ConfigSystem.buildFromConfigString(configString); + } +} \ No newline at end of file diff --git a/src/config-system/index.ts b/src/config-system/index.ts new file mode 100644 index 000000000..cfa27172c --- /dev/null +++ b/src/config-system/index.ts @@ -0,0 +1,3 @@ +import { ConfigSystem } from './config-system'; + +export { ConfigSystem } \ No newline at end of file diff --git a/src/types/config.ts b/src/types/config.ts new file mode 100644 index 000000000..ec7304610 --- /dev/null +++ b/src/types/config.ts @@ -0,0 +1,10 @@ +import { SquadServerConfig } from './squad-server-config'; + +export interface Config { + server: SquadServerConfig; + logger?: { + verboseness?: { [key: string]: number; }; + colors?: { [key: string]: number; }; + timestamps?: boolean; + } +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 000000000..58cc9ab56 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,4 @@ +import { Config } from './config.js'; +import { SquadServerConfig } from './squad-server-config.js'; + +export { Config, SquadServerConfig }; \ No newline at end of file diff --git a/src/types/squad-server-config.ts b/src/types/squad-server-config.ts new file mode 100644 index 000000000..8d924c61a --- /dev/null +++ b/src/types/squad-server-config.ts @@ -0,0 +1 @@ +export interface SquadServerConfig {} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 567aa6ec4..11c40c041 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { - "allowJs": true + "allowJs": true, + "esModuleInterop": true } } \ No newline at end of file From ce9f2ffd020cbe448857e7e1c22882da452d211b Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Thu, 1 Aug 2024 18:18:37 +0100 Subject: [PATCH 03/28] Add new plugin system --- index.ts | 4 ++++ plugins/squadjs-command/index.ts | 15 +++++++++++++++ squad-server/index.js | 23 ++++++++++++++++------- src/plugin-system/index.ts | 4 ++++ src/plugin-system/plugin-system.ts | 26 ++++++++++++++++++++++++++ src/plugin-system/plugin.ts | 14 ++++++++++++++ 6 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 plugins/squadjs-command/index.ts create mode 100644 src/plugin-system/index.ts create mode 100644 src/plugin-system/plugin-system.ts create mode 100644 src/plugin-system/plugin.ts diff --git a/index.ts b/index.ts index 1350a353e..29d92a148 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,7 @@ import printLogo from 'squad-server/logo'; import { ConfigSystem } from './src/config-system'; +import { PluginSystem } from './src/plugin-system'; async function main() { // Print the SquadJS logo. @@ -20,6 +21,9 @@ async function main() { ? await ConfigSystem.buildFromConfigString(config) : await ConfigSystem.buildFromConfigFile(configPath || './config.json'); + // Load the installed plugins. + await PluginSystem.loadPlugins(server); + // Watch the server. await server.watch(); } diff --git a/plugins/squadjs-command/index.ts b/plugins/squadjs-command/index.ts new file mode 100644 index 000000000..e818853d5 --- /dev/null +++ b/plugins/squadjs-command/index.ts @@ -0,0 +1,15 @@ +import { Plugin } from '../../src/plugin-system'; + +export default class SquadJSCommand extends Plugin { + async onChatMessage(data: any): Promise { + console.log(data); + + // Check whether the message contained the SquadJS command. + const command = data.message.match(/!squadjs/); + + // Send the response to the command. + if (command) { + await this.server.rcon.warn(data.player.eosID, 'This server is running SquadJS!'); + } + } +} \ No newline at end of file diff --git a/squad-server/index.js b/squad-server/index.js index 8f93f5c7a..d17dbb76c 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -58,6 +58,8 @@ export default class SquadServer extends EventEmitter { this.pingSquadJSAPI = this.pingSquadJSAPI.bind(this); this.pingSquadJSAPIInterval = 5 * 60 * 1000; this.pingSquadJSAPITimeout = null; + + this.plugins = []; } async watch() { @@ -97,16 +99,15 @@ export default class SquadServer extends EventEmitter { autoReconnectInterval: this.options.rconAutoReconnectInterval }); + // Handle chat messages. this.rcon.on('CHAT_MESSAGE', async (data) => { + // Get additional data. data.player = await this.getPlayerByEOSID(data.eosID); - this.emit('CHAT_MESSAGE', data); - const command = data.message.match(/!([^ ]+) ?(.*)/); - if (command) - this.emit(`CHAT_COMMAND:${command[1].toLowerCase()}`, { - ...data, - message: command[2].trim() - }); + // Inform plugins of event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onChatMessage(data)) + ); }); this.rcon.on('POSSESSED_ADMIN_CAMERA', async (data) => { @@ -729,4 +730,12 @@ export default class SquadServer extends EventEmitter { getMatchStartTimeByPlaytime(playtime) { return new Date(Date.now() - +playtime * 1000); } + + async mountPlugin(plugin) { + // Tell the plugin it is being mounted. + await plugin.mount(); + + // Add the plugin to the list of mounted plugins. + this.plugins.push(plugin); + } } diff --git a/src/plugin-system/index.ts b/src/plugin-system/index.ts new file mode 100644 index 000000000..b70489a47 --- /dev/null +++ b/src/plugin-system/index.ts @@ -0,0 +1,4 @@ +import { Plugin } from './plugin'; +import { PluginSystem } from './plugin-system'; + +export { Plugin, PluginSystem }; \ No newline at end of file diff --git a/src/plugin-system/plugin-system.ts b/src/plugin-system/plugin-system.ts new file mode 100644 index 000000000..d89350202 --- /dev/null +++ b/src/plugin-system/plugin-system.ts @@ -0,0 +1,26 @@ +import fs from 'fs/promises'; +import path from 'path'; +import SquadServer from 'squad-server'; +import { Plugin } from './plugin'; + +export class PluginSystem { + public static async loadPlugins(server: SquadServer): Promise { + // Get a list of plugin folders. + const pluginFolders = await fs.readdir('./plugins'); + + // Loop over each plugin folder. + for (const pluginFolder of pluginFolders) { + // Get the path to the plugin. + const pluginPath: string = path.resolve('./plugins', pluginFolder, 'index.ts'); + + // Import the plugin. + const { default: ImportedPlugin }: { default: typeof Plugin } = await import(`file://${pluginPath}`); + + // Initiate the plugin. + const plugin = new ImportedPlugin(server); + + // Mount the new plugin. + await server.mountPlugin(plugin); + } + } +} \ No newline at end of file diff --git a/src/plugin-system/plugin.ts b/src/plugin-system/plugin.ts new file mode 100644 index 000000000..19b514608 --- /dev/null +++ b/src/plugin-system/plugin.ts @@ -0,0 +1,14 @@ +import SquadServer from 'squad-server'; + +export class Plugin { + protected server: SquadServer; + + public constructor(server: SquadServer) { + // Store a reference to the server so the plugin can access it. + this.server = server; + } + + public async mount() {} + + public async onChatMessage(data: object): Promise {} +} \ No newline at end of file From 7fca40b73907f2264304b76b3764d82040ac3b17 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Thu, 1 Aug 2024 18:23:16 +0100 Subject: [PATCH 04/28] Remove plugins from the SquadJS API ping data --- squad-server/index.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/squad-server/index.js b/squad-server/index.js index d17dbb76c..f1146734a 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -695,13 +695,7 @@ export default class SquadServer extends EventEmitter { // Send information about SquadJS. squadjs: { version: SQUADJS_VERSION, - logReaderMode: this.options.logReaderMode, - - // Send the plugin config so we can see what plugins they're using (none of the config is sensitive). - plugins: this.plugins.map((plugin) => ({ - ...plugin.rawOptions, - plugin: plugin.constructor.name - })) + logReaderMode: this.options.logReaderMode } }; From 343e0c37a3c6ec40a8660d56808ffd0387b10432 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Thu, 1 Aug 2024 23:23:19 +0100 Subject: [PATCH 05/28] Add dependency to demo --- plugins/squadjs-command/index.ts | 8 ++++++-- plugins/squadjs-command/package.json | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 plugins/squadjs-command/package.json diff --git a/plugins/squadjs-command/index.ts b/plugins/squadjs-command/index.ts index e818853d5..1ea02bbd2 100644 --- a/plugins/squadjs-command/index.ts +++ b/plugins/squadjs-command/index.ts @@ -1,14 +1,18 @@ +import winston from 'winston'; import { Plugin } from '../../src/plugin-system'; +const logger: winston.Logger = winston.createLogger({ + transports: [new winston.transports.Console()] +}); + export default class SquadJSCommand extends Plugin { async onChatMessage(data: any): Promise { - console.log(data); - // Check whether the message contained the SquadJS command. const command = data.message.match(/!squadjs/); // Send the response to the command. if (command) { + logger.info(`${data.player.name} called the !squadjs command.`); await this.server.rcon.warn(data.player.eosID, 'This server is running SquadJS!'); } } diff --git a/plugins/squadjs-command/package.json b/plugins/squadjs-command/package.json new file mode 100644 index 000000000..835cb7ee5 --- /dev/null +++ b/plugins/squadjs-command/package.json @@ -0,0 +1,9 @@ +{ + "name": "squadjs-command", + "version": "1.0.0", + "type": "module", + "private": true, + "dependencies": { + "winston": "^3.13.1" + } +} From e8d1142f57e45d2242f11a710f7f5c136316eeea Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Thu, 1 Aug 2024 23:24:29 +0100 Subject: [PATCH 06/28] Add some additional comments --- plugins/squadjs-command/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/squadjs-command/index.ts b/plugins/squadjs-command/index.ts index 1ea02bbd2..80798b273 100644 --- a/plugins/squadjs-command/index.ts +++ b/plugins/squadjs-command/index.ts @@ -1,18 +1,23 @@ import winston from 'winston'; import { Plugin } from '../../src/plugin-system'; +// Create an instance of winston. const logger: winston.Logger = winston.createLogger({ transports: [new winston.transports.Console()] }); +// Define the plugin. export default class SquadJSCommand extends Plugin { async onChatMessage(data: any): Promise { // Check whether the message contained the SquadJS command. const command = data.message.match(/!squadjs/); - // Send the response to the command. + // Handle uses of the command. if (command) { + // Log that the command was used. logger.info(`${data.player.name} called the !squadjs command.`); + + // Send the response to the command. await this.server.rcon.warn(data.player.eosID, 'This server is running SquadJS!'); } } From fbba1294617f9e92d4fa4c0fde2a2b7cd8b4e2e2 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Thu, 1 Aug 2024 23:26:03 +0100 Subject: [PATCH 07/28] Remove unused packages --- squad-server/package.json | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/squad-server/package.json b/squad-server/package.json index c7d8104c2..1b1f03e02 100644 --- a/squad-server/package.json +++ b/squad-server/package.json @@ -5,25 +5,11 @@ "dependencies": { "axios": "^0.21.1", "core": "1.0.0", - "didyoumean": "^1.2.1", - "discord.js": "^12.3.1", - "gamedig": "^2.0.20", - "graphql": "^15.4.0", - "graphql-request": "^3.4.0", - "mariadb": "^2.5.1", - "mysql2": "^2.2.5", - "pg": "^8.5.1", - "pg-hstore": "^2.3.3", - "sequelize": "^6.3.5", - "socket.io": "^4.5.4", - "sqlite3": "^5.0.0", - "tedious": "^15.1.2", - "tinygradient": "^1.1.2" + "gamedig": "^2.0.20" }, "exports": { ".": "./index.js", "./factory": "./factory.js", - "./logo": "./utils/print-logo.js", - "./plugins": "./plugins/index.js" + "./logo": "./utils/print-logo.js" } } From 91d348af82817832f49b089bddd7fd3a6e235eaf Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Fri, 2 Aug 2024 00:09:30 +0100 Subject: [PATCH 08/28] Convert other event types --- squad-server/index.js | 205 ++++++++++++++++++++++++++++-------- src/plugin-system/plugin.ts | 36 +++++++ 2 files changed, 195 insertions(+), 46 deletions(-) diff --git a/squad-server/index.js b/squad-server/index.js index f1146734a..b66feffc0 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -1,5 +1,3 @@ -import EventEmitter from 'events'; - import axios from 'axios'; import Logger from 'core/logger'; @@ -16,10 +14,8 @@ import fetchAdminLists from './utils/admin-lists.js'; import { isPlayerID, anyIDToPlayer, anyIDsToPlayers } from './utils/any-id.js'; import { playerIdNames } from 'core/id-parser'; -export default class SquadServer extends EventEmitter { +export default class SquadServer { constructor(options = {}) { - super(); - for (const option of ['host']) if (!(option in options)) throw new Error(`${option} must be specified.`); @@ -111,56 +107,81 @@ export default class SquadServer extends EventEmitter { }); this.rcon.on('POSSESSED_ADMIN_CAMERA', async (data) => { + // Get additional data. data.player = await this.getPlayerByEOSID(data.eosID); + // Record what time the admin entered the admin camera. this.adminsInAdminCam[data.eosID] = data.time; - this.emit('POSSESSED_ADMIN_CAMERA', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerPossessAdminCamera(data)) + ); }); this.rcon.on('UNPOSSESSED_ADMIN_CAMERA', async (data) => { + // Get additional data. data.player = await this.getPlayerByEOSID(data.eosID); + if (this.adminsInAdminCam[data.eosID]) { data.duration = data.time.getTime() - this.adminsInAdminCam[data.eosID].getTime(); } else { data.duration = 0; } + // Remove the admin from the list of admins currently in the admin camera. delete this.adminsInAdminCam[data.eosID]; - this.emit('UNPOSSESSED_ADMIN_CAMERA', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerUnPossessAdminCamera(data)) + ); }); - this.rcon.on('RCON_ERROR', (data) => { - this.emit('RCON_ERROR', data); + this.rcon.on('RCON_ERROR', async (data) => { + // Inform the plugins of the event. + await Promise.allSettled(this.plugins.map(async (plugin) => await plugin.onRconError(data))); }); this.rcon.on('PLAYER_WARNED', async (data) => { + // Get additional data. data.player = await this.getPlayerByName(data.name); - this.emit('PLAYER_WARNED', data); + // Inform the plugins of the event. + await Promise.allSettled(this.plugins.map(async (plugin) => await plugin.onPlayerWarn(data))); }); this.rcon.on('PLAYER_KICKED', async (data) => { + // Get additional data. data.player = await this.getPlayerByEOSID(data.eosID); - this.emit('PLAYER_KICKED', data); + // Inform the plugins of the event. + await Promise.allSettled(this.plugins.map(async (plugin) => await plugin.onPlayerKick(data))); }); this.rcon.on('PLAYER_BANNED', async (data) => { + // Get additional data. data.player = await this.getPlayerByEOSID(data.eosID); - this.emit('PLAYER_BANNED', data); + // Inform the plugins of the event. + await Promise.allSettled(this.plugins.map(async (plugin) => await plugin.onPlayerBan(data))); }); this.rcon.on('SQUAD_CREATED', async (data) => { + // Get additional player data. data.player = await this.getPlayerByEOSID(data.playerEOSID, true); data.player.squadID = data.squadID; + // Delete player's name from data. delete data.playerName; + + // Delete player's ids from data? for (const k in data) if (k.startsWith('player') && k.endsWith('ID')) delete data[k]; - this.emit('SQUAD_CREATED', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onSquadCreate(data)) + ); }); } @@ -184,54 +205,83 @@ export default class SquadServer extends EventEmitter { ftp: this.options.ftp }); - this.logParser.on('ADMIN_BROADCAST', (data) => { - this.emit('ADMIN_BROADCAST', data); + this.logParser.on('ADMIN_BROADCAST', async (data) => { + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onAdminBroadcast(data)) + ); }); this.logParser.on('DEPLOYABLE_DAMAGED', async (data) => { + // Get additional data. data.player = await this.getPlayerByNameSuffix(data.playerSuffix); + // Delete player's suffix from data. delete data.playerSuffix; - this.emit('DEPLOYABLE_DAMAGED', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onDeployableDamage(data)) + ); }); this.logParser.on('NEW_GAME', async (data) => { + // Get additional data. data.layer = await Layers.getLayerByClassname(data.layerClassname); + // Update layer history. this.layerHistory.unshift({ layer: data.layer, time: data.time }); this.layerHistory = this.layerHistory.slice(0, this.layerHistoryMaxLength); + // Update the current layer. this.currentLayer = data.layer; + + // Update admin lists. await this.updateAdmins(); - this.emit('NEW_GAME', data); + + // Inform the plugins of the event. + await Promise.allSettled(this.plugins.map(async (plugin) => await plugin.onRoundStart(data))); }); this.logParser.on('JOIN_SUCCEEDED', async (data) => { + // Log that a player connected. Logger.verbose( 'SquadServer', 1, `Player connected ${data.playerSuffix} - SteamID: ${data.steamID} - EOSID: ${data.eosID} - IP: ${data.ip}` ); + // Get additional data. data.player = await this.getPlayerByEOSID(data.eosID); if (data.player) data.player.suffix = data.playerSuffix; - for (const k in data) if (playerIdNames.includes(k)) delete data[k]; + // Delete the player's suffix from the data. delete data.playerSuffix; - this.emit('PLAYER_CONNECTED', data); + // Delete the player's ids from the data. + for (const k in data) if (playerIdNames.includes(k)) delete data[k]; + + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerConnection(data)) + ); }); this.logParser.on('PLAYER_DISCONNECTED', async (data) => { + // Get additional data. data.player = await this.getPlayerByEOSID(data.eosID); + // Delete player's ids from data. for (const k in data) if (playerIdNames.includes(k)) delete data[k]; - this.emit('PLAYER_DISCONNECTED', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerDisconnection(data)) + ); }); this.logParser.on('PLAYER_DAMAGED', async (data) => { + // Get additional data. data.victim = await this.getPlayerByName(data.victimName); data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); @@ -243,15 +293,21 @@ export default class SquadServer extends EventEmitter { data.victim.teamID === data.attacker.teamID && data.victim.eosID !== data.attacker.eosID; } + // Delete players' names from data. delete data.victimName; delete data.attackerName; - this.emit('PLAYER_DAMAGED', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerDamage(data)) + ); }); this.logParser.on('PLAYER_WOUNDED', async (data) => { + // Get additional data. data.victim = await this.getPlayerByName(data.victimName); data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); + if (!data.attacker) data.attacker = await this.getPlayerByController(data.attackerPlayerController); @@ -259,16 +315,28 @@ export default class SquadServer extends EventEmitter { data.teamkill = data.victim.teamID === data.attacker.teamID && data.victim.eosID !== data.attacker.eosID; + // Delete players' names from data. delete data.victimName; delete data.attackerName; - this.emit('PLAYER_WOUNDED', data); - if (data.teamkill) this.emit('TEAMKILL', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerWound(data)) + ); + + if (data.teamkill) { + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerTeamkill(data)) + ); + } }); this.logParser.on('PLAYER_DIED', async (data) => { + // Get additional data. data.victim = await this.getPlayerByName(data.victimName); data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); + if (!data.attacker) data.attacker = await this.getPlayerByController(data.attackerPlayerController); @@ -276,47 +344,68 @@ export default class SquadServer extends EventEmitter { data.teamkill = data.victim.teamID === data.attacker.teamID && data.victim.eosID !== data.attacker.eosID; + // Delete players' names from data. delete data.victimName; delete data.attackerName; - this.emit('PLAYER_DIED', data); + // Inform the plugins of the event. + await Promise.allSettled(this.plugins.map(async (plugin) => await plugin.onPlayerDie(data))); }); this.logParser.on('PLAYER_REVIVED', async (data) => { + // Get additional data. data.victim = await this.getPlayerByEOSID(data.victimEOSID); data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); data.reviver = await this.getPlayerByEOSID(data.reviverEOSID); + // Delete players' names from data. delete data.victimName; delete data.attackerName; delete data.reviverName; - this.emit('PLAYER_REVIVED', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerRevive(data)) + ); }); this.logParser.on('PLAYER_POSSESS', async (data) => { + // Get additional data. data.player = await this.getPlayerByEOSID(data.playerEOSID); if (data.player) data.player.possessClassname = data.possessClassname; + // Delete player's suffix from data. delete data.playerSuffix; - this.emit('PLAYER_POSSESS', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerPossess(data)) + ); }); this.logParser.on('PLAYER_UNPOSSESS', async (data) => { + // Get additional data. data.player = await this.getPlayerByEOSID(data.playerEOSID); + // Delete player's suffix from data. delete data.playerSuffix; - this.emit('PLAYER_UNPOSSESS', data); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerUnPossess(data)) + ); }); this.logParser.on('ROUND_ENDED', async (data) => { - this.emit('ROUND_ENDED', data); + // Inform the plugins of the event. + await Promise.allSettled(this.plugins.map(async (plugin) => await plugin.onRoundEnd(data))); }); - this.logParser.on('TICK_RATE', (data) => { - this.emit('TICK_RATE', data); + this.logParser.on('TICK_RATE', async (data) => { + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onTickRateUpdate(data)) + ); }); } @@ -447,18 +536,34 @@ export default class SquadServer extends EventEmitter { for (const player of this.players) { const oldInfo = oldPlayerInfo[player.eosID]; if (oldInfo === undefined) continue; - if (player.teamID !== oldInfo.teamID) - this.emit('PLAYER_TEAM_CHANGE', { - player: player, - oldTeamID: oldInfo.teamID, - newTeamID: player.teamID - }); - if (player.squadID !== oldInfo.squadID) - this.emit('PLAYER_SQUAD_CHANGE', { - player: player, - oldSquadID: oldInfo.squadID, - newSquadID: player.squadID - }); + + if (player.teamID !== oldInfo.teamID) { + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map( + async (plugin) => + await plugin.onPlayerChangeTeam({ + player: player, + oldTeamID: oldInfo.teamID, + newTeamID: player.teamID + }) + ) + ); + } + + if (player.squadID !== oldInfo.squadID) { + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map( + async (plugin) => + await plugin.onPlayerChangeSquad({ + player: player, + oldSquadID: oldInfo.squadID, + newSquadID: player.squadID + }) + ) + ); + } } if (this.a2sPlayerCount > 0 && players.length === 0) @@ -468,7 +573,10 @@ export default class SquadServer extends EventEmitter { `Real Player Count: ${this.a2sPlayerCount} but loaded ${players.length}` ); - this.emit('UPDATED_PLAYER_INFORMATION'); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onPlayerInformationUpdate()) + ); } catch (err) { Logger.verbose('SquadServer', 1, 'Failed to update player list.', err); } @@ -516,7 +624,10 @@ export default class SquadServer extends EventEmitter { this.nextLayer = nextLayer; this.nextLayerToBeVoted = nextMapToBeVoted; - this.emit('UPDATED_LAYER_INFORMATION'); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onLayerInformationUpdate()) + ); } catch (err) { Logger.verbose('SquadServer', 1, 'Failed to update layer information.', err); } @@ -586,8 +697,10 @@ export default class SquadServer extends EventEmitter { if (!this.currentLayer) this.currentLayer = Layers.getLayerByClassname(info.currentLayer); if (!this.nextLayer) this.nextLayer = Layers.getLayerByClassname(info.nextLayer); - this.emit('UPDATED_A2S_INFORMATION', info); - this.emit('UPDATED_SERVER_INFORMATION', info); + // Inform the plugins of the event. + await Promise.allSettled( + this.plugins.map(async (plugin) => await plugin.onServerInformationUpdate()) + ); } catch (err) { Logger.verbose('SquadServer', 1, 'Failed to update server information.', err); } diff --git a/src/plugin-system/plugin.ts b/src/plugin-system/plugin.ts index 19b514608..cbabc10e5 100644 --- a/src/plugin-system/plugin.ts +++ b/src/plugin-system/plugin.ts @@ -10,5 +10,41 @@ export class Plugin { public async mount() {} + public async onRoundStart(data: object): Promise {} + public async onRoundEnd(data: object): Promise {} + + public async onServerInformationUpdate(): Promise {} + public async onPlayerInformationUpdate(): Promise {} + public async onLayerInformationUpdate(): Promise {} + public async onTickRateUpdate(data: object): Promise {} + public async onChatMessage(data: object): Promise {} + public async onAdminBroadcast (data: object): Promise {} + + public async onPlayerConnection(data: object): Promise {} + public async onPlayerDisconnection(data: object): Promise {} + public async onPlayerWarn(data: object): Promise {} + public async onPlayerKick(data: object): Promise {} + public async onPlayerBan(data: object): Promise {} + + public async onPlayerChangeTeam(data: object): Promise {} + public async onPlayerChangeSquad(data: object): Promise {} + + public async onPlayerDamage(data: object): Promise {} + public async onPlayerWound(data: object): Promise {} + public async onPlayerDie(data: object): Promise {} + public async onPlayerTeamkill(data: object): Promise {} + public async onPlayerRevive(data: object): Promise {} + + public async onPlayerPossess(data: object): Promise {} + public async onPlayerUnPossess(data: object): Promise {} + + public async onPlayerPossessAdminCamera(data: object): Promise {} + public async onPlayerUnPossessAdminCamera(data: object): Promise {} + + public async onSquadCreate(data: object): Promise {} + + public async onDeployableDamage(data: object): Promise {} + + public async onRconError(data: object): Promise {} } \ No newline at end of file From 85c31ddc20b9ca9357d249927cd293f575e8a5c0 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Fri, 2 Aug 2024 00:29:21 +0100 Subject: [PATCH 09/28] Move SquadJS API pings to a plugin --- core/constants.js | 3 -- plugins/squadjs-api/index.ts | 70 ++++++++++++++++++++++++++++++++ plugins/squadjs-api/package.json | 9 ++++ squad-server/index.js | 55 ------------------------- squad-server/package.json | 1 - 5 files changed, 79 insertions(+), 59 deletions(-) delete mode 100644 core/constants.js create mode 100644 plugins/squadjs-api/index.ts create mode 100644 plugins/squadjs-api/package.json diff --git a/core/constants.js b/core/constants.js deleted file mode 100644 index e7dcc7ee1..000000000 --- a/core/constants.js +++ /dev/null @@ -1,3 +0,0 @@ -const SQUADJS_API_DOMAIN = 'https://squadjs.thomas-smyth.uk'; - -export { SQUADJS_API_DOMAIN }; diff --git a/plugins/squadjs-api/index.ts b/plugins/squadjs-api/index.ts new file mode 100644 index 000000000..193058853 --- /dev/null +++ b/plugins/squadjs-api/index.ts @@ -0,0 +1,70 @@ +import axios from 'axios'; +import Logger from 'core/logger'; +import SquadServer from '../../squad-server'; +import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; +import { Plugin } from '../../src/plugin-system'; + +const SQUADJS_API_DOMAIN: string = 'https://squadjs.thomas-smyth.uk'; + +// Define the plugin. +export default class SquadJSCommand extends Plugin { + private instance: number; + private readonly interval: number = 5 * 60 * 1000; + + public constructor(server: SquadServer) { + super(server); + + // Bind the ping method so this is accessible. + this.ping = this.ping.bind(this); + } + + async mount(): Promise { + // @ts-ignore + this.instance = setInterval(this.ping, this.interval); + } + + async ping(): Promise { + Logger.verbose('SquadServer', 1, 'Pinging SquadJS API...'); + + // Prepare the data to send to the SquadJS API. + const payload = { + // Send information about the server. + server: { + host: this.server.options.host, + queryPort: this.server.options.queryPort, + + name: this.server.serverName, + playerCount: this.server.a2sPlayerCount + this.server.publicQueue + this.server.reserveQueue + }, + + // Send information about SquadJS. + squadjs: { + version: SQUADJS_VERSION, + logReaderMode: this.server.options.logReaderMode + } + }; + + // Handle an errors sending the request. + try { + // Send the request. + const { data } = await axios.post(SQUADJS_API_DOMAIN + '/api/v1/ping', payload); + + // Log the response. + if (data.error) { + Logger.verbose( + 'SquadServer', + 1, + `Successfully pinged the SquadJS API. Got back error: ${data.error}` + ); + } else { + Logger.verbose( + 'SquadServer', + 1, + `Successfully pinged the SquadJS API. Got back message: ${data.message}` + ); + } + } catch (err) { + Logger.verbose('SquadServer', 1, 'Failed to ping the SquadJS API: ', err.message); + } + } +} \ No newline at end of file diff --git a/plugins/squadjs-api/package.json b/plugins/squadjs-api/package.json new file mode 100644 index 000000000..3bcaf25d0 --- /dev/null +++ b/plugins/squadjs-api/package.json @@ -0,0 +1,9 @@ +{ + "name": "squadjs-command", + "version": "1.0.0", + "type": "module", + "private": true, + "dependencies": { + "axios": "^1.7.3" + } +} diff --git a/squad-server/index.js b/squad-server/index.js index b66feffc0..1af4f25d1 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -1,15 +1,10 @@ -import axios from 'axios'; - 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 fetchAdminLists from './utils/admin-lists.js'; import { isPlayerID, anyIDToPlayer, anyIDsToPlayers } from './utils/any-id.js'; import { playerIdNames } from 'core/id-parser'; @@ -51,10 +46,6 @@ export default class SquadServer { this.updateA2SInformationInterval = 30 * 1000; this.updateA2SInformationTimeout = null; - this.pingSquadJSAPI = this.pingSquadJSAPI.bind(this); - this.pingSquadJSAPIInterval = 5 * 60 * 1000; - this.pingSquadJSAPITimeout = null; - this.plugins = []; } @@ -78,8 +69,6 @@ export default class SquadServer { await this.logParser.watch(); Logger.verbose('SquadServer', 1, `Watching ${this.serverName}...`); - - await this.pingSquadJSAPI(); } async unwatch() { @@ -790,50 +779,6 @@ export default class SquadServer { ); } - async pingSquadJSAPI() { - if (this.pingSquadJSAPITimeout) clearTimeout(this.pingSquadJSAPITimeout); - - Logger.verbose('SquadServer', 1, 'Pinging SquadJS API...'); - - const payload = { - // Send information about the server. - server: { - host: this.options.host, - queryPort: this.options.queryPort, - - name: this.serverName, - playerCount: this.a2sPlayerCount + this.publicQueue + this.reserveQueue - }, - - // Send information about SquadJS. - squadjs: { - version: SQUADJS_VERSION, - logReaderMode: this.options.logReaderMode - } - }; - - try { - const { data } = await axios.post(SQUADJS_API_DOMAIN + '/api/v1/ping', payload); - - if (data.error) - Logger.verbose( - 'SquadServer', - 1, - `Successfully pinged the SquadJS API. Got back error: ${data.error}` - ); - else - Logger.verbose( - 'SquadServer', - 1, - `Successfully pinged the SquadJS API. Got back message: ${data.message}` - ); - } catch (err) { - Logger.verbose('SquadServer', 1, 'Failed to ping the SquadJS API: ', err.message); - } - - this.pingSquadJSAPITimeout = setTimeout(this.pingSquadJSAPI, this.pingSquadJSAPIInterval); - } - getMatchStartTimeByPlaytime(playtime) { return new Date(Date.now() - +playtime * 1000); } diff --git a/squad-server/package.json b/squad-server/package.json index 1b1f03e02..712c93a15 100644 --- a/squad-server/package.json +++ b/squad-server/package.json @@ -3,7 +3,6 @@ "version": "1.0.0", "type": "module", "dependencies": { - "axios": "^0.21.1", "core": "1.0.0", "gamedig": "^2.0.20" }, From d1ff0ef908e02b3a7c3921a724c1c96a000f02c8 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 3 Aug 2024 00:49:02 +0100 Subject: [PATCH 10/28] Update eslint --- .eslintignore | 4 --- .eslintrc | 9 ------ .lintstagedrc | 4 +-- core/id-parser.js | 5 +++- core/rcon.js | 1 + eslint.config.js | 13 ++++++++ package.json | 17 +++++------ plugins/squadjs-api/index.ts | 5 ++-- plugins/squadjs-command/index.ts | 3 +- src/config-system/config-system.ts | 3 +- src/config-system/index.ts | 2 +- src/plugin-system/index.ts | 2 +- src/plugin-system/plugin-interface.ts | 30 +++++++++++++++++++ src/plugin-system/plugin-system.ts | 8 +++-- src/plugin-system/plugin.ts | 43 ++------------------------- src/types/config.ts | 8 ++--- src/types/index.ts | 2 +- src/types/squad-server-config.ts | 6 +++- 18 files changed, 84 insertions(+), 81 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc create mode 100644 eslint.config.js create mode 100644 src/plugin-system/plugin-interface.ts diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 0f29d767f..000000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -# General -**/node_modules/* - -index-test.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index a4f65126c..000000000 --- a/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2020 - }, - "extends": [ - "standard", - "prettier" - ] -} \ No newline at end of file diff --git a/.lintstagedrc b/.lintstagedrc index 7d0cb018c..125ea91fa 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,7 +1,7 @@ { - "*.js": [ + "*.{js,ts}": [ "eslint --fix .", - "prettier --write \"./**/*.js\"", + "prettier --write \"./**/*.{js,ts}\"", "git add" ] } diff --git a/core/id-parser.js b/core/id-parser.js index 2254452b5..0f512b191 100644 --- a/core/id-parser.js +++ b/core/id-parser.js @@ -32,7 +32,10 @@ class IdsIterator { next() { const match = this.inner.next(); if (match.done) return { value: undefined, done: true }; - return { value: { key: match.value[1], value: match.value[2] }, done: false }; + return { + value: { key: match.value[1], value: match.value[2] }, + done: false + }; } forEach(callbackFn) { diff --git a/core/rcon.js b/core/rcon.js index 685b8f0e7..adb2c7970 100644 --- a/core/rcon.js +++ b/core/rcon.js @@ -178,6 +178,7 @@ export default class Rcon extends EventEmitter { }; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars processChatPacket(decodedPacket) {} onClose(hadError) { diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..69342c985 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,13 @@ +// @ts-check + +import eslint from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended, { + languageOptions: { + globals: { + ...globals.node + } + } +}); diff --git a/package.json b/package.json index 27150ff68..edc28c947 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ], "scripts": { "prepare": "husky install", - "lint": "eslint --fix . && prettier --write \"./**/*.js\"", + "lint": "eslint --fix . && prettier --write \"./**/*.{js,ts}\"", "lint-staged": "lint-staged", "run": "tsx index.ts" }, @@ -22,15 +22,14 @@ "tsx": "^4.16.5" }, "devDependencies": { - "eslint": "^7.17.0", - "eslint-config-prettier": "^7.1.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-standard": "^5.0.0", + "@eslint/js": "^9.8.0", + "@types/eslint__js": "^8.42.3", + "eslint": "^9.8.0", + "globals": "^15.9.0", "husky": "^5.1.3", "lint-staged": "^10.5.4", - "prettier": "^2.2.1" + "prettier": "^2.2.1", + "typescript": "^5.5.4", + "typescript-eslint": "^8.0.0" } } diff --git a/plugins/squadjs-api/index.ts b/plugins/squadjs-api/index.ts index 193058853..f2d6066b7 100644 --- a/plugins/squadjs-api/index.ts +++ b/plugins/squadjs-api/index.ts @@ -8,7 +8,7 @@ const SQUADJS_API_DOMAIN: string = 'https://squadjs.thomas-smyth.uk'; // Define the plugin. export default class SquadJSCommand extends Plugin { - private instance: number; + private instance: ReturnType; private readonly interval: number = 5 * 60 * 1000; public constructor(server: SquadServer) { @@ -19,7 +19,6 @@ export default class SquadJSCommand extends Plugin { } async mount(): Promise { - // @ts-ignore this.instance = setInterval(this.ping, this.interval); } @@ -67,4 +66,4 @@ export default class SquadJSCommand extends Plugin { Logger.verbose('SquadServer', 1, 'Failed to ping the SquadJS API: ', err.message); } } -} \ No newline at end of file +} diff --git a/plugins/squadjs-command/index.ts b/plugins/squadjs-command/index.ts index 80798b273..e1c614f88 100644 --- a/plugins/squadjs-command/index.ts +++ b/plugins/squadjs-command/index.ts @@ -8,6 +8,7 @@ const logger: winston.Logger = winston.createLogger({ // Define the plugin. export default class SquadJSCommand extends Plugin { + // eslint-disable-next-line @typescript-eslint/no-explicit-any async onChatMessage(data: any): Promise { // Check whether the message contained the SquadJS command. const command = data.message.match(/!squadjs/); @@ -21,4 +22,4 @@ export default class SquadJSCommand extends Plugin { await this.server.rcon.warn(data.player.eosID, 'This server is running SquadJS!'); } } -} \ No newline at end of file +} diff --git a/src/config-system/config-system.ts b/src/config-system/config-system.ts index 12d9ce436..d1ee6a339 100644 --- a/src/config-system/config-system.ts +++ b/src/config-system/config-system.ts @@ -22,6 +22,7 @@ export class ConfigSystem { let config: Config; try { config = JSON.parse(configString); + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { throw new Error('Unable to parse config file.'); } @@ -44,4 +45,4 @@ export class ConfigSystem { // Create the SquadServer. return ConfigSystem.buildFromConfigString(configString); } -} \ No newline at end of file +} diff --git a/src/config-system/index.ts b/src/config-system/index.ts index cfa27172c..ecae0fd95 100644 --- a/src/config-system/index.ts +++ b/src/config-system/index.ts @@ -1,3 +1,3 @@ import { ConfigSystem } from './config-system'; -export { ConfigSystem } \ No newline at end of file +export { ConfigSystem }; diff --git a/src/plugin-system/index.ts b/src/plugin-system/index.ts index b70489a47..1918f49ee 100644 --- a/src/plugin-system/index.ts +++ b/src/plugin-system/index.ts @@ -1,4 +1,4 @@ import { Plugin } from './plugin'; import { PluginSystem } from './plugin-system'; -export { Plugin, PluginSystem }; \ No newline at end of file +export { Plugin, PluginSystem }; diff --git a/src/plugin-system/plugin-interface.ts b/src/plugin-system/plugin-interface.ts new file mode 100644 index 000000000..8a851f4cc --- /dev/null +++ b/src/plugin-system/plugin-interface.ts @@ -0,0 +1,30 @@ +export interface PluginInterface { + mount?(data: object): Promise; + onRoundStart?(data: object): Promise; + onRoundEnd?(data: object): Promise; + onServerInformationUpdate?(): Promise; + onPlayerInformationUpdate?(): Promise; + onLayerInformationUpdate?(): Promise; + onTickRateUpdate?(data: object): Promise; + onChatMessage?(data: object): Promise; + onAdminBroadcast?(data: object): Promise; + onPlayerConnection?(data: object): Promise; + onPlayerDisconnection?(data: object): Promise; + onPlayerWarn?(data: object): Promise; + onPlayerKick?(data: object): Promise; + onPlayerBan?(data: object): Promise; + onPlayerChangeTeam?(data: object): Promise; + onPlayerChangeSquad?(data: object): Promise; + onPlayerDamage?(data: object): Promise; + onPlayerWound?(data: object): Promise; + onPlayerDie?(data: object): Promise; + onPlayerTeamkill?(data: object): Promise; + onPlayerRevive?(data: object): Promise; + onPlayerPossess?(data: object): Promise; + onPlayerUnPossess?(data: object): Promise; + onPlayerPossessAdminCamera?(data: object): Promise; + onPlayerUnPossessAdminCamera?(data: object): Promise; + onSquadCreate?(data: object): Promise; + onDeployableDamage?(data: object): Promise; + onRconError?(data: object): Promise; +} diff --git a/src/plugin-system/plugin-system.ts b/src/plugin-system/plugin-system.ts index d89350202..cbf545ce3 100644 --- a/src/plugin-system/plugin-system.ts +++ b/src/plugin-system/plugin-system.ts @@ -1,7 +1,7 @@ import fs from 'fs/promises'; import path from 'path'; import SquadServer from 'squad-server'; -import { Plugin } from './plugin'; +import type { Plugin } from './plugin'; export class PluginSystem { public static async loadPlugins(server: SquadServer): Promise { @@ -14,7 +14,9 @@ export class PluginSystem { const pluginPath: string = path.resolve('./plugins', pluginFolder, 'index.ts'); // Import the plugin. - const { default: ImportedPlugin }: { default: typeof Plugin } = await import(`file://${pluginPath}`); + const { default: ImportedPlugin }: { default: typeof Plugin } = await import( + `file://${pluginPath}` + ); // Initiate the plugin. const plugin = new ImportedPlugin(server); @@ -23,4 +25,4 @@ export class PluginSystem { await server.mountPlugin(plugin); } } -} \ No newline at end of file +} diff --git a/src/plugin-system/plugin.ts b/src/plugin-system/plugin.ts index cbabc10e5..9bbaaeec6 100644 --- a/src/plugin-system/plugin.ts +++ b/src/plugin-system/plugin.ts @@ -1,6 +1,7 @@ import SquadServer from 'squad-server'; +import { PluginInterface } from './plugin-interface'; -export class Plugin { +export class Plugin implements PluginInterface { protected server: SquadServer; public constructor(server: SquadServer) { @@ -9,42 +10,4 @@ export class Plugin { } public async mount() {} - - public async onRoundStart(data: object): Promise {} - public async onRoundEnd(data: object): Promise {} - - public async onServerInformationUpdate(): Promise {} - public async onPlayerInformationUpdate(): Promise {} - public async onLayerInformationUpdate(): Promise {} - public async onTickRateUpdate(data: object): Promise {} - - public async onChatMessage(data: object): Promise {} - public async onAdminBroadcast (data: object): Promise {} - - public async onPlayerConnection(data: object): Promise {} - public async onPlayerDisconnection(data: object): Promise {} - public async onPlayerWarn(data: object): Promise {} - public async onPlayerKick(data: object): Promise {} - public async onPlayerBan(data: object): Promise {} - - public async onPlayerChangeTeam(data: object): Promise {} - public async onPlayerChangeSquad(data: object): Promise {} - - public async onPlayerDamage(data: object): Promise {} - public async onPlayerWound(data: object): Promise {} - public async onPlayerDie(data: object): Promise {} - public async onPlayerTeamkill(data: object): Promise {} - public async onPlayerRevive(data: object): Promise {} - - public async onPlayerPossess(data: object): Promise {} - public async onPlayerUnPossess(data: object): Promise {} - - public async onPlayerPossessAdminCamera(data: object): Promise {} - public async onPlayerUnPossessAdminCamera(data: object): Promise {} - - public async onSquadCreate(data: object): Promise {} - - public async onDeployableDamage(data: object): Promise {} - - public async onRconError(data: object): Promise {} -} \ No newline at end of file +} diff --git a/src/types/config.ts b/src/types/config.ts index ec7304610..1330e1deb 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -3,8 +3,8 @@ import { SquadServerConfig } from './squad-server-config'; export interface Config { server: SquadServerConfig; logger?: { - verboseness?: { [key: string]: number; }; - colors?: { [key: string]: number; }; + verboseness?: { [key: string]: number }; + colors?: { [key: string]: number }; timestamps?: boolean; - } -} \ No newline at end of file + }; +} diff --git a/src/types/index.ts b/src/types/index.ts index 58cc9ab56..eea426239 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,4 @@ import { Config } from './config.js'; import { SquadServerConfig } from './squad-server-config.js'; -export { Config, SquadServerConfig }; \ No newline at end of file +export { Config, SquadServerConfig }; diff --git a/src/types/squad-server-config.ts b/src/types/squad-server-config.ts index 8d924c61a..a8b0b38ca 100644 --- a/src/types/squad-server-config.ts +++ b/src/types/squad-server-config.ts @@ -1 +1,5 @@ -export interface SquadServerConfig {} \ No newline at end of file +export interface SquadServerConfig { + // Placeholder. + // eslint-disable-next-line + [key: string]: any; +} From 5e319305ffd0c2acce5841645b210774029d1557 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 3 Aug 2024 11:01:09 +0100 Subject: [PATCH 11/28] Add docs to plugins --- package.json | 2 +- plugins/squadjs-api/README.md | 68 ++++++++++++++++++++++++++++ plugins/squadjs-api/package.json | 5 +- plugins/squadjs-command/README.md | 58 ++++++++++++++++++++++++ plugins/squadjs-command/index.ts | 15 ++---- plugins/squadjs-command/package.json | 10 ++-- 6 files changed, 141 insertions(+), 17 deletions(-) create mode 100644 plugins/squadjs-api/README.md create mode 100644 plugins/squadjs-command/README.md diff --git a/package.json b/package.json index edc28c947..607ec464c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "SquadJS", "version": "4.1.0", + "type": "module", "repository": "https://github.com/Team-Silver-Sphere/SquadJS.git", "author": "Thomas Smyth ", "license": "BSL-1.0", @@ -16,7 +17,6 @@ "lint-staged": "lint-staged", "run": "tsx index.ts" }, - "type": "module", "dependencies": { "squad-server": "1.0.0", "tsx": "^4.16.5" diff --git a/plugins/squadjs-api/README.md b/plugins/squadjs-api/README.md new file mode 100644 index 000000000..318a37f4d --- /dev/null +++ b/plugins/squadjs-api/README.md @@ -0,0 +1,68 @@ +
    + +Logo +Logo + +#### SquadJS API Plugin + +[![GitHub release](https://img.shields.io/github/release/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/releases) +[![GitHub contributors](https://img.shields.io/github/contributors/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/graphs/contributors) +[![GitHub release](https://img.shields.io/github/license/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/blob/master/LICENSE) + +
    + +[![GitHub issues](https://img.shields.io/github/issues/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/issues) +[![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/pulls) +[![GitHub issues](https://img.shields.io/github/stars/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/stargazers) +[![Discord](https://img.shields.io/discord/266210223406972928.svg?style=flat-square&logo=discord)](https://discord.gg/9F2Ng5C) + +

    +
    + +## **About** +The SquadJS API Plugin sends analytics data to the [SquadJS API](https://github.com/Thomas-Smyth/thomas-smyth.uk/tree/master/src/subdomains/squadjs) at regularly intervals. The SquadJS development team use this data to understand the needs of SquadJS users and prioritise areas of development. + +The data includes the following information about your Squad server and SquadJS instance: + * Host + * Query Port + * Name + * Player Count + * SquadJS Version + * Log Reader Mode + +The SquadJS API Plugin is enabled by default. + +
    + +## **Installation** +1. Clone or download the plugin into the `/plugins` folder. +2. Within the plugin's subfolder, run `npm install` or `yarn install`. + +## License +``` +Boost Software License - Version 1.0 - August 17th, 2003 + +Copyright (c) 2020 Thomas Smyth + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +``` diff --git a/plugins/squadjs-api/package.json b/plugins/squadjs-api/package.json index 3bcaf25d0..ea0defcee 100644 --- a/plugins/squadjs-api/package.json +++ b/plugins/squadjs-api/package.json @@ -1,7 +1,10 @@ { - "name": "squadjs-command", + "name": "squadjs-api-plugin", "version": "1.0.0", "type": "module", + "repository": "https://github.com/Team-Silver-Sphere/SquadJS.git", + "author": "Thomas Smyth ", + "license": "BSL-1.0", "private": true, "dependencies": { "axios": "^1.7.3" diff --git a/plugins/squadjs-command/README.md b/plugins/squadjs-command/README.md new file mode 100644 index 000000000..e81310361 --- /dev/null +++ b/plugins/squadjs-command/README.md @@ -0,0 +1,58 @@ +
    + +Logo +Logo + +#### SquadJS Command Plugin + +[![GitHub release](https://img.shields.io/github/release/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/releases) +[![GitHub contributors](https://img.shields.io/github/contributors/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/graphs/contributors) +[![GitHub release](https://img.shields.io/github/license/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/blob/master/LICENSE) + +
    + +[![GitHub issues](https://img.shields.io/github/issues/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/issues) +[![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/pulls) +[![GitHub issues](https://img.shields.io/github/stars/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/stargazers) +[![Discord](https://img.shields.io/discord/266210223406972928.svg?style=flat-square&logo=discord)](https://discord.gg/9F2Ng5C) + +

    +
    + +## **About** +The SquadJS Command Plugin allows players to see whether a server is using SquadJS by typing the `!squadjs` command in the chat. The SquadJS Command Plugin is enabled by default. + +
    + +## **Installation** +1. Clone or download the plugin into the `/plugins` folder. +2. Within the plugin's subfolder, run `npm install` or `yarn install`. + +## License +``` +Boost Software License - Version 1.0 - August 17th, 2003 + +Copyright (c) 2020 Thomas Smyth + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +``` diff --git a/plugins/squadjs-command/index.ts b/plugins/squadjs-command/index.ts index e1c614f88..4f114a6a6 100644 --- a/plugins/squadjs-command/index.ts +++ b/plugins/squadjs-command/index.ts @@ -1,11 +1,6 @@ -import winston from 'winston'; +import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; import { Plugin } from '../../src/plugin-system'; -// Create an instance of winston. -const logger: winston.Logger = winston.createLogger({ - transports: [new winston.transports.Console()] -}); - // Define the plugin. export default class SquadJSCommand extends Plugin { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -15,11 +10,11 @@ export default class SquadJSCommand extends Plugin { // Handle uses of the command. if (command) { - // Log that the command was used. - logger.info(`${data.player.name} called the !squadjs command.`); - // Send the response to the command. - await this.server.rcon.warn(data.player.eosID, 'This server is running SquadJS!'); + await this.server.rcon.warn( + data.player.eosID, + `This server is running SquadJS (v${SQUADJS_VERSION})!` + ); } } } diff --git a/plugins/squadjs-command/package.json b/plugins/squadjs-command/package.json index 835cb7ee5..9fb5812a2 100644 --- a/plugins/squadjs-command/package.json +++ b/plugins/squadjs-command/package.json @@ -1,9 +1,9 @@ { - "name": "squadjs-command", + "name": "squadjs-api-plugin", "version": "1.0.0", "type": "module", - "private": true, - "dependencies": { - "winston": "^3.13.1" - } + "repository": "https://github.com/Team-Silver-Sphere/SquadJS.git", + "author": "Thomas Smyth ", + "license": "BSL-1.0", + "private": true } From d0182ba60d022df182ed04fa78b4daa61faf5d2e Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 3 Aug 2024 13:02:29 +0100 Subject: [PATCH 12/28] Add basic plugin docs. --- README.md | 11 +- plugin-development.md | 182 +++++++++++++++++++++++++++++++ plugins/squadjs-api/index.ts | 2 +- plugins/squadjs-command/index.ts | 2 +- 4 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 plugin-development.md diff --git a/README.md b/README.md index 0358e3ad9..05672cde6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ SquadJS is a scripting framework, designed for Squad servers, that aims to handl
    ## **Using SquadJS** -SquadJS relies on being able to access the Squad server log directory in order to parse logs live to collect information. Thus, SquadJS must be hosted on the same server box as your Squad server or be connected to your Squad server via FTP. +SquadJS relies on being able to access the Squad server log directory in order to parse logs live to collect information. Thus, SquadJS must be hosted on the same server box as your Squad server or be connected to your Squad server via FTP or SFTP. #### Prerequisites * Git @@ -132,11 +132,14 @@ The larger the number set in the `verboseness` section for a specified module th
    ## **Plugins** -The following is a list of plugins built into SquadJS, you can click their title for more information: +SquadJS plugins unlock the framework's full potential by giving you access to its extensive data. These powerful tools can automate a wide range of tasks, from warmly welcoming new players to meticulously collecting player statistics. -Placeholder. +A thriving community of developers creates and shares a wealth of open-source plugins, like the ones listed below. Remember, these third-party plugins may vary in quality and compatibility. -Interested in creating your own plugin? [See more here](./squad-server/plugins/readme.md) +SquadJS API Plugin (./plugins/squadjs-api) +SquadJS Command Plugin (./plugins/squadjs-api) + +Want to join the fun and build your own plugin? Check out the comprehensive guide [here](./plugin-development.md). ## Statement on Accuracy Some information SquadJS collects from Squad servers was never intended or designed to be collected. As a result, it is impossible for any framework to collect the same information with 100% accuracy. SquadJS aims to get as close as possible to that figure, however, it acknowledges that this is not possible in some specific scenarios. diff --git a/plugin-development.md b/plugin-development.md new file mode 100644 index 000000000..eb4587a2b --- /dev/null +++ b/plugin-development.md @@ -0,0 +1,182 @@ +
    + +Logo +Logo + +#### SquadJS + +[![GitHub release](https://img.shields.io/github/release/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/releases) +[![GitHub contributors](https://img.shields.io/github/contributors/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/graphs/contributors) +[![GitHub release](https://img.shields.io/github/license/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/blob/master/LICENSE) + +
    + +[![GitHub issues](https://img.shields.io/github/issues/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/issues) +[![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/pulls) +[![GitHub issues](https://img.shields.io/github/stars/Team-Silver-Sphere/SquadJS.svg?style=flat-square)](https://github.com/Team-Silver-Sphere/SquadJS/stargazers) +[![Discord](https://img.shields.io/discord/266210223406972928.svg?style=flat-square&logo=discord)](https://discord.gg/9F2Ng5C) + +

    +
    + +## **About** +SquadJS plugins unlock the framework's full potential by giving you access to its extensive data. These powerful tools can automate a wide range of tasks, from warmly welcoming new players to meticulously collecting player statistics. A thriving community of developers creates and shares a wealth of open-source plugins. In this guide, we will show you how to join the fun and build your own plugin. + +## **Pre-Requisites** +This guide assumes you have some basic skills in Git and Node.js programming. + +## **Getting Started** +Follow the steps below to get started. + +1. Create a new folder in the `./plugins` folder. + * We will use this folder to store your plugin's resources. + * We recommend that use Git (and GitHub) to manage versions of your plugin. This will also allow you to back you work up and/or share it with others. + +2. Copy the contents of the `./plugins/squadjs-command` folder into your new folder. + * We will use this plugin as a template. + * Do not forget to rename things and update the documentation appropriately. + +3. Implement your plugin by modifying the `index.ts` file. + * Plugins are structured as classes. As documented below, you can implement various methods to trigger your plugin's logic at relevant times. + * Your plugin will be automatically imported from this file by SquadJS. Do not rename the file and keep your plugin as its default export. + * Feel free to create additional `.js`/`.ts` files if you wish to organise your plugin's code across multiple files. + +4. To install dependencies, run `npm install ...` or `yarn add ...` in your new folder. + +## **Plugin Constructor** +Some SquadJS plugins may have some preliminary logic that must be executed before the plugin is enabled. For instance, the plugin may need to initialise a dependency. Plugins can complete this logic through the constructor. + +```ts +import SquadServer from '../../squad-server'; +import { Plugin } from '../../src/plugin-system'; + +// Define the plugin. +export default class ConstructorPlugin extends Plugin { + public constructor(server: SquadServer) { + super(server); + + // Do preliminary logic. + } +} +``` + +## **`mount()` Method** +Some SquadJS plugins may have some preliminary logic that must be executed asynchronously before the plugin is enabled. For instance, the plugin may need to connect to a database. Plugins can complete this logic through the `mount()` method. + +```ts +import { Plugin } from '../../src/plugin-system'; + +// Define the plugin. +export default class MountPlugin extends Plugin { + async mount(): Promise { + // Do something. + } +} +``` + +## **Event-Driven Plugins** +Most SquadJS plugins are event-driven, responding to specific actions within the game. For example, the SquadJS Command Plugin listens for chat messages containing the `!squadjs` command and responds with a warning message. + +Plugins can react to various events by implementing corresponding methods. To handle chat messages, you would use the `onChatMessage` method. A comprehensive list of other available methods is provided [here](./src/plugin-system/plugin-interface.ts). Event data, such as the content of a chat message, is passed to the method as an object containing multiple properties. The exact structure of this event data will be documented soon. + +```ts +import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; +import { Plugin } from '../../src/plugin-system'; + +// Define the plugin. +export default class ChatEventPlugin extends Plugin { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async onChatMessage(data: any): Promise { + // Respond to the event. + } +} +``` + +## **Time-Triggered Plugins** +Some SquadJS plugins are time-triggered. For example, the SquadJS API Plugin sends analytics data to the SquadJS API every few minutes. Plugins can operate both event-driven and time-triggered. Additionally, event-driven plugins can initiate time-based actions, such as executing a task a set time after an event occurs. + +Plugins can trigger their own methods at regular intervals using the `setInterval` as seen below. + +```ts +import SquadServer from '../../squad-server'; +import { Plugin } from '../../src/plugin-system'; + +// Define the plugin. +export default class IntervalCommand extends Plugin { + private instance: ReturnType; + private readonly interval: number = 5 * 60 * 1000; + + public constructor(server: SquadServer) { + super(server); + + // Bind the ping method so this is accessible. + this.ping = this.ping.bind(this); + } + + // Start the intervals when the plugin is mounted to the SquadServer. + async mount(): Promise { + this.instance = setInterval(this.ping, this.interval); + } + + async ping(): Promise { + // Do something. + } +} +``` + +Plugins can also trigger their own methods at after a delay using the `setTimeout` function as seen below. + +```ts +import SquadServer from '../../squad-server'; +import { Plugin } from '../../src/plugin-system'; + +// Define the plugin. +export default class TimeoutCommand extends Plugin { + private instance: ReturnType; + private readonly delay: number = 5 * 60 * 1000; + + public constructor(server: SquadServer) { + super(server); + + // Bind the ping method so this is accessible. + this.ping = this.ping.bind(this); + } + + // Start the timeout when the plugin is mounted to the SquadServer. + async mount(): Promise { + this.instance = setTimeout(this.ping, this.delay); + } + + async ping(): Promise { + // Do something. + } +} +``` + +## **Communication with the Squad Server** +Plugins have access to the Squad server through the `server` property. This allows them to access data, e.g. a list of players, and complete actions, e.g. kick players. A list of data and actions available will be documented soon. + +```ts +import { Plugin } from '../../src/plugin-system'; + +// Define the plugin. +export default class PlayerCountCommandPlugin extends Plugin { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async onChatMessage(data: any): Promise { + // Check whether the message contained the playercount command. + const command = data.message.match(/!playercount/); + + // Handle uses of the command. + if (command) { + // Send the response to the command. + await this.server.rcon.warn( + data.player.eosID, + `There are ${this.server.players.length} players online.` + ); + } + } +} +``` + +## **Struggling?** +Struggling to write your own plugin? Or simply want to engage with thriving community of plugin developers? Check out [our Discord](https://discord.gg/9F2Ng5C)! \ No newline at end of file diff --git a/plugins/squadjs-api/index.ts b/plugins/squadjs-api/index.ts index f2d6066b7..d8e1e7cf7 100644 --- a/plugins/squadjs-api/index.ts +++ b/plugins/squadjs-api/index.ts @@ -7,7 +7,7 @@ import { Plugin } from '../../src/plugin-system'; const SQUADJS_API_DOMAIN: string = 'https://squadjs.thomas-smyth.uk'; // Define the plugin. -export default class SquadJSCommand extends Plugin { +export default class SquadJSAPIPlugin extends Plugin { private instance: ReturnType; private readonly interval: number = 5 * 60 * 1000; diff --git a/plugins/squadjs-command/index.ts b/plugins/squadjs-command/index.ts index 4f114a6a6..479b9b435 100644 --- a/plugins/squadjs-command/index.ts +++ b/plugins/squadjs-command/index.ts @@ -2,7 +2,7 @@ import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; import { Plugin } from '../../src/plugin-system'; // Define the plugin. -export default class SquadJSCommand extends Plugin { +export default class SquadJSCommandPlugin extends Plugin { // eslint-disable-next-line @typescript-eslint/no-explicit-any async onChatMessage(data: any): Promise { // Check whether the message contained the SquadJS command. From 6075b40f746dcc925fff678d994a3defd49202b5 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 3 Aug 2024 13:05:15 +0100 Subject: [PATCH 13/28] Fix formatting in readme --- README.md | 4 ++-- plugin-development.md | 2 +- plugins/{squadjs-api => squadjs-api-plugin}/README.md | 0 plugins/{squadjs-api => squadjs-api-plugin}/index.ts | 0 plugins/{squadjs-api => squadjs-api-plugin}/package.json | 0 plugins/{squadjs-command => squadjs-command-plugin}/README.md | 0 plugins/{squadjs-command => squadjs-command-plugin}/index.ts | 0 .../{squadjs-command => squadjs-command-plugin}/package.json | 0 8 files changed, 3 insertions(+), 3 deletions(-) rename plugins/{squadjs-api => squadjs-api-plugin}/README.md (100%) rename plugins/{squadjs-api => squadjs-api-plugin}/index.ts (100%) rename plugins/{squadjs-api => squadjs-api-plugin}/package.json (100%) rename plugins/{squadjs-command => squadjs-command-plugin}/README.md (100%) rename plugins/{squadjs-command => squadjs-command-plugin}/index.ts (100%) rename plugins/{squadjs-command => squadjs-command-plugin}/package.json (100%) diff --git a/README.md b/README.md index 05672cde6..f695877cf 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,8 @@ SquadJS plugins unlock the framework's full potential by giving you access to it A thriving community of developers creates and shares a wealth of open-source plugins, like the ones listed below. Remember, these third-party plugins may vary in quality and compatibility. -SquadJS API Plugin (./plugins/squadjs-api) -SquadJS Command Plugin (./plugins/squadjs-api) + * [SquadJS API Plugin](./plugins/squadjs-api-plugin) + * [SquadJS Command Plugin](./plugins/squadjs-command-plugin) Want to join the fun and build your own plugin? Check out the comprehensive guide [here](./plugin-development.md). diff --git a/plugin-development.md b/plugin-development.md index eb4587a2b..90e2f6d72 100644 --- a/plugin-development.md +++ b/plugin-development.md @@ -32,7 +32,7 @@ Follow the steps below to get started. * We will use this folder to store your plugin's resources. * We recommend that use Git (and GitHub) to manage versions of your plugin. This will also allow you to back you work up and/or share it with others. -2. Copy the contents of the `./plugins/squadjs-command` folder into your new folder. +2. Copy the contents of the `./plugins/squadjs-command-plugin` folder into your new folder. * We will use this plugin as a template. * Do not forget to rename things and update the documentation appropriately. diff --git a/plugins/squadjs-api/README.md b/plugins/squadjs-api-plugin/README.md similarity index 100% rename from plugins/squadjs-api/README.md rename to plugins/squadjs-api-plugin/README.md diff --git a/plugins/squadjs-api/index.ts b/plugins/squadjs-api-plugin/index.ts similarity index 100% rename from plugins/squadjs-api/index.ts rename to plugins/squadjs-api-plugin/index.ts diff --git a/plugins/squadjs-api/package.json b/plugins/squadjs-api-plugin/package.json similarity index 100% rename from plugins/squadjs-api/package.json rename to plugins/squadjs-api-plugin/package.json diff --git a/plugins/squadjs-command/README.md b/plugins/squadjs-command-plugin/README.md similarity index 100% rename from plugins/squadjs-command/README.md rename to plugins/squadjs-command-plugin/README.md diff --git a/plugins/squadjs-command/index.ts b/plugins/squadjs-command-plugin/index.ts similarity index 100% rename from plugins/squadjs-command/index.ts rename to plugins/squadjs-command-plugin/index.ts diff --git a/plugins/squadjs-command/package.json b/plugins/squadjs-command-plugin/package.json similarity index 100% rename from plugins/squadjs-command/package.json rename to plugins/squadjs-command-plugin/package.json From d02308f41952634465880d2d46cc2533a366b06c Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 3 Aug 2024 13:10:11 +0100 Subject: [PATCH 14/28] Remove reference to the SquadJS API from the main readme --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index f695877cf..9a7520fd9 100644 --- a/README.md +++ b/README.md @@ -151,17 +151,6 @@ Below is a list of scenarios we know may cause some information to be inaccurate - The accurate collection of some server log events will not occur. SquadJS collects players' "suffix" name, i.e. their Steam name without the clan tag added via the game settings, when they join the server and uses this to identify them in certain logs that do not include their full name. As a result, for players connecting prior to SquadJS starting some log events associated with their actions will show the player as `null`. * Duplicated Player Names - If two or more players have the same name or suffix name (see above) then SquadJS will be unable to identify them in the logs. When this occurs event logs will show the player as `null`. Be on the watch for groups of players who try to abuse this in order to TK or complete other malicious actions without being detected by SquadJS plugins. -## SquadJS API -SquadJS pings the following data to the [SquadJS API](https://github.com/Team-Silver-Sphere/SquadJS-API/) at regular intervals to assist with its development: -* Squad server IP, query port, name & player count (including queue size). -* SquadJS version. -* Log reader mode, i.e. `tail` or `ftp`. -* Plugin configuration. - -At this time, this cannot be disabled. - -Please note, plugin configurations do **not** and should **not** contain any sensitive information which allows us to collect this information. Any sensitive information, e.g. Discord login tokens, should be included in the `connectors` section of the config which is not sent to our API. It is important that developers of custom plugins maintain this approach to avoid submitting confidential information to our API. - ## Versions and Releases Whilst installing SquadJS you may do the following to obtain slightly different versions: * Download the [latest release](https://github.com/Team-Silver-Sphere/SquadJS/releases/latest) - To get the latest **stable** version of SquadJS. From b757928ac0875f43e0fce18c927b3c16107a1ed7 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 3 Aug 2024 13:10:49 +0100 Subject: [PATCH 15/28] Update credits --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9a7520fd9..99c86a789 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,6 @@ The above policy was written and put into effect after the release of SquadJS v2 ## Credits SquadJS would not be possible without the support of so many individuals and organisations. Our thanks goes out to: * [SquadJS's contributors](https://github.com/Team-Silver-Sphere/SquadJS/graphs/contributors). -* [Thomas Smyth's GitHub sponsors](https://github.com/sponsors/Thomas-Smyth). * subtlerod for proposing the initial log parsing idea, helping to design the log parsing process and for providing multiple servers to test with. * Shanomac99 and the rest of the Squad Wiki team for providing us with [layer information](https://github.com/Squad-Wiki-Editorial/squad-wiki-pipeline-map-data). * Fourleaf, Mex, various members of ToG / ToG-L and others that helped to stage logs and participate in small scale tests. From 6c5d40db5899a01e80fb3be313fd94f4b42f0a98 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 3 Aug 2024 13:11:37 +0100 Subject: [PATCH 16/28] Add installed by default flag to plugin list --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 99c86a789..557aa98e3 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,8 @@ SquadJS plugins unlock the framework's full potential by giving you access to it A thriving community of developers creates and shares a wealth of open-source plugins, like the ones listed below. Remember, these third-party plugins may vary in quality and compatibility. - * [SquadJS API Plugin](./plugins/squadjs-api-plugin) - * [SquadJS Command Plugin](./plugins/squadjs-command-plugin) + * [SquadJS API Plugin](./plugins/squadjs-api-plugin) (Installed by Default) + * [SquadJS Command Plugin](./plugins/squadjs-command-plugin) (Installed by Default) Want to join the fun and build your own plugin? Check out the comprehensive guide [here](./plugin-development.md). From c489565ff48a48bca964519e941e01d724cf9173 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Wed, 7 Aug 2024 20:00:52 +0100 Subject: [PATCH 17/28] Add config system --- package.json | 3 +- plugin-development.md | 46 +++++++++++++++++--- plugins/squadjs-api-plugin/README.md | 9 ++++ plugins/squadjs-api-plugin/config.json | 3 ++ plugins/squadjs-command-plugin/README.md | 11 +++++ plugins/squadjs-command-plugin/config.json | 4 ++ plugins/squadjs-command-plugin/index.ts | 37 +++++++++++++--- squad-server/package.json | 1 + src/plugin-system/plugin-interface.ts | 4 +- src/plugin-system/plugin-system.ts | 50 ++++++++++++++++++++-- src/plugin-system/plugin.ts | 10 +++-- src/utils/index.ts | 3 ++ src/utils/logger.ts | 7 +++ 13 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 plugins/squadjs-api-plugin/config.json create mode 100644 plugins/squadjs-command-plugin/config.json create mode 100644 src/utils/index.ts create mode 100644 src/utils/logger.ts diff --git a/package.json b/package.json index 607ec464c..86d20a82c 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ }, "dependencies": { "squad-server": "1.0.0", - "tsx": "^4.16.5" + "tsx": "^4.16.5", + "winston": "^3.13.1" }, "devDependencies": { "@eslint/js": "^9.8.0", diff --git a/plugin-development.md b/plugin-development.md index 90e2f6d72..9d2806d83 100644 --- a/plugin-development.md +++ b/plugin-development.md @@ -43,6 +43,37 @@ Follow the steps below to get started. 4. To install dependencies, run `npm install ...` or `yarn add ...` in your new folder. +## **Configuration** +Many SquadJS plugins will have some level of configurability. This can be done by creating a `config.json` file within the plugin's folder and defining the options within. + +Plugins can implement this by defining their config in an interface and passing this as a generic type to the `Plugin` base class as seen below. Default values can be defined in the constructor. The config can then be accessed within the plugin's methods through `this.config`. + +```ts +interface PluginConfig { + message: string; +} + +// Define the plugin. +export default class ConfigPlugin extends Plugin { + constructor(server: SquadServer, config?: PluginConfig) { + // Set default config. + config = { + message: 'Hello world!', + ...(config || {}), + }; + + + // Initiate the parent class. + super(server, config); + } + + async mount(): Promise { + // Access the config. + console.log(this.config.message); + } +} +``` + ## **Plugin Constructor** Some SquadJS plugins may have some preliminary logic that must be executed before the plugin is enabled. For instance, the plugin may need to initialise a dependency. Plugins can complete this logic through the constructor. @@ -52,8 +83,9 @@ import { Plugin } from '../../src/plugin-system'; // Define the plugin. export default class ConstructorPlugin extends Plugin { - public constructor(server: SquadServer) { - super(server); + public constructor(server: SquadServer, config?: undefined) { + // Initiate the parent class. + super(server, config); // Do preliminary logic. } @@ -99,14 +131,14 @@ Plugins can trigger their own methods at regular intervals using the `setInterva ```ts import SquadServer from '../../squad-server'; -import { Plugin } from '../../src/plugin-system'; +import { Plugin, PluginConfig } from '../../src/plugin-system'; // Define the plugin. export default class IntervalCommand extends Plugin { private instance: ReturnType; private readonly interval: number = 5 * 60 * 1000; - public constructor(server: SquadServer) { + public constructor(server: SquadServer, config?: PluginConfig) { super(server); // Bind the ping method so this is accessible. @@ -128,14 +160,14 @@ Plugins can also trigger their own methods at after a delay using the `setTimeou ```ts import SquadServer from '../../squad-server'; -import { Plugin } from '../../src/plugin-system'; +import { Plugin, PluginConfig } from '../../src/plugin-system'; // Define the plugin. export default class TimeoutCommand extends Plugin { private instance: ReturnType; private readonly delay: number = 5 * 60 * 1000; - public constructor(server: SquadServer) { + public constructor(server: SquadServer, config?: PluginConfig) { super(server); // Bind the ping method so this is accessible. @@ -154,7 +186,7 @@ export default class TimeoutCommand extends Plugin { ``` ## **Communication with the Squad Server** -Plugins have access to the Squad server through the `server` property. This allows them to access data, e.g. a list of players, and complete actions, e.g. kick players. A list of data and actions available will be documented soon. +SquadJS plugins have access to the Squad server through the `server` property. This allows them to access data, e.g. a list of players, and complete actions, e.g. kick players. A list of data and actions available will be documented soon. ```ts import { Plugin } from '../../src/plugin-system'; diff --git a/plugins/squadjs-api-plugin/README.md b/plugins/squadjs-api-plugin/README.md index 318a37f4d..b9c53d4f6 100644 --- a/plugins/squadjs-api-plugin/README.md +++ b/plugins/squadjs-api-plugin/README.md @@ -38,6 +38,15 @@ The SquadJS API Plugin is enabled by default. 1. Clone or download the plugin into the `/plugins` folder. 2. Within the plugin's subfolder, run `npm install` or `yarn install`. +## **Configuration** +To configure this plugin, create a `config.json` file in this folder. + + +The following options are available: +| Option | Type | Optional | Default | Description | +| --- | --- | --- | --- | --- | +| `disabled` | `boolean` | Y | `false` | If set to `true`, the plugin will not be imported, initialised or mounted. | + ## License ``` Boost Software License - Version 1.0 - August 17th, 2003 diff --git a/plugins/squadjs-api-plugin/config.json b/plugins/squadjs-api-plugin/config.json new file mode 100644 index 000000000..c7bc56f73 --- /dev/null +++ b/plugins/squadjs-api-plugin/config.json @@ -0,0 +1,3 @@ +{ + "disabled": false +} \ No newline at end of file diff --git a/plugins/squadjs-command-plugin/README.md b/plugins/squadjs-command-plugin/README.md index e81310361..7739edd48 100644 --- a/plugins/squadjs-command-plugin/README.md +++ b/plugins/squadjs-command-plugin/README.md @@ -28,6 +28,17 @@ The SquadJS Command Plugin allows players to see whether a server is using Squad 1. Clone or download the plugin into the `/plugins` folder. 2. Within the plugin's subfolder, run `npm install` or `yarn install`. +## **Configuration** +To configure this plugin, create a `config.json` file in this folder. + + +The following options are available: + +| Option | Type | Optional | Default | Description | +| --- | --- | --- | --- | --- | +| `disabled` | `boolean` | Y | `false` | If set to `true` the plugin will not be imported, initialised or mounted. | +| `mode` | `'broadcast'` or `'warn'` | Y | `warn` | If set to `broadcast`, the response to the command will be sent to all players via an admin broadcast. If set to `warn`, the response to the command will be sent to the requesting player via a warning. | + ## License ``` Boost Software License - Version 1.0 - August 17th, 2003 diff --git a/plugins/squadjs-command-plugin/config.json b/plugins/squadjs-command-plugin/config.json new file mode 100644 index 000000000..cdb2d8745 --- /dev/null +++ b/plugins/squadjs-command-plugin/config.json @@ -0,0 +1,4 @@ +{ + "disabled": false, + "mode": "broadcast" +} \ No newline at end of file diff --git a/plugins/squadjs-command-plugin/index.ts b/plugins/squadjs-command-plugin/index.ts index 479b9b435..2c140486a 100644 --- a/plugins/squadjs-command-plugin/index.ts +++ b/plugins/squadjs-command-plugin/index.ts @@ -1,8 +1,24 @@ import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; import { Plugin } from '../../src/plugin-system'; +import SquadServer from 'squad-server/index'; + +interface PluginConfig { + mode: 'broadcast' | 'warn'; +} // Define the plugin. -export default class SquadJSCommandPlugin extends Plugin { +export default class SquadJSCommandPlugin extends Plugin { + constructor(server: SquadServer, config?: PluginConfig) { + // Set default config. + config = { + mode: 'broadcast', + ...(config || {}) + }; + + // Initiate the parent class. + super(server, config); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any async onChatMessage(data: any): Promise { // Check whether the message contained the SquadJS command. @@ -10,11 +26,20 @@ export default class SquadJSCommandPlugin extends Plugin { // Handle uses of the command. if (command) { - // Send the response to the command. - await this.server.rcon.warn( - data.player.eosID, - `This server is running SquadJS (v${SQUADJS_VERSION})!` - ); + // Define the response. + const message = `This server is running SquadJS (v${SQUADJS_VERSION})!`; + + // Send the response for the appropriate type. + switch (this.config.mode) { + case 'broadcast': + await this.server.rcon.broadcast(message); + break; + case 'warn': + await this.server.rcon.warn(data.player.eosID, message); + break; + default: + break; + } } } } diff --git a/squad-server/package.json b/squad-server/package.json index 712c93a15..3892f73f9 100644 --- a/squad-server/package.json +++ b/squad-server/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "dependencies": { + "axios": "^1.7.3", "core": "1.0.0", "gamedig": "^2.0.20" }, diff --git a/src/plugin-system/plugin-interface.ts b/src/plugin-system/plugin-interface.ts index 8a851f4cc..74750639b 100644 --- a/src/plugin-system/plugin-interface.ts +++ b/src/plugin-system/plugin-interface.ts @@ -1,5 +1,5 @@ -export interface PluginInterface { - mount?(data: object): Promise; +export interface Plugin { + mount(data: object): Promise; onRoundStart?(data: object): Promise; onRoundEnd?(data: object): Promise; onServerInformationUpdate?(): Promise; diff --git a/src/plugin-system/plugin-system.ts b/src/plugin-system/plugin-system.ts index cbf545ce3..fd8029fb5 100644 --- a/src/plugin-system/plugin-system.ts +++ b/src/plugin-system/plugin-system.ts @@ -1,28 +1,70 @@ -import fs from 'fs/promises'; +import fs from 'fs'; import path from 'path'; import SquadServer from 'squad-server'; +import { logger } from '../utils'; import type { Plugin } from './plugin'; export class PluginSystem { public static async loadPlugins(server: SquadServer): Promise { // Get a list of plugin folders. - const pluginFolders = await fs.readdir('./plugins'); + const pluginFolders = fs.readdirSync('./plugins'); // Loop over each plugin folder. for (const pluginFolder of pluginFolders) { + // Log progress. + logger.info(`[PluginSystem] Importing ${pluginFolder}...`); + // Get the path to the plugin. - const pluginPath: string = path.resolve('./plugins', pluginFolder, 'index.ts'); + const pluginFolderPath = path.resolve('./plugins', pluginFolder); + const pluginPath = path.resolve(pluginFolderPath, 'index.ts'); + const pluginConfigPath = path.resolve(pluginFolderPath, './config.json'); + + // Initiate a variable to store the config in. + // eslint-disable-next-line + let config: { disabled?: boolean; [key: string]: any } | undefined; + + // Check the config file exists. + if (fs.readFileSync(pluginConfigPath)) { + // Read the config file. + const configString = fs.readFileSync(pluginConfigPath, 'utf-8'); + + // Parse the config. + try { + config = JSON.parse(configString); + } catch (err) { + logger.error(`[PluginSystem] ${pluginFolder} has an invalid configuration file.`); + throw err; + } + } + + // Do not import, initiate or mount the plugin if it is disabled. + if (config?.disabled) { + logger.warn(`[PluginSystem] ${pluginFolder} is disabled.`); + continue; + } + + // Delete disabled if it is defined. This does not need to be passed to the plugin. + if (config) { + delete config.disabled; + } // Import the plugin. const { default: ImportedPlugin }: { default: typeof Plugin } = await import( `file://${pluginPath}` ); + // Log progress. + logger.info(`[PluginSystem] ${pluginFolder} is successfully imported.`); + logger.info(`[PluginSystem] Mounting ${pluginFolder}...`); + // Initiate the plugin. - const plugin = new ImportedPlugin(server); + const plugin = new ImportedPlugin(server, config); // Mount the new plugin. await server.mountPlugin(plugin); + + // Log progress. + logger.info(`[PluginSystem] ${pluginFolder} is successfully mounted.`); } } } diff --git a/src/plugin-system/plugin.ts b/src/plugin-system/plugin.ts index 9bbaaeec6..1aa4893a2 100644 --- a/src/plugin-system/plugin.ts +++ b/src/plugin-system/plugin.ts @@ -1,12 +1,16 @@ import SquadServer from 'squad-server'; -import { PluginInterface } from './plugin-interface'; +import { Plugin as PluginInterface } from './plugin-interface'; -export class Plugin implements PluginInterface { +export class Plugin implements PluginInterface { protected server: SquadServer; + protected config: PluginConfig; - public constructor(server: SquadServer) { + constructor(server: SquadServer, config: PluginConfig) { // Store a reference to the server so the plugin can access it. this.server = server; + + // Store a reference to the config so the plugin can access it. + this.config = config; } public async mount() {} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 000000000..085c4de1e --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,3 @@ +import { logger } from './logger'; + +export { logger }; diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 000000000..c546d8a83 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,7 @@ +import winston from 'winston'; + +export const logger = winston.createLogger({ + level: 'info', + format: winston.format.combine(winston.format.colorize(), winston.format.simple()), + transports: [new winston.transports.Console({})] +}); From ac5e83c56d739a631fa9bc031f26016664dbe02e Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Wed, 7 Aug 2024 20:43:18 +0100 Subject: [PATCH 18/28] Remove reference to built-in plugins in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 557aa98e3..e16281c2f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ ## **About** -SquadJS is a scripting framework, designed for Squad servers, that aims to handle all communication and data collection to and from the servers. Using SquadJS as the base to any of your scripting projects allows you to easily write complex plugins without having to worry about the hassle of RCON or log parsing. However, for your convenience SquadJS comes shipped with multiple plugins already built for you allowing you to experience the power of SquadJS right away. +SquadJS is a powerful scripting framework designed to streamline Squad server management. By handling all server communication and data collection, SquadJS eliminates the complexities of RCON and log parsing. This allows developers to focus on creating sophisticated plugins without the underlying infrastructure headaches. Build your next Squad scripting project on a solid foundation with SquadJS.
    From c844d39722183f74aab419171b179bddc7cfe0d3 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 10 Aug 2024 17:16:20 +0100 Subject: [PATCH 19/28] Add licenses to plugins --- plugins/squadjs-api-plugin/LICENSE | 25 +++++++++++++++++++++++++ plugins/squadjs-command-plugin/LICENSE | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 plugins/squadjs-api-plugin/LICENSE create mode 100644 plugins/squadjs-command-plugin/LICENSE diff --git a/plugins/squadjs-api-plugin/LICENSE b/plugins/squadjs-api-plugin/LICENSE new file mode 100644 index 000000000..b47fa33d2 --- /dev/null +++ b/plugins/squadjs-api-plugin/LICENSE @@ -0,0 +1,25 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Copyright (c) 2024 Thomas Smyth + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/plugins/squadjs-command-plugin/LICENSE b/plugins/squadjs-command-plugin/LICENSE new file mode 100644 index 000000000..b47fa33d2 --- /dev/null +++ b/plugins/squadjs-command-plugin/LICENSE @@ -0,0 +1,25 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Copyright (c) 2024 Thomas Smyth + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. From 749a1b5749b56d3e5e8fda068a78bb10fbd815c6 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 10 Aug 2024 17:16:55 +0100 Subject: [PATCH 20/28] Update license dates --- plugins/squadjs-api-plugin/README.md | 2 +- plugins/squadjs-command-plugin/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/squadjs-api-plugin/README.md b/plugins/squadjs-api-plugin/README.md index b9c53d4f6..d45baa52a 100644 --- a/plugins/squadjs-api-plugin/README.md +++ b/plugins/squadjs-api-plugin/README.md @@ -51,7 +51,7 @@ The following options are available: ``` Boost Software License - Version 1.0 - August 17th, 2003 -Copyright (c) 2020 Thomas Smyth +Copyright (c) 2024 Thomas Smyth Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by diff --git a/plugins/squadjs-command-plugin/README.md b/plugins/squadjs-command-plugin/README.md index 7739edd48..29c1adf1c 100644 --- a/plugins/squadjs-command-plugin/README.md +++ b/plugins/squadjs-command-plugin/README.md @@ -43,7 +43,7 @@ The following options are available: ``` Boost Software License - Version 1.0 - August 17th, 2003 -Copyright (c) 2020 Thomas Smyth +Copyright (c) 2024 Thomas Smyth Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by From 8062953eab7398039bf62582b37122cb8a38825c Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sat, 10 Aug 2024 17:23:39 +0100 Subject: [PATCH 21/28] Chat logo links in READMEs --- plugins/squadjs-api-plugin/README.md | 4 ++-- plugins/squadjs-command-plugin/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/squadjs-api-plugin/README.md b/plugins/squadjs-api-plugin/README.md index d45baa52a..dcbace38a 100644 --- a/plugins/squadjs-api-plugin/README.md +++ b/plugins/squadjs-api-plugin/README.md @@ -1,7 +1,7 @@
    -Logo -Logo +Logo +Logo #### SquadJS API Plugin diff --git a/plugins/squadjs-command-plugin/README.md b/plugins/squadjs-command-plugin/README.md index 29c1adf1c..129247a2f 100644 --- a/plugins/squadjs-command-plugin/README.md +++ b/plugins/squadjs-command-plugin/README.md @@ -1,7 +1,7 @@
    -Logo -Logo +Logo +Logo #### SquadJS Command Plugin From 1a6462d9d1af06397913780b9962a4e155b5b987 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sun, 11 Aug 2024 01:11:58 +0100 Subject: [PATCH 22/28] Add Joi validation to configs --- plugins/squadjs-api-plugin/index.ts | 1 + plugins/squadjs-command-plugin/config.json | 3 +-- plugins/squadjs-command-plugin/index.ts | 27 ++++++++++--------- plugins/squadjs-command-plugin/package.json | 13 ++++++++- .../scripts/generate-config-type.ts | 17 ++++++++++++ .../src/plugin-config/schema/index.ts | 5 ++++ .../src/plugin-config/types/index.ts | 8 ++++++ 7 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 plugins/squadjs-command-plugin/scripts/generate-config-type.ts create mode 100644 plugins/squadjs-command-plugin/src/plugin-config/schema/index.ts create mode 100644 plugins/squadjs-command-plugin/src/plugin-config/types/index.ts diff --git a/plugins/squadjs-api-plugin/index.ts b/plugins/squadjs-api-plugin/index.ts index d8e1e7cf7..524feb6bf 100644 --- a/plugins/squadjs-api-plugin/index.ts +++ b/plugins/squadjs-api-plugin/index.ts @@ -4,6 +4,7 @@ import SquadServer from '../../squad-server'; import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; import { Plugin } from '../../src/plugin-system'; +// Define constants. const SQUADJS_API_DOMAIN: string = 'https://squadjs.thomas-smyth.uk'; // Define the plugin. diff --git a/plugins/squadjs-command-plugin/config.json b/plugins/squadjs-command-plugin/config.json index cdb2d8745..c7bc56f73 100644 --- a/plugins/squadjs-command-plugin/config.json +++ b/plugins/squadjs-command-plugin/config.json @@ -1,4 +1,3 @@ { - "disabled": false, - "mode": "broadcast" + "disabled": false } \ No newline at end of file diff --git a/plugins/squadjs-command-plugin/index.ts b/plugins/squadjs-command-plugin/index.ts index 2c140486a..8d46139d8 100644 --- a/plugins/squadjs-command-plugin/index.ts +++ b/plugins/squadjs-command-plugin/index.ts @@ -1,19 +1,22 @@ +import SquadServer from 'squad-server/index'; import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; import { Plugin } from '../../src/plugin-system'; -import SquadServer from 'squad-server/index'; - -interface PluginConfig { - mode: 'broadcast' | 'warn'; -} +import { pluginConfigSchema } from './src/plugin-config/schema'; +import { PluginConfig } from './src/plugin-config/types'; // Define the plugin. export default class SquadJSCommandPlugin extends Plugin { constructor(server: SquadServer, config?: PluginConfig) { - // Set default config. - config = { - mode: 'broadcast', - ...(config || {}) - }; + // Validate the plugin-config and set default values. + const result = pluginConfigSchema.validate(config); + + // Throw an error if the plugin-config is invalid. + if (result.error) { + throw result.error; + } + + // Define the plugin-config. + config = result.value; // Initiate the parent class. super(server, config); @@ -22,14 +25,14 @@ export default class SquadJSCommandPlugin extends Plugin { // eslint-disable-next-line @typescript-eslint/no-explicit-any async onChatMessage(data: any): Promise { // Check whether the message contained the SquadJS command. - const command = data.message.match(/!squadjs/); + const command = data.message.match(/^squadjs/); // Handle uses of the command. if (command) { // Define the response. const message = `This server is running SquadJS (v${SQUADJS_VERSION})!`; - // Send the response for the appropriate type. + // Send the response for the appropriate mode. switch (this.config.mode) { case 'broadcast': await this.server.rcon.broadcast(message); diff --git a/plugins/squadjs-command-plugin/package.json b/plugins/squadjs-command-plugin/package.json index 9fb5812a2..cec74bdbf 100644 --- a/plugins/squadjs-command-plugin/package.json +++ b/plugins/squadjs-command-plugin/package.json @@ -5,5 +5,16 @@ "repository": "https://github.com/Team-Silver-Sphere/SquadJS.git", "author": "Thomas Smyth ", "license": "BSL-1.0", - "private": true + "private": true, + "dependencies": { + "joi": "^17.13.3" + }, + "devDependencies": { + "joi-to-typescript": "^4.14.0", + "tsx": "^4.17.0", + "typescript": "^5.5.4" + }, + "scripts": { + "generate-config-type": "tsx scripts/generate-config-type.ts" + } } diff --git a/plugins/squadjs-command-plugin/scripts/generate-config-type.ts b/plugins/squadjs-command-plugin/scripts/generate-config-type.ts new file mode 100644 index 000000000..f6ba20396 --- /dev/null +++ b/plugins/squadjs-command-plugin/scripts/generate-config-type.ts @@ -0,0 +1,17 @@ +import fs from 'fs'; +import joiToTypescript from 'joi-to-typescript'; + +// Define constants. +const SCHEMA_DIRECTORY: string = './src/plugin-config/schema'; +const TYPE_DIRECTORY: string = './src/plugin-config/types'; + +// Generate the types. +(async () => { + // Delete the existing types. + fs.rmSync(TYPE_DIRECTORY, { recursive: true, force: true }); + + await joiToTypescript.convertFromDirectory({ + schemaDirectory: SCHEMA_DIRECTORY, + typeOutputDirectory: TYPE_DIRECTORY + }); +})(); diff --git a/plugins/squadjs-command-plugin/src/plugin-config/schema/index.ts b/plugins/squadjs-command-plugin/src/plugin-config/schema/index.ts new file mode 100644 index 000000000..e12eaa868 --- /dev/null +++ b/plugins/squadjs-command-plugin/src/plugin-config/schema/index.ts @@ -0,0 +1,5 @@ +import Joi from 'joi'; + +export const pluginConfigSchema = Joi.object({ + mode: Joi.string().valid('broadcast', 'warn').default('warn') +}).meta({ className: 'PluginConfig' }); diff --git a/plugins/squadjs-command-plugin/src/plugin-config/types/index.ts b/plugins/squadjs-command-plugin/src/plugin-config/types/index.ts new file mode 100644 index 000000000..f49672295 --- /dev/null +++ b/plugins/squadjs-command-plugin/src/plugin-config/types/index.ts @@ -0,0 +1,8 @@ +/** + * This file was automatically generated by joi-to-typescript + * Do not modify this file manually + */ + +export interface PluginConfig { + mode?: 'broadcast' | 'warn'; +} From ffa6b2516592aaedb6a2210ab9dd56ce33dc2b68 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sun, 11 Aug 2024 17:24:32 +0100 Subject: [PATCH 23/28] Move plugin index.ts file --- plugin-development.md | 2 +- plugins/squadjs-api-plugin/{ => src}/index.ts | 6 +++--- plugins/squadjs-command-plugin/{ => src}/index.ts | 8 ++++---- src/plugin-system/plugin-system.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename plugins/squadjs-api-plugin/{ => src}/index.ts (91%) rename plugins/squadjs-command-plugin/{ => src}/index.ts (84%) diff --git a/plugin-development.md b/plugin-development.md index 9d2806d83..69c3963f2 100644 --- a/plugin-development.md +++ b/plugin-development.md @@ -36,7 +36,7 @@ Follow the steps below to get started. * We will use this plugin as a template. * Do not forget to rename things and update the documentation appropriately. -3. Implement your plugin by modifying the `index.ts` file. +3. Implement your plugin by modifying the `src/index.ts` file. * Plugins are structured as classes. As documented below, you can implement various methods to trigger your plugin's logic at relevant times. * Your plugin will be automatically imported from this file by SquadJS. Do not rename the file and keep your plugin as its default export. * Feel free to create additional `.js`/`.ts` files if you wish to organise your plugin's code across multiple files. diff --git a/plugins/squadjs-api-plugin/index.ts b/plugins/squadjs-api-plugin/src/index.ts similarity index 91% rename from plugins/squadjs-api-plugin/index.ts rename to plugins/squadjs-api-plugin/src/index.ts index 524feb6bf..aaf4558c8 100644 --- a/plugins/squadjs-api-plugin/index.ts +++ b/plugins/squadjs-api-plugin/src/index.ts @@ -1,8 +1,8 @@ import axios from 'axios'; import Logger from 'core/logger'; -import SquadServer from '../../squad-server'; -import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; -import { Plugin } from '../../src/plugin-system'; +import SquadServer from '../../../squad-server/index.js'; +import { SQUADJS_VERSION } from '../../../squad-server/utils/constants.js'; +import { Plugin } from '../../../src/plugin-system'; // Define constants. const SQUADJS_API_DOMAIN: string = 'https://squadjs.thomas-smyth.uk'; diff --git a/plugins/squadjs-command-plugin/index.ts b/plugins/squadjs-command-plugin/src/index.ts similarity index 84% rename from plugins/squadjs-command-plugin/index.ts rename to plugins/squadjs-command-plugin/src/index.ts index 8d46139d8..2f48cc138 100644 --- a/plugins/squadjs-command-plugin/index.ts +++ b/plugins/squadjs-command-plugin/src/index.ts @@ -1,8 +1,8 @@ import SquadServer from 'squad-server/index'; -import { SQUADJS_VERSION } from '../../squad-server/utils/constants.js'; -import { Plugin } from '../../src/plugin-system'; -import { pluginConfigSchema } from './src/plugin-config/schema'; -import { PluginConfig } from './src/plugin-config/types'; +import { SQUADJS_VERSION } from '../../../squad-server/utils/constants.js'; +import { Plugin } from '../../../src/plugin-system'; +import { pluginConfigSchema } from './plugin-config/schema'; +import { PluginConfig } from './plugin-config/types'; // Define the plugin. export default class SquadJSCommandPlugin extends Plugin { diff --git a/src/plugin-system/plugin-system.ts b/src/plugin-system/plugin-system.ts index fd8029fb5..d93eb30ed 100644 --- a/src/plugin-system/plugin-system.ts +++ b/src/plugin-system/plugin-system.ts @@ -16,7 +16,7 @@ export class PluginSystem { // Get the path to the plugin. const pluginFolderPath = path.resolve('./plugins', pluginFolder); - const pluginPath = path.resolve(pluginFolderPath, 'index.ts'); + const pluginPath = path.resolve(pluginFolderPath, 'src/index.ts'); const pluginConfigPath = path.resolve(pluginFolderPath, './config.json'); // Initiate a variable to store the config in. From 863924e196496c331d0726d4578a6956b1e61ef3 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sun, 11 Aug 2024 17:25:28 +0100 Subject: [PATCH 24/28] Update code quality libraries --- .gitignore | 4 +--- .husky/.gitignore | 1 - .husky/pre-commit | 7 +------ package.json | 16 ++++++++-------- 4 files changed, 10 insertions(+), 18 deletions(-) delete mode 100644 .husky/.gitignore diff --git a/.gitignore b/.gitignore index a83345981..f1dd18863 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ # Project Files -*.sqlite -*.tmp - config-test*.json +*.tmp # Dependencies node_modules/ diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec13..000000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/.husky/pre-commit b/.husky/pre-commit index c43ea2578..caf55ba5e 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn run lint-staged -git add README.md -git add config.json +yarn run lint \ No newline at end of file diff --git a/package.json b/package.json index 86d20a82c..1c7031d6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "SquadJS", - "version": "4.1.0", + "name": "squadjs", + "version": "6.0.0-alpha.1", "type": "module", "repository": "https://github.com/Team-Silver-Sphere/SquadJS.git", "author": "Thomas Smyth ", @@ -12,7 +12,7 @@ "squad-server" ], "scripts": { - "prepare": "husky install", + "prepare": "husky", "lint": "eslint --fix . && prettier --write \"./**/*.{js,ts}\"", "lint-staged": "lint-staged", "run": "tsx index.ts" @@ -25,12 +25,12 @@ "devDependencies": { "@eslint/js": "^9.8.0", "@types/eslint__js": "^8.42.3", - "eslint": "^9.8.0", + "eslint": "^9.9.0", "globals": "^15.9.0", - "husky": "^5.1.3", - "lint-staged": "^10.5.4", - "prettier": "^2.2.1", + "husky": "^9.1.4", + "lint-staged": "^15.2.8", + "prettier": "^3.3.3", "typescript": "^5.5.4", - "typescript-eslint": "^8.0.0" + "typescript-eslint": "^8.0.1" } } From 6eab2e6015315b7049ec663e762a488cceb48095 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sun, 11 Aug 2024 17:28:02 +0100 Subject: [PATCH 25/28] Fix error in squadjs-api-plugin --- plugins/squadjs-api-plugin/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/squadjs-api-plugin/src/index.ts b/plugins/squadjs-api-plugin/src/index.ts index aaf4558c8..80e5a3055 100644 --- a/plugins/squadjs-api-plugin/src/index.ts +++ b/plugins/squadjs-api-plugin/src/index.ts @@ -13,7 +13,7 @@ export default class SquadJSAPIPlugin extends Plugin { private readonly interval: number = 5 * 60 * 1000; public constructor(server: SquadServer) { - super(server); + super(server, undefined); // Bind the ping method so this is accessible. this.ping = this.ping.bind(this); From 1572c2ad2114a87b089a09217389d8f47d807931 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sun, 11 Aug 2024 17:29:26 +0100 Subject: [PATCH 26/28] Refactor printLogo dependency --- index.ts | 2 +- package.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/index.ts b/index.ts index 29d92a148..bfe152455 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,4 @@ -import printLogo from 'squad-server/logo'; +import printLogo from './squad-server/utils/print-logo'; import { ConfigSystem } from './src/config-system'; import { PluginSystem } from './src/plugin-system'; diff --git a/package.json b/package.json index 1c7031d6d..376a479ae 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "run": "tsx index.ts" }, "dependencies": { - "squad-server": "1.0.0", "tsx": "^4.16.5", "winston": "^3.13.1" }, From cec0d01f063ed265c135a7cdb8f13d65f5262529 Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sun, 11 Aug 2024 18:53:31 +0100 Subject: [PATCH 27/28] Update tsconfig, fix ts errors, and remove workspaces --- package.json | 7 +- plugins/squadjs-api-plugin/src/index.ts | 6 +- plugins/squadjs-command-plugin/src/index.ts | 7 +- squad-server/index.js | 4 +- squad-server/layers/layers.js | 2 +- squad-server/log-parser/index.js | 2 +- squad-server/log-parser/player-connected.js | 2 +- squad-server/log-parser/player-damaged.js | 2 +- squad-server/log-parser/player-died.js | 2 +- squad-server/log-parser/player-possess.js | 2 +- squad-server/log-parser/player-revived.js | 2 +- squad-server/log-parser/player-un-possess.js | 2 +- squad-server/log-parser/player-wounded.js | 2 +- squad-server/rcon.js | 6 +- squad-server/utils/admin-lists.js | 2 +- squad-server/utils/any-id.js | 2 +- squad-server/utils/awn-api.js | 2 +- src/config-system/config-system.ts | 2 +- src/plugin-system/plugin-system.ts | 2 +- src/plugin-system/plugin.ts | 2 +- tsconfig.json | 108 ++++++++++++++++++- 21 files changed, 131 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 376a479ae..8fb948abc 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,6 @@ "repository": "https://github.com/Team-Silver-Sphere/SquadJS.git", "author": "Thomas Smyth ", "license": "BSL-1.0", - "private": true, - "workspaces": [ - "assets", - "core", - "squad-server" - ], "scripts": { "prepare": "husky", "lint": "eslint --fix . && prettier --write \"./**/*.{js,ts}\"", @@ -24,6 +18,7 @@ "devDependencies": { "@eslint/js": "^9.8.0", "@types/eslint__js": "^8.42.3", + "@types/node": "^22.2.0", "eslint": "^9.9.0", "globals": "^15.9.0", "husky": "^9.1.4", diff --git a/plugins/squadjs-api-plugin/src/index.ts b/plugins/squadjs-api-plugin/src/index.ts index 80e5a3055..1408d7936 100644 --- a/plugins/squadjs-api-plugin/src/index.ts +++ b/plugins/squadjs-api-plugin/src/index.ts @@ -1,6 +1,6 @@ import axios from 'axios'; -import Logger from 'core/logger'; -import SquadServer from '../../../squad-server/index.js'; +import Logger from '../../../core/logger'; +import SquadServer from '../../../squad-server'; import { SQUADJS_VERSION } from '../../../squad-server/utils/constants.js'; import { Plugin } from '../../../src/plugin-system'; @@ -9,7 +9,7 @@ const SQUADJS_API_DOMAIN: string = 'https://squadjs.thomas-smyth.uk'; // Define the plugin. export default class SquadJSAPIPlugin extends Plugin { - private instance: ReturnType; + private instance?: ReturnType; private readonly interval: number = 5 * 60 * 1000; public constructor(server: SquadServer) { diff --git a/plugins/squadjs-command-plugin/src/index.ts b/plugins/squadjs-command-plugin/src/index.ts index 2f48cc138..477923243 100644 --- a/plugins/squadjs-command-plugin/src/index.ts +++ b/plugins/squadjs-command-plugin/src/index.ts @@ -1,4 +1,4 @@ -import SquadServer from 'squad-server/index'; +import SquadServer from '../../../squad-server'; import { SQUADJS_VERSION } from '../../../squad-server/utils/constants.js'; import { Plugin } from '../../../src/plugin-system'; import { pluginConfigSchema } from './plugin-config/schema'; @@ -15,11 +15,8 @@ export default class SquadJSCommandPlugin extends Plugin { throw result.error; } - // Define the plugin-config. - config = result.value; - // Initiate the parent class. - super(server, config); + super(server, result.value); } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/squad-server/index.js b/squad-server/index.js index cedb1ff9a..5815b57f6 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -1,4 +1,4 @@ -import Logger from 'core/logger'; +import Logger from '../core/logger'; import { Layers } from './layers/index.js'; @@ -7,7 +7,7 @@ import Rcon from './rcon.js'; import fetchAdminLists from './utils/admin-lists.js'; import { isPlayerID, anyIDToPlayer, anyIDsToPlayers } from './utils/any-id.js'; -import { playerIdNames } from 'core/id-parser'; +import { playerIdNames } from '../core/id-parser'; export default class SquadServer { constructor(options = {}) { diff --git a/squad-server/layers/layers.js b/squad-server/layers/layers.js index 6e8290145..9557e1b24 100644 --- a/squad-server/layers/layers.js +++ b/squad-server/layers/layers.js @@ -1,6 +1,6 @@ import axios from 'axios'; -import Logger from 'core/logger'; +import Logger from '../../core/logger'; import Layer from './layer.js'; diff --git a/squad-server/log-parser/index.js b/squad-server/log-parser/index.js index 7f492c499..d14e095d4 100644 --- a/squad-server/log-parser/index.js +++ b/squad-server/log-parser/index.js @@ -1,4 +1,4 @@ -import LogParser from 'core/log-parser'; +import LogParser from '../../core/log-parser'; import AdminBroadcast from './admin-broadcast.js'; import DeployableDamaged from './deployable-damaged.js'; diff --git a/squad-server/log-parser/player-connected.js b/squad-server/log-parser/player-connected.js index 7a917a7f9..14ae4ef75 100644 --- a/squad-server/log-parser/player-connected.js +++ b/squad-server/log-parser/player-connected.js @@ -1,4 +1,4 @@ -import { iterateIDs, lowerID } from 'core/id-parser'; +import { iterateIDs, lowerID } from '../../core/id-parser'; export default { regex: diff --git a/squad-server/log-parser/player-damaged.js b/squad-server/log-parser/player-damaged.js index 892775f10..0b75f30b0 100644 --- a/squad-server/log-parser/player-damaged.js +++ b/squad-server/log-parser/player-damaged.js @@ -1,4 +1,4 @@ -import { iterateIDs, capitalID } from 'core/id-parser'; +import { iterateIDs, capitalID } from '../../core/id-parser'; export default { regex: diff --git a/squad-server/log-parser/player-died.js b/squad-server/log-parser/player-died.js index 5a3c6172f..6650b36e0 100644 --- a/squad-server/log-parser/player-died.js +++ b/squad-server/log-parser/player-died.js @@ -1,4 +1,4 @@ -import { iterateIDs, capitalID } from 'core/id-parser'; +import { iterateIDs, capitalID } from '../../core/id-parser'; export default { regex: diff --git a/squad-server/log-parser/player-possess.js b/squad-server/log-parser/player-possess.js index 5e7befea5..c848e88e0 100644 --- a/squad-server/log-parser/player-possess.js +++ b/squad-server/log-parser/player-possess.js @@ -1,4 +1,4 @@ -import { iterateIDs, capitalID } from 'core/id-parser'; +import { iterateIDs, capitalID } from '../../core/id-parser'; export default { regex: diff --git a/squad-server/log-parser/player-revived.js b/squad-server/log-parser/player-revived.js index 6901650f9..48ed5768f 100644 --- a/squad-server/log-parser/player-revived.js +++ b/squad-server/log-parser/player-revived.js @@ -1,4 +1,4 @@ -import { iterateIDs, capitalID } from 'core/id-parser'; +import { iterateIDs, capitalID } from '../../core/id-parser'; export default { // the names are currently the wrong way around in these logs diff --git a/squad-server/log-parser/player-un-possess.js b/squad-server/log-parser/player-un-possess.js index 55d162a33..b6599c4fa 100644 --- a/squad-server/log-parser/player-un-possess.js +++ b/squad-server/log-parser/player-un-possess.js @@ -1,4 +1,4 @@ -import { iterateIDs, capitalID } from 'core/id-parser'; +import { iterateIDs, capitalID } from '../../core/id-parser'; export default { regex: diff --git a/squad-server/log-parser/player-wounded.js b/squad-server/log-parser/player-wounded.js index 5a1e0a50c..60db718b9 100644 --- a/squad-server/log-parser/player-wounded.js +++ b/squad-server/log-parser/player-wounded.js @@ -1,4 +1,4 @@ -import { iterateIDs, capitalID } from 'core/id-parser'; +import { iterateIDs, capitalID } from '../../core/id-parser'; export default { regex: diff --git a/squad-server/rcon.js b/squad-server/rcon.js index 2355d6d27..12dec14dd 100644 --- a/squad-server/rcon.js +++ b/squad-server/rcon.js @@ -1,6 +1,6 @@ -import Logger from 'core/logger'; -import Rcon from 'core/rcon'; -import { iterateIDs, capitalID, lowerID } from 'core/id-parser'; +import Logger from '../core/logger'; +import Rcon from '../core/rcon'; +import { iterateIDs, capitalID, lowerID } from '../core/id-parser'; export default class SquadRcon extends Rcon { processChatPacket(decodedPacket) { diff --git a/squad-server/utils/admin-lists.js b/squad-server/utils/admin-lists.js index 1bebb9a16..1f164e4e6 100644 --- a/squad-server/utils/admin-lists.js +++ b/squad-server/utils/admin-lists.js @@ -3,7 +3,7 @@ import path from 'path'; import { fileURLToPath } from 'url'; import axios from 'axios'; -import Logger from 'core/logger'; +import Logger from '../../core/logger'; const __dirname = fileURLToPath(import.meta.url); diff --git a/squad-server/utils/any-id.js b/squad-server/utils/any-id.js index d8d0e5b0e..e2c8ae0a4 100644 --- a/squad-server/utils/any-id.js +++ b/squad-server/utils/any-id.js @@ -1,4 +1,4 @@ -import { playerIdNames } from 'core/id-parser'; +import { playerIdNames } from '../../core/id-parser'; /** * Check if given ID belongs to a player. diff --git a/squad-server/utils/awn-api.js b/squad-server/utils/awn-api.js index 200003646..ca3809da7 100644 --- a/squad-server/utils/awn-api.js +++ b/squad-server/utils/awn-api.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import Logger from 'core/logger'; +import Logger from '../../core/logger'; export default class AwnAPI { constructor(options) { diff --git a/src/config-system/config-system.ts b/src/config-system/config-system.ts index d1ee6a339..7e29d8306 100644 --- a/src/config-system/config-system.ts +++ b/src/config-system/config-system.ts @@ -1,4 +1,4 @@ -import Logger from 'core/logger'; +import Logger from '../../core/logger'; import fs from 'fs'; import SquadServer from '../../squad-server'; diff --git a/src/plugin-system/plugin-system.ts b/src/plugin-system/plugin-system.ts index d93eb30ed..96e4a945c 100644 --- a/src/plugin-system/plugin-system.ts +++ b/src/plugin-system/plugin-system.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import SquadServer from 'squad-server'; +import SquadServer from '../../squad-server'; import { logger } from '../utils'; import type { Plugin } from './plugin'; diff --git a/src/plugin-system/plugin.ts b/src/plugin-system/plugin.ts index 1aa4893a2..7e624728c 100644 --- a/src/plugin-system/plugin.ts +++ b/src/plugin-system/plugin.ts @@ -1,4 +1,4 @@ -import SquadServer from 'squad-server'; +import SquadServer from '../../squad-server'; import { Plugin as PluginInterface } from './plugin-interface'; export class Plugin implements PluginInterface { diff --git a/tsconfig.json b/tsconfig.json index 11c40c041..e8245d2bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,108 @@ { "compilerOptions": { - "allowJs": true, - "esModuleInterop": true + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": false, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ } -} \ No newline at end of file +} From a702494121b5e3187de3b1857466330eb34341be Mon Sep 17 00:00:00 2001 From: Thomas Smyth Date: Sun, 11 Aug 2024 19:08:31 +0100 Subject: [PATCH 28/28] Update gitignore to ignore additional plugins --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index f1dd18863..7aa670660 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Plugin Folders +plugins/ +!plugins/squadjs-api-plugin +!plugins/squadjs-command-plugin + # Project Files config-test*.json *.tmp