-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #32 from xCausxn/feat/discordChatAdminRequest
(feat) Add basic Discord Admin ping
- Loading branch information
Showing
11 changed files
with
159 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters