Skip to content

Commit

Permalink
Merge pull request #32 from xCausxn/feat/discordChatAdminRequest
Browse files Browse the repository at this point in the history
(feat) Add basic Discord Admin ping
  • Loading branch information
Thomas-Smyth authored Jul 8, 2020
2 parents 507cac7 + 69a288c commit 2b93d0d
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Project Files
squad-server/log-parser/test-data/
*.tmp

index-test.js

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ SquadJS relies on being able to access the Squad server log directory in order t
## Plugins
* [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.
* [Discord Teamkill](https://github.com/Thomas-Smyth/SquadJS/tree/master/plugins/discord-teamkill) - Log teamkills to Discord.
* [Discord Server Status](https://github.com/Thomas-Smyth/SquadJS/tree/master/plugins/discord-server-status) - Add a server status embed to Discord.
* [Map Vote](https://github.com/Thomas-Smyth/SquadJS/tree/master/plugins/mapvote) - In-game chat map voting system.
Expand Down
6 changes: 2 additions & 4 deletions connectors/squad-layer-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,8 @@ export default class SquadLayerFilter extends SquadLayersClass {

return (
!historyLayer ||
(
historyLayer.teamOne.faction !== layer.teamTwo.faction &&
historyLayer.teamTwo.faction !== layer.teamOne.faction
)
(historyLayer.teamOne.faction !== layer.teamTwo.faction &&
historyLayer.teamTwo.faction !== layer.teamOne.faction)
);
}

Expand Down
13 changes: 12 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import SquadLayerFilter from 'connectors/squad-layer-filter';
import {
discordAdminCamLogs,
discordChat,
discordChatAdminRequest,
discordServerStatus,
discordTeamkill,
influxdbLog,
influxdbLogDefaultSchema,
mapvote,
mysqlLog,
teamRandomizer
teamRandomizer,
seedingMessage
} from 'plugins';

async function main() {
Expand All @@ -25,6 +27,13 @@ async function main() {
queryPort: 27165,
rconPort: 21114,
rconPassword: 'password',

// Uncomment the following lines to read logs over FTP.
// ftpPort: 21,
// ftpUser: 'FTP Username',
// ftpPassword: 'FTP Password',
// logReaderMode: 'ftp',

logDir: 'C:/path/to/squad/log/folder'
});

Expand All @@ -33,6 +42,7 @@ async function main() {
await discordClient.login('Discord Login Token');
await discordAdminCamLogs(server, discordClient, 'discordChannelID');
await discordChat(server, discordClient, 'discordChannelID');
await discordChatAdminRequest(server, discordClient, 'discordChannelID', { pingGroups: ['discordGroupID'] });
await discordServerStatus(server, discordClient);
await discordTeamkill(server, discordClient, 'discordChannelID');

Expand All @@ -41,6 +51,7 @@ async function main() {
mapvote(server, 'didyoumean', squadLayerFilter, {});

teamRandomizer(server);
seedingMessage(server);

// MySQL Plugins
const mysqlPool = mysql.createPool({
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SquadJS",
"version": "1.0.13",
"version": "1.1.0",
"repository": "https://github.com/Thomas-Smyth/SquadJS.git",
"author": "Thomas Smyth <https://github.com/Thomas-Smyth>",
"license": "BSL-1.0",
Expand Down
36 changes: 36 additions & 0 deletions plugins/discord-chat-admin-request/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<div align="center">

<img src="../../assets/squadjs-logo.png" alt="Logo" width="500"/>

#### SquadJS - Discord Chat Admin Request Plugin

</div>

## About

The Discord Chat Admin Request plugin allows players to ping for an admin in discord. It can be configured to limit access to specific chats.

## Installation

```js
// Place the following two lines at the top of your index.js file.
import Discord from 'discord.js';
import { discordChatAdminRequest } 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 discordChatAdminRequest(
server,
discordClient,
'discordChannelID',
{ // 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.
ignoreChats: ['ChatSquad', 'ChatAdmin'], // an array of chats to not display.
color: '#f44336' // color of embed
}
);
```
72 changes: 72 additions & 0 deletions plugins/discord-chat-admin-request/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { COPYRIGHT_MESSAGE } from 'core/config';
import { RCON_CHAT_MESSAGE } from 'squad-server/events/rcon';

export default async function(server, discordClient, channelID, options = {}) {
if (!server) {
throw new Error('DiscordChatAdminRequest must be provided with a reference to the server.');
}

if (!discordClient) {
throw new Error('DiscordChatAdminRequest must be provided with a Discord.js client.');
}

if (!channelID) {
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,
...options
};

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;

const playerInfo = await server.getPlayerBySteamID(info.steamID);
const trimmedMessage = info.message.replace(adminPrefix, '').trim();

if (trimmedMessage.length === 0) {
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(' ') : '',
embed: {
title: `${playerInfo.name} has requested admin support!`,
color: options.color,
fields: [
{
name: 'Player',
value: playerInfo.name,
inline: true
},
{
name: 'SteamID',
value: `[${playerInfo.steamID}](https://steamcommunity.com/profiles/${info.steamID})`,
inline: true
},
{
name: 'Team & Squad',
value: `Team: ${playerInfo.teamID}, Squad: ${playerInfo.squadID || 'Unassigned'}`
},
{
name: 'Message',
value: trimmedMessage
}
],
timestamp: info.time.toISOString(),
footer: {
text: COPYRIGHT_MESSAGE
}
}
});
});
}
14 changes: 7 additions & 7 deletions plugins/discord-server-status/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ await discordClient.login('Discord Login Token'); // insert your Discord bot's l
await discordServerStatus(
server,
discordClient,
{ // options - the options included below display the defaults and can be removed for simplicity.
color: 16761867, // color of embed
colorGradient: true, // gradient color based on player count
connectLink: true, // show Steam connect link
command: '!server', // command used to send message
disableStatus: false // disable bot status as server status
}
{ // options - the options included below display the defaults and can be removed for simplicity.
color: 16761867, // color of embed
colorGradient: true, // gradient color based on player count
connectLink: true, // show Steam connect link
command: '!server', // command used to send message
disableStatus: false // disable bot status as server status
}
);
```
2 changes: 2 additions & 0 deletions plugins/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import autoTKWarn from './auto-tk-warn/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';
import discordDebug from './discord-debug/index.js';
import discordServerStatus from './discord-server-status/index.js';
import discordTeamkill from './discord-teamkill/index.js';
Expand All @@ -15,6 +16,7 @@ export {
autoTKWarn,
discordAdminCamLogs,
discordChat,
discordChatAdminRequest,
discordDebug,
discordServerStatus,
discordTeamkill,
Expand Down
126 changes: 24 additions & 102 deletions squad-server/log-parser/log-readers/ftp.js
Original file line number Diff line number Diff line change
@@ -1,117 +1,39 @@
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';

import ftp from 'basic-ftp';
import FTPTail from 'ftp-tail';

import sleep from 'core/utils/sleep';

// THIS LOG READER IS CURRENTLY UNDER DEVELOPMENT. IT IS ADVISED THAT YOU DO NOT USE IT.
export default class FTPLogReader {
export default class TailLogReader {
constructor(queueLine, options = {}) {
if (typeof queueLine !== 'function')
throw new Error('queueLine argument must be specified and be a function.');
if (!options.host) throw new Error('Host must be specified.');
if (!options.ftpUser) throw new Error('FTP user must be specified.');
if (!options.ftpPassword) throw new Error('FTP password must be specified.');
if (!options.remotePath) throw new Error('Remote path must be specified.');
if (!options.host) throw new Error('host argument must be specified.');
if (!options.ftpUser) throw new Error('user argument must be specified.');
if (!options.ftpPassword) throw new Error('password argument must be specified.');

this.reader = new FTPTail({
host: options.host,
port: options.ftpPort || 21,
user: options.ftpUser,
password: options.ftpPassword,
secure: options.ftpSecure || false,
timeout: options.ftpTimeout || 2000,
encoding: 'utf8',
verbose: options.ftpVerbose,

path: path.join(options.logDir, 'SquadGame.log'),

fetchInterval: options.ftpTetchInterval || 0,
maxTempFileSize: options.ftpMaxTempFileSize || 5 * 1000 * 1000 // 5 MB
});

this.queueLine = queueLine;
this.host = options.host;
this.port = options.ftpPort || 21;
this.user = options.ftpUser;
this.password = options.ftpPassword;
this.remotePath = options.logDir;
this.timeout = options.ftpTimeout || 3000;
this.encoding = 'utf8';
this.defaultInterval = options.ftpPullInterval || 500;
this.interval = this.defaultInterval;
this.tempFilePath = path.join(
process.cwd(),
'temp',
crypto
.createHash('md5')
.update(this.host.replace(/\./g, '-') + this.port + this.remotePath)
.digest('hex') + '.tmp'
);
this.maxTempFileSize = 5 * 1000 * 1000; // 5 MB
this.tailLastBytes = 100 * 1000;
this.reader.on('line', queueLine);
}

async watch() {
this.client = new ftp.Client(this.timeout);
this.client.ftp.encoding = this.encoding;

await this.client.access({
host: this.host,
port: this.port,
user: this.user,
password: this.password
});

this.interval = this.defaultInterval;
this.runLoop();
await this.reader.watch();
}

async unwatch() {
this.client.close();
this.interval = -1;
if (fs.existsSync(this.tempFilePath)) {
fs.unlinkSync(this.tempFilePath);
}
}

async runLoop() {
while (this.interval !== -1) {
const runStartTime = Date.now();

if (fs.existsSync(this.tempFilePath)) {
const { size } = fs.statSync(this.tempFilePath);
if (size > this.maxTempFileSize || !this.lastByteReceived) {
fs.unlinkSync(this.tempFilePath);
}
}

// If we haven't received any data yet, tail the end of the file; else download all data since last pull
if (this.lastByteReceived == null) {
const fileSize = await this.client.size(this.remotePath);
this.lastByteReceived =
fileSize - (this.tailLastBytes < fileSize ? this.tailLastBytes : fileSize);
}

// Download the data to a temp file, overwrite any previous data
// we overwrite previous data to calculate how much data we've received
await this.client.downloadTo(
fs.createWriteStream(this.tempFilePath, { flags: 'w' }),
this.remotePath,
this.lastByteReceived
);
const downloadSize = fs.statSync(this.tempFilePath).size;
this.lastByteReceived += downloadSize; // update the last byte marker - this is so we can get data since this position on the ftp download

const fileData = await new Promise((resolve, reject) => {
fs.readFile(this.tempFilePath, (err, data) => {
if (err) reject(err);
resolve(data);
});
});

fileData
.toString('utf8')
.split('\r\n')
.forEach(this.queueLine);

const ftpDataTime = Date.now();
const ftpDataTimeMs = ftpDataTime - runStartTime;

console.log('FTP Retrieve took: ' + ftpDataTimeMs + 'ms');

const waitTime = this.interval - ftpDataTimeMs;
if (waitTime > 0) {
await sleep(waitTime);
}
const runEndTime = Date.now();
console.log('Run time: ' + (runEndTime - runStartTime) + 'ms');
}
await this.reader.unwatch();
}
}
2 changes: 1 addition & 1 deletion squad-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"type": "module",
"dependencies": {
"async": "^3.2.0",
"basic-ftp": "^4.5.4",
"cli-progress": "^3.8.2",
"core": "1.0.0",
"ftp-tail": "^1.0.2",
"gamedig": "^2.0.20",
"moment": "^2.24.0",
"tail": "^2.0.3"
Expand Down

0 comments on commit 2b93d0d

Please sign in to comment.