1
0
mirror of https://github.com/toast-ts/Daggerbot-TS.git synced 2024-11-17 16:30:58 -05:00
Daggerbot-TS/src/commands/mp.ts

275 lines
14 KiB
TypeScript
Raw Normal View History

2023-05-23 01:14:17 -04:00
import Discord from 'discord.js';
2023-04-14 06:47:58 -04:00
import TClient from '../client.js';
2023-01-02 07:08:59 -05:00
import path from 'node:path';
2023-02-13 02:37:23 -05:00
import canvas from 'canvas';
2023-10-02 13:05:51 -04:00
import PalletLibrary from '../helpers/PalletLibrary.js';
2023-08-30 04:34:59 -04:00
import FormatPlayer from '../helpers/FormatPlayer.js';
2023-08-29 20:21:53 -04:00
import MessageTool from '../helpers/MessageTool.js';
import Logger from '../helpers/Logger.js';
2023-05-23 01:14:17 -04:00
import {readFileSync} from 'node:fs';
2023-09-01 00:32:11 -04:00
import {FSData} from '../typings/interfaces';
2023-08-19 21:04:14 -04:00
const serverChoices = [
{name: 'Main Server', value: 'mainServer'},
{name: 'Second Server', value: 'secondServer'}
]
2022-11-18 11:56:18 -05:00
export default {
2023-08-19 08:50:05 -04:00
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
2023-10-02 13:05:51 -04:00
if (client.uptime < 35000) return interaction.reply('I have just restarted, please wait for MPLoop to finish initializing.');
2023-08-24 12:06:39 -04:00
const serverSelector = interaction.options.getString('server');
2023-10-06 01:54:27 -04:00
if (['468835769092669461', '1149238561934151690'].includes(interaction.channelId) && !MessageTool.isStaff(interaction.member) && ['status', 'players'].includes(interaction.options.getSubcommand())) return interaction.reply('Please use <#739084625862852715> for `/mp status/players` commands to prevent clutter in this channel.').then(()=>setTimeout(()=>interaction.deleteReply(), 6000));
2023-08-19 08:50:05 -04:00
2023-10-02 18:40:03 -04:00
const database = await client.MPServer.findInCache(interaction.guildId);
2023-09-03 00:15:02 -04:00
const endpoint = await fetch(database[serverSelector].ip+'/feed/dedicated-server-stats.json?code='+database[serverSelector].code, {signal: AbortSignal.timeout(7500),headers:{'User-Agent':'Daggerbot - MPdata/undici'}}).then(r=>r.json() as Promise<FSData>);
2023-08-19 20:17:22 -04:00
const embed = new client.embed();
2023-08-19 08:50:05 -04:00
({
players: async()=>{
const data = JSON.parse(readFileSync(path.join(`src/database/${client.MPServerCache[serverSelector].name}PlayerData.json`), {encoding: 'utf8'})).slice(client.statsGraph);
// handle negative days
for (const [i, change] of data.entries()) if (change < 0) data[i] = data[i - 1] || data[i + 1] || 0;
const first_graph_top = 16;
const second_graph_top = 16;
const textSize = 40;
const img = canvas.createCanvas(1500, 750);
const ctx = img.getContext('2d');
const graphOrigin = [15, 65];
const graphSize = [1300, 630];
const nodeWidth = graphSize[0] / (data.length - 1);
ctx.fillStyle = '#36393f';
ctx.fillRect(0, 0, img.width, img.height);
// grey horizontal lines
ctx.lineWidth = 5;
const interval_candidates: [number, number, number][] = [];
for (let i = 4; i < 10; i++) {
const interval = first_graph_top / i;
if (Number.isInteger(interval)) {
let intervalString = interval.toString();
const reference_number = i * Math.max(intervalString.split('').filter(x => x === '0').length / intervalString.length, 0.3) * (['1', '2', '4', '5', '6', '8'].includes(intervalString[0]) ? 1.5 : 0.67)
interval_candidates.push([interval, i, reference_number]);
}
}
const chosen_interval = interval_candidates.sort((a, b) => b[2] - a[2])[0];
const previousY: number[] = [];
ctx.strokeStyle = '#202225';
for (let i = 0; i <= chosen_interval[1]; i++) {
const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]);
if (y < graphOrigin[1]) continue;
const even = ((i + 1) % 2) === 0;
if (even) ctx.strokeStyle = '#2c2f33';
ctx.beginPath();
ctx.lineTo(graphOrigin[0], y);
ctx.lineTo(graphOrigin[0] + graphSize[0], y);
ctx.stroke();
ctx.closePath();
if (even) ctx.strokeStyle = '#202225';
previousY.push(y, i * chosen_interval[0]);
}
// 30d mark
ctx.setLineDash([8, 16]);
ctx.beginPath();
const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 60));
ctx.lineTo(lastMonthStart, graphOrigin[1]);
ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]);
ctx.stroke();
ctx.closePath();
ctx.setLineDash([]);
// draw points
ctx.lineWidth = 5;
const gradient = ctx.createLinearGradient(0, graphOrigin[1], 0, graphOrigin[1] + graphSize[1]);
gradient.addColorStop(1 / 16, '#e62c3b'); // Red
gradient.addColorStop(5 / 16, '#ffea00'); // Yellow
gradient.addColorStop(12 / 16, '#57f287'); // Green
let lastCoords: number[] = [];
for (let [i, curPC /* current player count */] of data.entries()) {
if (curPC < 0) curPC = 0;
const x = i * nodeWidth + graphOrigin[0];
const y = ((1 - (curPC / second_graph_top)) * graphSize[1]) + graphOrigin[1];
const nexPC /* next player count */ = data[i + 1];
const prvPC /* previous player count */ = data[i - 1];
ctx.strokeStyle = gradient;
ctx.beginPath();
if (lastCoords.length) ctx.moveTo(lastCoords[0], lastCoords[1]);
// if the line being drawn is horizontal, make it go until it has to go down
if (y === lastCoords[1]) {
let newX = x;
for (let j = i + 1; j <= data.length; j++) {
if (data[j] === curPC) newX += nodeWidth;
else break;
}
ctx.lineTo(newX, y);
} else ctx.lineTo(x, y);
lastCoords = [x, y];
ctx.stroke();
ctx.closePath();
if (curPC !== prvPC || curPC !== nexPC) { // Ball if vertical different to next or prev point
// ball
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(x, y, ctx.lineWidth * 1.3, 0, 2 * Math.PI)
ctx.closePath();
ctx.fill();
};
}
2023-08-15 06:47:31 -04:00
2023-08-19 08:50:05 -04:00
// draw text
ctx.font = '400 ' + textSize + 'px sans-serif';
ctx.fillStyle = 'white';
// highest value
if (!isNaN(previousY.at(-2) as number)) {
const maxx = graphOrigin[0] + graphSize[0] + textSize / 2;
const maxy = (previousY.at(-2) as number) + (textSize / 3);
ctx.fillText((previousY.at(-1) as number).toLocaleString('en-US'), maxx, maxy);
}
// lowest value
const lowx = graphOrigin[0] + graphSize[0] + textSize / 2;
const lowy = graphOrigin[1] + graphSize[1] + (textSize / 3);
ctx.fillText('0 players', lowx, lowy);
// 30d
ctx.fillText('30 min ago', lastMonthStart, graphOrigin[1] - (textSize / 2));
// time ->
const tx = graphOrigin[0] + (textSize / 2);
const ty = graphOrigin[1] + graphSize[1] + (textSize);
ctx.fillText('time ->', tx, ty);
const playerData: string[] = [];
let Color = client.config.embedColor;
if (endpoint.slots.used === endpoint.slots.capacity) Color = client.config.embedColorRed;
else if (endpoint.slots.used > 8) Color = client.config.embedColorYellow;
else Color = client.config.embedColorGreen;
2023-08-30 04:34:59 -04:00
for (const player of endpoint.slots.players.filter(x=>x.isUsed)) playerData.push(`**${player.name}${FormatPlayer.decoratePlayerIcons(player)}**\nFarming for ${FormatPlayer.uptimeFormat(player.uptime)}`)
2023-08-19 08:50:05 -04:00
const slot = `${endpoint.slots.used}/${endpoint.slots.capacity}`;
const ingameTime = `${('0'+Math.floor((endpoint.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((endpoint.server.dayTime/60/1000)%60)).slice(-2)}`;
2023-08-19 20:17:22 -04:00
interaction.reply({embeds:[new client.embed().setColor(Color).setTitle(endpoint.server.name.length > 0 ? endpoint.server.name : 'Offline').setDescription(endpoint.slots.used < 1 ? '*No players online*' : playerData.join('\n\n')).setImage('attachment://FSStats.png').setAuthor({name:slot}).setFooter({text: 'Current time: '+ingameTime})], files: [new client.attachmentBuilder(img.toBuffer(),{name:'FSStats.png'})]})
2023-08-19 08:50:05 -04:00
},
status: async()=>{
2023-08-19 20:17:22 -04:00
if (!endpoint) return console.log('Endpoint failed - status');
2023-08-19 08:50:05 -04:00
try {
2023-08-19 20:17:22 -04:00
if (endpoint.server.name.length > 1){
interaction.reply({embeds: [embed.setTitle('Status/Details').setColor(client.config.embedColor).addFields(
{name: 'Server name', value: `${endpoint?.server.name.length === 0 ? '\u200b' : `\`${endpoint?.server.name}\``}`, inline: true},
{name: 'Players', value: `${endpoint.slots.used} out of ${endpoint.slots.capacity}`, inline: true},
{name: 'Current map', value: `${endpoint?.server.mapName.length === 0 ? '\u200b' : endpoint.server.mapName}`, inline: true},
{name: 'Version', value: `${endpoint?.server.version.length === 0 ? '\u200b' : endpoint.server.version}`, inline: true},
{name: 'In-game Time', value: `${('0' + Math.floor((endpoint.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((endpoint.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}
2023-08-19 08:50:05 -04:00
)]})
2023-08-19 20:17:22 -04:00
} else if (endpoint.server.name.length === 0) interaction.reply('Server is currently offline.')
2023-08-19 08:50:05 -04:00
} catch (err){
console.log(err)
2023-08-19 20:17:22 -04:00
interaction.reply('Ah, you caught a rare one... Please notify <@&'+client.config.mainServer.roles.bottech+'>')
2023-08-19 08:50:05 -04:00
}
},
info: async()=>{
2023-08-19 20:17:22 -04:00
if (!endpoint) return console.log('Endpoint failed - info')
if (endpoint.server.name.length < 1) embed.setFooter({text: 'Server is currently offline.'})
2023-08-29 20:21:53 -04:00
interaction.reply({embeds: [embed.setColor(client.config.embedColor).setDescription(MessageTool.concatMessage(
2023-08-19 20:17:22 -04:00
`**Server name**: \`${endpoint?.server.name.length === 0 ? '\u200b' : endpoint.server.name}\``,
2023-08-19 08:50:05 -04:00
'**Password:** `mf4700`',
'**Crossplay server**',
2023-08-19 20:17:22 -04:00
`**Map:** ${endpoint.server.mapName.length < 1 ? 'Null Island' : endpoint.server.mapName}`,
`**Mods:** [Click here](${database[serverSelector].ip}/mods.html) **|** [Direct Download](${database[serverSelector].ip}/all_mods_download?onlyActive=true)`,
2023-08-19 08:50:05 -04:00
'**Filters:** [Click here](https://discord.com/channels/468835415093411861/468835769092669461/926581585938120724)',
'Please see <#543494084363288637> for additional information.'
2023-08-29 20:21:53 -04:00
))]});
2023-08-19 08:50:05 -04:00
},
2023-09-02 13:27:00 -04:00
url: async()=>{
2023-08-19 08:50:05 -04:00
if (client.config.mainServer.id == interaction.guildId) {
2023-10-06 01:54:27 -04:00
if (!interaction.member.roles.cache.has(client.config.mainServer.roles.mpmanager) && !interaction.member.roles.cache.has(client.config.mainServer.roles.bottech) && !interaction.member.roles.cache.has(client.config.mainServer.roles.admin)) return MessageTool.youNeedRole(interaction, 'mpmanager');
2023-08-19 08:50:05 -04:00
}
const address = interaction.options.getString('address');
if (!address){
try {
2023-10-02 18:40:03 -04:00
const Url = await client.MPServer.findInCache(interaction.guildId);
2023-09-02 13:27:00 -04:00
if (Url[serverSelector].ip && Url[serverSelector].code) return interaction.reply(Url[serverSelector].ip+'/feed/dedicated-server-stats.json?code='+Url[serverSelector].code)
2023-08-19 08:50:05 -04:00
} catch(err){
Logger.forwardToConsole('error', 'MPDB', err);
2023-08-19 08:50:05 -04:00
interaction.reply(`\`\`\`${err}\`\`\``)
}
} else {
if (!address.match(/dedicated-server-stats/)) return interaction.reply('The URL does not match `dedicated-server-stats.xml`');
const newURL = address.replace('xml','json').split('/feed/dedicated-server-stats.json?code=');
try {
Logger.forwardToConsole('log', 'MPDB', `${serverSelector}\'s URL for ${interaction.guild.name} has been updated by ${interaction.member.displayName} (${interaction.member.id})`);
2023-09-02 13:27:00 -04:00
const affected = await client.MPServer._content.findByIdAndUpdate({_id: interaction.guildId}, {$set: {[serverSelector]: {ip: newURL[0], code: newURL[1]}}})
if (affected) return interaction.reply('URL successfully updated.')
} catch (err) {
Logger.forwardToConsole('log', 'MPDB', `${serverSelector}\'s URL for ${interaction.guild.name} has been created by ${interaction.member.displayName} (${interaction.member.id})`);
2023-09-02 13:27:00 -04:00
await client.MPServer._content.create({_id: interaction.guildId, [serverSelector]: { ip: newURL[0], code: newURL[1] }})
.then(()=>interaction.reply('This server doesn\'t have any data in the database, therefore I have created it for you.'))
.catch((err:Error)=>interaction.reply(`I got hit by a flying brick while trying to populate the server data:\n\`\`\`${err.message}\`\`\``))
2023-08-19 08:50:05 -04:00
}
}
2023-10-02 13:05:51 -04:00
},
pallets: async()=>{
if (!endpoint) return console.log('Endpoint failed - pallets');
const filter = endpoint.vehicles.filter(v=>v.type === 'pallet');
if (filter.length < 1) return interaction.reply('There are no pallets on the server.');
2023-10-02 13:14:57 -04:00
else interaction.reply(`There are currently ${filter.length} pallets on the server. Here\'s the breakdown:\`\`\`\n${Object.values(PalletLibrary(endpoint)).map(t=>`${t.name.padEnd(12)}${t.size}`).join('\n')}\`\`\``)
2023-09-02 13:27:00 -04:00
}
2023-08-19 20:17:22 -04:00
})[interaction.options.getSubcommand()]();
2023-03-05 05:04:10 -05:00
},
2023-05-23 01:14:17 -04:00
data: new Discord.SlashCommandBuilder()
2023-08-19 21:04:14 -04:00
.setName('mp')
.setDescription('Display MP status and other things')
.addSubcommand(x=>x
.setName('status')
.setDescription('Display server status')
.addStringOption(x=>x
.setName('server')
.setDescription('The server to update')
.setRequired(true)
2023-10-02 13:05:51 -04:00
.setChoices(...serverChoices)))
2023-08-19 21:04:14 -04:00
.addSubcommand(x=>x
.setName('players')
.setDescription('Display players on server')
.addStringOption(x=>x
.setName('server')
.setDescription('The server to display players for')
.setRequired(true)
2023-10-02 13:05:51 -04:00
.setChoices(...serverChoices)))
2023-09-02 13:27:00 -04:00
.addSubcommand(x=>x
2023-08-19 21:04:14 -04:00
.setName('url')
.setDescription('View or update the server URL')
.addStringOption(x=>x
.setName('server')
.setDescription('The server to update')
.setRequired(true)
2023-10-02 13:05:51 -04:00
.setChoices(...serverChoices))
2023-08-19 21:04:14 -04:00
.addStringOption(x=>x
.setName('address')
.setDescription('The URL to the dedicated-server-stats.json file')
2023-09-02 13:27:00 -04:00
.setRequired(false)))
2023-08-19 21:04:14 -04:00
.addSubcommand(x=>x
.setName('info')
.setDescription('Display server information')
.addStringOption(x=>x
.setName('server')
.setDescription('The server to display information for')
.setRequired(true)
2023-10-02 13:05:51 -04:00
.setChoices(...serverChoices)))
.addSubcommand(x=>x
.setName('pallets')
.setDescription('Check total amount of pallets on the server')
.addStringOption(x=>x
.setName('server')
.setDescription('The server to get amount of pallets from')
.setRequired(true)
.setChoices(...serverChoices)))
2023-01-22 13:14:38 -05:00
}