diff --git a/README.md b/README.md index 05817e9f..e5e7e77f 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ SquadJS relies on being able to access the Squad server log directory in order t 4. Start SquadJS: `node index.js`. ## Plugins + * [Discord Admin Broadcast](https://github.com/Thomas-Smyth/SquadJS/tree/master/plugins/discord-admin-broadcast) - Log admin broadcasts to Discord. * [Discord Admin Cam Logs](https://github.com/Thomas-Smyth/SquadJS/tree/master/plugins/discord-admin-cam-logs) - Log admin cam usage to Discord. * [Discord Chat](https://github.com/Thomas-Smyth/SquadJS/tree/master/plugins/discord-chat) - Log in game chat to Discord. * [Discord Chat Admin Request](https://github.com/Thomas-Smyth/SquadJS/tree/master/plugins/discord-chat-admin-request) - Log `!admin` alerts to Discord. diff --git a/index.js b/index.js index fac52696..4eb361b0 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ import Server from 'squad-server'; import SquadLayerFilter from 'connectors/squad-layer-filter'; import { + discordAdminBroadcast, discordAdminCamLogs, discordChat, discordChatAdminRequest, @@ -40,9 +41,12 @@ async function main() { // Discord Plugins const discordClient = new Discord.Client(); await discordClient.login('Discord Login Token'); + await discordAdminBroadcast(server, discordClient, 'discordChannelID'); await discordAdminCamLogs(server, discordClient, 'discordChannelID'); await discordChat(server, discordClient, 'discordChannelID'); - await discordChatAdminRequest(server, discordClient, 'discordChannelID', { pingGroups: ['discordGroupID'] }); + await discordChatAdminRequest(server, discordClient, 'discordChannelID', { + pingGroups: ['discordGroupID'] + }); await discordServerStatus(server, discordClient); await discordTeamkill(server, discordClient, 'discordChannelID'); diff --git a/package.json b/package.json index a168f582..59890b07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "SquadJS", - "version": "1.1.0", + "version": "1.2.0", "repository": "https://github.com/Thomas-Smyth/SquadJS.git", "author": "Thomas Smyth ", "license": "BSL-1.0", diff --git a/plugins/discord-admin-broadcast/README.md b/plugins/discord-admin-broadcast/README.md new file mode 100644 index 00000000..faceb6f8 --- /dev/null +++ b/plugins/discord-admin-broadcast/README.md @@ -0,0 +1,30 @@ +
+ +Logo + +#### SquadJS - Discord Admin Broadcast Plugin +
+ +## About +The Discord Admin Broadcast plugin streams admin broadcasts logs to Discord. + +## Installation +```js +// Place the following two lines at the top of your index.js file. +import Discord from 'discord.js'; +import { discordAdminBroadcast } from 'plugins'; + +// Place the following two lines in your index.js file before using an Discord plugins. +const discordClient = new Discord.Client(); +await discordClient.login('Discord Login Token'); // insert your Discord bot's login token here. + +// Place the following lines after all of the above. +await discordAdminBroadcast( + server, + discordClient, + 'discordChannelID', + { // options - the options included below display the defaults and can be removed for simplicity. + color: 16761867 // color of embed + } +); +``` diff --git a/plugins/discord-admin-broadcast/index.js b/plugins/discord-admin-broadcast/index.js new file mode 100644 index 00000000..8a42f36a --- /dev/null +++ b/plugins/discord-admin-broadcast/index.js @@ -0,0 +1,36 @@ +import { COPYRIGHT_MESSAGE } from 'core/config'; +import { LOG_PARSER_ADMIN_BROADCAST } from 'squad-server/events/log-parser'; + +export default async function(server, discordClient, channelID, options = {}) { + if (!server) throw new Error('DiscordChat must be provided with a reference to the server.'); + + if (!discordClient) throw new Error('DiscordChat must be provided with a Discord.js client.'); + + if (!channelID) throw new Error('DiscordChat must be provided with a channel ID.'); + + options = { + color: 16761867, + ...options + }; + + const channel = await discordClient.channels.fetch(channelID); + + server.on(LOG_PARSER_ADMIN_BROADCAST, async info => { + channel.send({ + embed: { + title: 'Admin Broadcast', + color: options.color, + fields: [ + { + name: 'Message', + value: `${info.message}` + } + ], + timestamp: info.time.toISOString(), + footer: { + text: COPYRIGHT_MESSAGE + } + } + }); + }); +} diff --git a/plugins/discord-chat-admin-request/README.md b/plugins/discord-chat-admin-request/README.md index dfd8f465..be36ee37 100644 --- a/plugins/discord-chat-admin-request/README.md +++ b/plugins/discord-chat-admin-request/README.md @@ -29,6 +29,7 @@ await discordChatAdminRequest( { // options - the options included below display the defaults and can be removed for simplicity. adminPrefix: '!admin', // prefix for an admin request. pingGroups: ['729853701308678154'], // Groups to ping on a request, leave empty for no ping. + pingDelay: 60 * 1000, // number of ms between pings. other messages will still be logged just without pings. ignoreChats: ['ChatSquad', 'ChatAdmin'], // an array of chats to not display. color: '#f44336' // color of embed } diff --git a/plugins/discord-chat-admin-request/index.js b/plugins/discord-chat-admin-request/index.js index edf886ff..a4e8cfd6 100644 --- a/plugins/discord-chat-admin-request/index.js +++ b/plugins/discord-chat-admin-request/index.js @@ -14,31 +14,35 @@ export default async function(server, discordClient, channelID, options = {}) { throw new Error('DiscordChatAdminRequest must be provided with a channel ID.'); } - const ignoreChats = options.ignoreChats || []; - const adminPrefix = options.adminPrefix || '!admin'; - const pingGroups = options.pingGroups || []; - options = { color: 16761867, + ignoreChats: [], + adminPrefix: '!admin', + pingGroups: [], + pingDelay: 60 * 1000, ...options }; + let lastPing = null; + const channel = await discordClient.channels.fetch(channelID); server.on(RCON_CHAT_MESSAGE, async info => { - if (ignoreChats.includes(info.chat)) return; - if (!info.message.startsWith(`${adminPrefix}`)) return; + if (options.ignoreChats.includes(info.chat)) return; + if (!info.message.startsWith(`${options.adminPrefix}`)) return; const playerInfo = await server.getPlayerBySteamID(info.steamID); - const trimmedMessage = info.message.replace(adminPrefix, '').trim(); + const trimmedMessage = info.message.replace(options.adminPrefix, '').trim(); if (trimmedMessage.length === 0) { - await server.rcon.warn(info.steamID, `Please specify what you would like help with when requesting an admin.`); + await server.rcon.warn( + info.steamID, + `Please specify what you would like help with when requesting an admin.` + ); return; } - channel.send({ - content: pingGroups.length ? pingGroups.map(groupID => `<@&${groupID}>`).join(' ') : '', + const message = { embed: { title: `${playerInfo.name} has requested admin support!`, color: options.color, @@ -67,6 +71,18 @@ export default async function(server, discordClient, channelID, options = {}) { text: COPYRIGHT_MESSAGE } } - }); + }; + + if (options.pingGroups.length > 0 && (lastPing === null || Date.now() - options.pingDelay > lastPing)) { + message.content = options.pingGroups.map(groupID => `<@&${groupID}>`).join(' '); + lastPing = Date.now(); + } + + channel.send(message); + + await server.rcon.warn( + info.steamID, + `An admin has been notified, please wait for us to get back to you.` + ); }); } diff --git a/plugins/discord-chat/README.md b/plugins/discord-chat/README.md index ccc3e228..6afae35b 100644 --- a/plugins/discord-chat/README.md +++ b/plugins/discord-chat/README.md @@ -25,7 +25,8 @@ await discordChat( 'discordChannelID', { // options - the options included below display the defaults and can be removed for simplicity. ignoreChats: ['ChatSquad', 'ChatAdmin'], // an array of chats to not display. - color: 16761867 // color of embed + color: 16761867, // color of embed + chatColors: { 'ChatAll': 16761867 } // change the color of chat types individually. Defaults to color above if not specified. } ); ``` diff --git a/plugins/discord-chat/index.js b/plugins/discord-chat/index.js index 8735ede2..d5179942 100644 --- a/plugins/discord-chat/index.js +++ b/plugins/discord-chat/index.js @@ -11,6 +11,9 @@ export default async function(server, discordClient, channelID, options = {}) { const ignoreChats = options.ignoreChats || ['ChatSquad', 'ChatAdmin']; options = { + chatColors: { + ...options.chatColors + }, color: 16761867, ...options }; @@ -25,7 +28,7 @@ export default async function(server, discordClient, channelID, options = {}) { channel.send({ embed: { title: info.chat, - color: options.color, + color: options.chatColors[info.chat] || options.color, fields: [ { name: 'Player', diff --git a/plugins/index.js b/plugins/index.js index 72a77f2e..0b226805 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -1,4 +1,5 @@ import autoTKWarn from './auto-tk-warn/index.js'; +import discordAdminBroadcast from './discord-admin-broadcast/index.js'; import discordAdminCamLogs from './discord-admin-cam-logs/index.js'; import discordChat from './discord-chat/index.js'; import discordChatAdminRequest from './discord-chat-admin-request/index.js'; @@ -14,6 +15,7 @@ import teamRandomizer from './team-randomizer/index.js'; export { autoTKWarn, + discordAdminBroadcast, discordAdminCamLogs, discordChat, discordChatAdminRequest, diff --git a/plugins/mapvote/mapvote-123.js b/plugins/mapvote/mapvote-123.js index c33d2721..51ddcdf5 100644 --- a/plugins/mapvote/mapvote-123.js +++ b/plugins/mapvote/mapvote-123.js @@ -53,8 +53,9 @@ export default function(server, options = {}) { }); await server.rcon.broadcast( - `A new map vote has started. Participate in the map vote by typing "!mapvote help" in chat.` + `A new map vote has started. Participate in the map vote by typing "!mapvote help" in chat. Map options to follow...` ); + await server.rcon.broadcast(mapvote.squadLayerFilter.getLayerNames().map((layerName, key) => `${key+1} - ${layerName}`).join(', ')); } return; } @@ -78,8 +79,9 @@ export default function(server, options = {}) { }); await server.rcon.broadcast( - `A new map vote has started. Participate in the map vote by typing "!mapvote help" in chat.` + `A new map vote has started. Participate in the map vote by typing "!mapvote help" in chat. Map options to follow...` ); + await server.rcon.broadcast(mapvote.squadLayerFilter.getLayerNames().map((layerName, key) => `${key+1} - ${layerName}`).join(', ')); return; } diff --git a/squad-server/events/log-parser.js b/squad-server/events/log-parser.js index 7d10bee4..5751794e 100644 --- a/squad-server/events/log-parser.js +++ b/squad-server/events/log-parser.js @@ -1,3 +1,12 @@ +/** Occurs when an admin broadcast is made. + * + * Data: + * - time - Date object of when the event occurred. + * - message - The message that was broadcasted. + * - from - Apparently who broadcasted it, but this is broken in Squad logs. + */ +const LOG_PARSER_ADMIN_BROADCAST = 'LOG_PARSER_ADMIN_BROADCAST'; + /** Occurs when a new layer is loaded. * * Data: @@ -115,6 +124,7 @@ const LOG_PARSER_PLAYER_WOUNDED = 'LOG_PARSER_PLAYER_WOUNDED'; const LOG_PARSER_SERVER_TICK_RATE = 'LOG_PARSER_SERVER_TICK_RATE'; export { + LOG_PARSER_ADMIN_BROADCAST, LOG_PARSER_NEW_GAME, LOG_PARSER_PLAYER_CONNECTED, LOG_PARSER_PLAYER_DAMAGED, diff --git a/squad-server/log-parser/rules/admin-broadcast.js b/squad-server/log-parser/rules/admin-broadcast.js new file mode 100644 index 00000000..55a6e108 --- /dev/null +++ b/squad-server/log-parser/rules/admin-broadcast.js @@ -0,0 +1,16 @@ +import { LOG_PARSER_ADMIN_BROADCAST } from '../../events/log-parser.js'; + +export default { + regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: ADMIN COMMAND: Message broadcasted <(.+)> from (.+)/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: args[2], + message: args[3], + from: args[4] + }; + + logParser.server.emit(LOG_PARSER_ADMIN_BROADCAST, data); + } +}; diff --git a/squad-server/log-parser/rules/index.js b/squad-server/log-parser/rules/index.js index 85cb483a..90f4caf9 100644 --- a/squad-server/log-parser/rules/index.js +++ b/squad-server/log-parser/rules/index.js @@ -1,3 +1,4 @@ +import AdminBroadcast from './admin-broadcast.js'; import NewGame from './new-game.js'; import PlayerConnected from './player-connected.js'; import PlayerDamaged from './player-damaged.js'; @@ -10,6 +11,7 @@ import ServerTickRate from './server-tick-rate.js'; import SteamIDConnected from './steamid-connected.js'; export default [ + AdminBroadcast, NewGame, PlayerConnected, PlayerDamaged,