From 4a586ac7015f4ec4c593857a7dce3960c7b1fd6b Mon Sep 17 00:00:00 2001 From: AnxietyisReal <96593068+AnxietyisReal@users.noreply.github.com> Date: Sun, 20 Aug 2023 10:17:22 +1000 Subject: [PATCH] Stable enough. --- src/MPLoop.ts | 64 ++++---- src/client.ts | 2 +- src/commands/mp.ts | 323 +++++++---------------------------------- src/disabled/mp.ts | 354 --------------------------------------------- 4 files changed, 77 insertions(+), 666 deletions(-) delete mode 100644 src/disabled/mp.ts diff --git a/src/MPLoop.ts b/src/MPLoop.ts index eae2e71..028f95c 100644 --- a/src/MPLoop.ts +++ b/src/MPLoop.ts @@ -5,9 +5,8 @@ import {FSPlayer, FSData, FSCareerSavegame, TServer} from './typings/interfaces' export default async(client:TClient, Channel:string, Message:string, Server:TServer, ServerName:string)=>{ let MPLoopPrefix = '[MPLoop] '; - let placeholder = '$_SERVERNAMEPLACEHOLDER'; //let httpRegex = /^(https?)(\:\/\/)/; - //let isServerOnline = false; + let isServerOnline = false; let playerData:Array = []; let noContentImage = 'https://cdn.discordapp.com/attachments/1118960531135541318/1140906691236479036/68efx1.png'; const msg = await (client.channels.resolve(Channel) as Discord.TextChannel).messages.fetch(Message); @@ -22,7 +21,7 @@ export default async(client:TClient, Channel:string, Message:string, Server:TSer decorator += player.name.includes('Toast') ? '<:toastv2:1132681026662056079>' : ''; return decorator } - + const HITALL = async()=>{ let sessionInit = {signal: AbortSignal.timeout(7500),headers:{'User-Agent':`Daggerbot - HITALL/fetch`}}; try { @@ -30,11 +29,15 @@ export default async(client:TClient, Channel:string, Message:string, Server:TSer const hitCSG = await fetch(Server.ip+'/feed/dedicated-server-savegame.html?code='+Server.code+'&file=careerSavegame', sessionInit).then(async r=>(client.xjs.xml2js(await r.text(), {compact: true}) as any).careerSavegame as FSCareerSavegame); if (!hitDSS ?? !hitCSG){ - if (hitDSS && !hitDSS.slots) return new Error(`${MPLoopPrefix}DSS failed with unknown slots table for ${ServerName}`); + if (hitDSS && !hitDSS.slots) return new Error(`${MPLoopPrefix}DSS failed with unknown slots table for ${client.MPServerCache[ServerName].name}`); if (hitDSS && !hitCSG) return msg.edit({content: 'No savegame found or autosave has ran.', embeds: [genericEmbed.setColor(client.config.embedColorOrange).setImage(noContentImage)]}); else return msg.edit({embeds: [serverErrorEmbed]}); } + // Truncate unnecessary parts of the name for the serverLog embed + client.MPServerCache[ServerName].name = hitDSS.server.name === 'Official Daggerwin Game Server' ? 'Daggerwin' : hitDSS.server.name === '! ! IRTGaming | Toast Test' ? 'Toast' : client.MPServerCache[ServerName].name; + //Second server name is unknown, will come back and update this later. + //Timescale formatting function formatTimescale(number:number,digits:number,icon:string){ var n = Number(number); @@ -43,51 +46,37 @@ export default async(client:TClient, Channel:string, Message:string, Server:TSer // Join/Leave log function playerLogEmbed(player:FSPlayer,joinLog:boolean){ - const logEmbed = new client.embed().setDescription(`**${player.name}${decoPlayer(player)}** ${joinLog ? 'joined' : 'left'} **${placeholder}** at `); + const logEmbed = new client.embed().setDescription(`**${player.name}${decoPlayer(player)}** ${joinLog ? 'joined' : 'left'} **${client.MPServerCache[ServerName].name}** at `); if (joinLog) return logEmbed.setColor(client.config.embedColorGreen); else if (player.uptime > 0) return logEmbed.setColor(client.config.embedColorRed).setFooter({text:`Farmed for ${client.formatPlayerUptime(player.uptime)}`}); else return logEmbed.setColor(client.config.embedColorRed); } - function playerLog(){ - // Player leaving - playersInCache.filter(x=>!playersOnServer.some(y=>y.name === x.name)).forEach(player=>serverLog.send({embeds:[playerLogEmbed(player,false)]})); - // Player joining - let playerObject; - if (playersInCache.length === 0 && client.uptime > 32010) playerObject = playersOnServer; - else if (playersInCache.length !== 0) playerObject = playersOnServer.filter(x=>!playersInCache.some(y=>y.name === x.name)); - if (playerObject) playerObject.forEach(x=>serverLog.send({embeds:[playerLogEmbed(x,true)]})); - - if (client.uptime > 32000){ - const Database:Array = JSON.parse(readFileSync(`src/database/${ServerName}PlayerData.json`,{encoding:'utf8',flag:'r+'})); - Database.push(hitDSS.slots?.used); - writeFileSync(`src/database/${ServerName}PlayerData.json`, JSON.stringify(Database)); - } - } - - //const serverIndicatorEmbed =(indicator:string)=>new client.embed().setTitle(`**${placeholder}** is now ${indicator}`).setColor(client.config.embedColorOrange).setTimestamp(); const serverLog = client.channels.resolve(client.config.mainServer.channels.fs_server_log) as Discord.TextChannel; const playersOnServer = hitDSS.slots?.players.filter(x=>x.isUsed); const playersInCache = client.MPServerCache[ServerName].players; if (!playersOnServer ?? playersOnServer === undefined) return new Error('[MPLoop] Empty array, ignoring...'); // For the love of god, stop throwing errors everytime. playersOnServer.forEach(player=>playerData.push(`**${player.name}${decoPlayer(player)}**\nFarming for ${client.formatPlayerUptime(player.uptime)}`)); + + // Player leaving + for (const player of playersInCache.filter(x=>!playersOnServer.some(y=>y.name === x.name))){ + if (player.uptime > 0) serverLog.send({embeds:[playerLogEmbed(player,false)]}); + } // Player joining + let playerObject; + if (!playersInCache.length && client.uptime > 32010) playerObject = playersOnServer; + if (playerObject) for (const player of playerObject) serverLog.send({embeds:[playerLogEmbed(player,true)]}); + else if (playersInCache.length) playerObject = playersOnServer.filter(x=>!playersInCache.some(y=>y.name === x.name)); + const Database:Array = JSON.parse(readFileSync(`src/database/${client.MPServerCache[ServerName].name}PlayerData.json`,{encoding:'utf8',flag:'r+'})); + Database.push(hitDSS.slots?.used); + writeFileSync(`src/database/${client.MPServerCache[ServerName].name}PlayerData.json`, JSON.stringify(Database)); + client.MPServerCache[ServerName].players = playersOnServer; - ServerName = client.MPServerCache[ServerName].name; // Truncate unnecessary parts of the name for the serverLog embed - if (hitDSS.server.name === 'Official Daggerwin Game Server') client.MPServerCache['mainServer'].name = 'Daggerwin'; - if (hitDSS.server.name === '! ! IRTGaming | Toast Test') client.MPServerCache['secondServer'].name = 'Toast'; - //Second server name is unknown, will come back and update this later. - playerLog(); - if (hitDSS.server.name.length < 1){ + if (hitDSS.server.name.length < 1) { msg.edit({content: 'This embed will resume when server is back online.', embeds: [genericEmbed.setColor(client.config.embedColorRed).setTitle('The server seems to be offline.')]}); - /* if (client.MPServerCache[ServerName].status === 'online') serverLog.send({embeds:[serverIndicatorEmbed('offline')]}); - client.MPServerCache[ServerName].status = 'offline' */ + client.MPServerCache[ServerName].status = 'offline' } else { - /* if (client.MPServerCache[ServerName]?.status === 'offline' ?? null){ - serverLog.send({embeds:[serverIndicatorEmbed('online')]}); - isServerOnline = true - } - client.MPServerCache[ServerName].status = 'online'; */ - + isServerOnline = true; + client.MPServerCache[ServerName].status = 'online'; const serverDetails = new client.embed().setColor(client.config.embedColor).setTitle('Server details').setFields( {name: 'Current map', value: hitDSS.server.mapName, inline: true}, {name: 'Server version', value: hitDSS.server.version, inline: true}, @@ -101,8 +90,7 @@ export default async(client:TClient, Channel:string, Message:string, Server:TSer } } catch(err) { msg.edit({content: null, embeds: [new client.embed().setColor(client.config.embedColorRed).setTitle('Host did not respond back in time')]}); - console.log(err) - //throw new Error(`Failed to make a request for ${ServerName}`, {cause: err.cause}) + throw new Error(`Failed to make a request for ${client.MPServerCache[ServerName].name}`, {cause: err.cause}) } } HITALL(); diff --git a/src/client.ts b/src/client.ts index d98f73e..21e8f5d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -106,7 +106,7 @@ export default class TClient extends Client { tls: false, family: 4 }).then(()=>console.log(this.logTime(), 'Successfully connected to MongoDB')).catch(()=>{throw new Error('Failed to connect to MongoDB'); exec('pm2 stop Daggerbot', {windowsHide:true})}) - this.login(this.tokens.beta); + this.login(this.tokens.main); for await (const file of readdirSync('dist/events')){ const eventFile = await import(`./events/${file}`); this.on(file.replace('.js',''), async(...args)=>eventFile.default.run(this,...args)) diff --git a/src/commands/mp.ts b/src/commands/mp.ts index 309fad0..410430d 100644 --- a/src/commands/mp.ts +++ b/src/commands/mp.ts @@ -7,13 +7,13 @@ import {FSData, TServer} from 'src/typings/interfaces.js'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - if (client.uptime < 30000) return interaction.reply('I have just restarted, please wait for MPLoop to initialize.') + if (client.uptime < 30000) return interaction.reply('I have just restarted, please wait for MPLoop to finish initializing.') const serverSelector = interaction.options.getString('server', true); if (interaction.channelId === '468835769092669461' && !client.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)); const database = await client.MPServer._content.findById(interaction.guildId); - //const debug = (database[serverSelector] as TServer); 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/fetch`}}).then(r=>r.json() as Promise); + const embed = new client.embed(); ({ players: async()=>{ const data = JSON.parse(readFileSync(path.join(`src/database/${client.MPServerCache[serverSelector].name}PlayerData.json`), {encoding: 'utf8'})).slice(client.statsGraph); @@ -151,108 +151,34 @@ export default { 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)}`; - interaction.reply({embeds:[new client.embed().setColor(Color).setTitle(endpoint.server.name).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'})]}) + 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'})]}) }, status: async()=>{ - interaction.reply(`$debug`) - } - })[interaction.options.getSubcommand()](); - }, - data: new Discord.SlashCommandBuilder() - .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) - .setChoices( - {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'} - ))) - .addSubcommand(x=>x - .setName('players') - .setDescription('Display players on server') - .addStringOption(x=>x - .setName('server') - .setDescription('The server to display players for') - .setRequired(true) - .setChoices( - {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'} - ))) - .addSubcommand(x=>x - .setName('url') - .setDescription('View or update the server URL') - .addStringOption(x=>x - .setName('server') - .setDescription('The server to update') - .setRequired(true) - .setChoices( - {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'} - )) - .addStringOption(x=>x - .setName('address') - .setDescription('The URL to the dedicated-server-stats.json file') - .setRequired(false))) - .addSubcommand(x=>x - .setName('info') - .setDescription('Display server information')) - .addSubcommand(x=>x - .setName('maintenance') - .setDescription('Toggle maintenance mode for #mp-active-players') - .addStringOption(x=>x - .setName('message') - .setDescription('The message to display in the channel'))) -} - -/* async function MPdata(client:TClient, interaction:Discord.ChatInputCommandInteraction, embed: Discord.EmbedBuilder, serverSelector: any) { - //let serverSelector; - let sessionInit = {signal: AbortSignal.timeout(7500),headers:{'User-Agent':`Daggerbot - MPdata/fetch`}}; - if (!await client.MPServer._content.findOne({_id:interaction.guildId})) return interaction.reply('This server isn\'t linked to the bot.'); - const ServerURL = await client.MPServer._content.findById(interaction.guildId); - if (!ServerURL[serverSelector].ip.match(/^(https?)(\:\/\/)/)) return interaction.reply(`The server IP for this server is currently invalid, please notify <@&${client.config.mainServer.roles.mpmanager}>`); - const hitDSS = await fetch(ServerURL[serverSelector].ip+'/feed/dedicated-server-stats.json?code='+ServerURL[serverSelector].code, sessionInit).then(r=>r.json()); -} - -export default { - run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - if (interaction.channelId === '468835769092669461' && !client.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)); - ({ - status: async()=>{ - const embed0 = new client.embed(); - const FSserver0 = await MPdata(client, interaction, embed0, 'mainServer'); - if (!FSserver0?.data) return console.log('FSserver0 failed - status'); + if (!endpoint) return console.log('Endpoint failed - status'); try { - if (FSserver0.data.server.name.length > 1){ - interaction.reply({embeds: [embed0.setTitle('Status/Details').setColor(client.config.embedColor).addFields( - {name: 'Server name', value: `${FSserver0?.data.server.name.length == 0 ? '\u200b' : `\`${FSserver0?.data.server.name}\``}`, inline: true}, - {name: 'Players', value: `${FSserver0.data.slots.used} out of ${FSserver0.data.slots.capacity}`, inline: true}, - {name: 'Current map', value: `${FSserver0?.data.server.mapName.length == 0 ? '\u200b' : FSserver0.data.server.mapName}`, inline: true}, - {name: 'Version', value: `${FSserver0?.data.server.version.length == 0 ? '\u200b' : FSserver0.data.server.version}`, inline: true}, - {name: 'In-game Time', value: `${('0' + Math.floor((FSserver0.data.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((FSserver0.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true} + 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} )]}) - } else if (FSserver0.data.server.name.length === 0) interaction.reply('Server is currently offline.') + } else if (endpoint.server.name.length === 0) interaction.reply('Server is currently offline.') } catch (err){ console.log(err) - interaction.reply('FSserver0 Error placeholder') + interaction.reply('Ah, you caught a rare one... Please notify <@&'+client.config.mainServer.roles.bottech+'>') } }, info: async()=>{ - const embed2 = new client.embed().setColor(client.config.embedColor) - const FSserver2 = await MPdata(client, interaction, embed2, 'secondServer') - if (!FSserver2?.data) return console.log('FSserver2 failed - info') - const MPURL = await client.MPServer._content.findById(interaction.guildId); - if (FSserver2.data.server.name.length == 0) embed2.setFooter({text: 'Server is currently offline.'}) - interaction.reply({embeds: [embed2.setDescription([ - `**Server name**: \`${FSserver2?.data.server.name.length === 0 ? '\u200b' : FSserver2?.data.server.name}\``, + if (!endpoint) return console.log('Endpoint failed - info') + if (endpoint.server.name.length < 1) embed.setFooter({text: 'Server is currently offline.'}) + interaction.reply({embeds: [embed.setColor(client.config.embedColor).setDescription([ + `**Server name**: \`${endpoint?.server.name.length === 0 ? '\u200b' : endpoint.server.name}\``, '**Password:** `mf4700`', '**Crossplay server**', - `**Map:** ${FSserver2.data.server.mapName.length < 1 ? 'Null Island' : FSserver2.data.server.mapName}`, - `**Mods:** [Click here](${MPURL.mainServer.ip}/mods.html) **|** [Direct Download](${MPURL.mainServer.ip}/all_mods_download?onlyActive=true)`, + `**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)`, '**Filters:** [Click here](https://discord.com/channels/468835415093411861/468835769092669461/926581585938120724)', 'Please see <#543494084363288637> for additional information.' ].join('\n'))]}); @@ -300,158 +226,6 @@ export default { } } }, - players: async()=>{ - const data = JSON.parse(readFileSync(path.join(`src/database/${client.MPServerCache[interaction.options.getString('server',true)].name}PlayerData.json`), {encoding: 'utf8'})).slice(client.statsGraph) - // handle negative days - data.forEach((change: number, i: number) => { - 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'; //'#111111'; - ctx.fillRect(0, 0, img.width, img.height); - - // grey horizontal lines - ctx.lineWidth = 5; - - let interval_candidates = []; - 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: Array = []; - - ctx.strokeStyle = '#202225'; //'#555B63'; - 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'; //'#3E4245'; - ctx.beginPath(); - ctx.lineTo(graphOrigin[0], y); - ctx.lineTo(graphOrigin[0] + graphSize[0], y); - ctx.stroke(); - ctx.closePath(); - if (even) ctx.strokeStyle = '#202225'; //'#555B63'; - previousY.push(y, i * chosen_interval[0]); - } - - // 30m mark - ctx.setLineDash([8, 16]); - ctx.beginPath(); - const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 30)); - ctx.lineTo(lastMonthStart, graphOrigin[1]); - ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]); - ctx.stroke(); - ctx.closePath(); - ctx.setLineDash([]); - - // draw points - ctx.lineWidth = 5; - - function getYCoordinate(value: number) { - return ((1 - (value / second_graph_top)) * graphSize[1]) + graphOrigin[1]; - } - - function colorAtPlayercount(playercount: number) { - if (playercount === first_graph_top) return client.config.embedColorRed as string; - else if (playercount > 9) return client.config.embedColorYellow as string; - else return client.config.embedColorGreen as string - } - let lastCoords: Array = []; - data.forEach((curPC: number /* current player count, i: number) => { - if (curPC < 0) curPC = 0; - const x = i * nodeWidth + graphOrigin[0]; - const y = getYCoordinate(curPC); - const nexPC /* next player count = data[i + 1]; - const prvPC /* previous player count = data[i - 1]; - const curColor = colorAtPlayercount(curPC); // color now - const prvColor = colorAtPlayercount(prvPC); // color at last point - if (curColor !== prvColor && !isNaN(prvPC) && lastCoords.length > 0) { // gradient should be used when the color between now and last point is not the same - // gradient from now to last point - const grd = ctx.createLinearGradient(lastCoords[0], lastCoords[1], x, y); - grd.addColorStop(0, colorAtPlayercount(prvPC)); // prev color at the beginning - grd.addColorStop(1, colorAtPlayercount(curPC)); // cur color at the end - // special case: playercount rises or falls rapidly accross all colors (eg. straight from red to green) - if (curColor !== client.config.embedColorYellow && prvColor !== client.config.embedColorYellow) { - const yellowY = getYCoordinate(10); // y coordinate at which line should be yellow - const stop = (yellowY - lastCoords[1]) / (y - lastCoords[1]); // between 0 and 1, where is yellowY between y and nextPointCoords[1] ? - grd.addColorStop(stop, client.config.embedColorYellow as string); // add a yellow stop to the gradient - } - ctx.strokeStyle = grd; - } else ctx.strokeStyle = colorAtPlayercount(curPC); - ctx.beginPath(); - if (lastCoords.length > 0) 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) return; // no ball because no vertical difference to next or prev point - else { - // ball - ctx.fillStyle = colorAtPlayercount(curPC); - ctx.beginPath(); - ctx.arc(x, y, ctx.lineWidth * 1.3, 0, 2 * Math.PI) - ctx.closePath(); - ctx.fill(); - } - }); - - // draw text - ctx.font = '400 ' + textSize + 'px sans-serif'; - ctx.fillStyle = 'white'; - - // highest value - const maxx = graphOrigin[0] + graphSize[0] + textSize / 2; - const maxy = previousY[0] + (textSize / 3); - ctx.fillText(previousY[1].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); - - // 30m - ctx.fillText('30 mins 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 embed1 = new client.embed(); - const FSserver1 = await MPdata(client, interaction, embed1, 'mainServer') - if (!FSserver1?.data) return console.log('FSserver1 failed - players') - embed1.setTitle(FSserver1?.data.server.name.length === 0 ? 'Offline' : FSserver1?.data.server.name) - .setDescription(`${FSserver1?.data.slots.used}/${FSserver1?.data.slots.capacity}`) - .setColor(FSserver1?.data.server.name.length === 0 ? client.config.embedColorRed : client.config.embedColor) - .setImage('attachment://FSStats.png'); - FSserver1?.data.slots.players.filter(x=>x.isUsed).forEach(player=>embed1.addFields({name: `${player.name} ${player.isAdmin ? '| admin' : ''}`, value: `Farming for ${client.formatPlayerUptime(player.uptime)}`})) - interaction.reply({embeds: [embed1], files: [new client.attachmentBuilder(img.toBuffer(),{name:'FSStats.png'})]}) - }, maintenance: ()=>{ if (client.config.mainServer.id == interaction.guildId) { 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 client.youNeedRole(interaction, 'mpmanager'); @@ -469,60 +243,63 @@ export default { interaction.reply({content: `<#${activePlayersChannel}> has been locked!`, ephemeral: true}); } } - } as any)[interaction.options.getSubcommand()](); + })[interaction.options.getSubcommand()](); }, data: new Discord.SlashCommandBuilder() .setName('mp') .setDescription('Display MP status and other things') .addSubcommand(x=>x .setName('status') - .setDescription('Check server status and details') + .setDescription('Display server status') .addStringOption(x=>x .setName('server') - .setDescription('Which server to pull info from') + .setDescription('The server to update') + .setRequired(true) .setChoices( {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'}) - .setRequired(true))) + {name: 'Second Server', value: 'secondServer'} + ))) .addSubcommand(x=>x .setName('players') - .setDescription('Check who\'s playing on the server') + .setDescription('Display players on server') .addStringOption(x=>x .setName('server') - .setDescription('Which server to pull the info from') + .setDescription('The server to display players for') + .setRequired(true) .setChoices( {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'}) - .setRequired(true))) - .addSubcommand(x=>x - .setName('info') - .setDescription('Provides you with server information such as filters and so on') - .addStringOption(x=>x - .setName('server') - .setDescription('Which server to pull the info from') - .setChoices( - {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'}) - .setRequired(true))) + {name: 'Second Server', value: 'secondServer'} + ))) .addSubcommand(x=>x .setName('url') - .setDescription('View the URL for this server\'s FSMP server or update the URL') + .setDescription('View or update the server URL') .addStringOption(x=>x .setName('server') - .setDescription('Which server to view/update the URL') + .setDescription('The server to update') + .setRequired(true) .setChoices( {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'}) - .setRequired(true)) + {name: 'Second Server', value: 'secondServer'} + )) .addStringOption(x=>x .setName('address') - .setDescription('Insert a \'dedicated-server-stats\' URL'))) + .setDescription('The URL to the dedicated-server-stats.json file') + .setRequired(false))) + .addSubcommand(x=>x + .setName('info') + .setDescription('Display server information') + .addStringOption(x=>x + .setName('server') + .setDescription('The server to display information for') + .setRequired(true) + .setChoices( + {name: 'Main Server', value: 'mainServer'}, + {name: 'Second Server', value: 'secondServer'} + ))) .addSubcommand(x=>x .setName('maintenance') - .setDescription('Lock/unlock "#mp-active-players" channel when server is unavailable to the public') + .setDescription('Toggle maintenance mode for #mp-active-players') .addStringOption(x=>x .setName('message') - .setDescription('The reason why is the server unavailable for?') - .setRequired(true))) + .setDescription('The message to display in the channel'))) } - */ \ No newline at end of file diff --git a/src/disabled/mp.ts b/src/disabled/mp.ts deleted file mode 100644 index c842f4b..0000000 --- a/src/disabled/mp.ts +++ /dev/null @@ -1,354 +0,0 @@ -import Discord from 'discord.js'; -import TClient from '../client.js'; -import path from 'node:path'; -import canvas from 'canvas'; -import {readFileSync} from 'node:fs'; - -async function MPdata(client:TClient, interaction:Discord.ChatInputCommandInteraction, embed: Discord.EmbedBuilder, serverSelector: any) { - //let serverSelector; - if (!await client.MPServer._content.findOne({_id:interaction.guildId})) return interaction.reply('This server isn\'t linked to the bot.'); - const ServerURL = await client.MPServer._content.findById(interaction.guildId); - if (!ServerURL) return interaction.reply(`No FS server found, please notify <@&${client.config.mainServer.roles.mpmanager}> to add it.`); - if (!ServerURL.mainServer.ip.match(/http|https/) ?? !ServerURL.secondServer.ip.match(/http|https/)) return interaction.reply(`The server IP for this server is currently invalid, please notify <@&${client.config.mainServer.roles.mpmanager}>`); - if (interaction.options.getString('server').includes('mainServer')) { - try { - serverSelector = await client.axios.get(ServerURL.mainServer.ip+'/feed/dedicated-server-stats.json?code='+ServerURL.mainServer.code, { - timeout: 2550, - headers: {'User-Agent': `Daggerbot - mp cmd/axios ${client.axios.VERSION}`} - }) - } catch(err) { - embed.setTitle('Host did not respond back in time').setColor(client.config.embedColorRed); - console.log(client.logTime(), 'DagMP:Main failed to fetch, host didn\'t respond in time.'); - interaction.reply('Host didn\'t respond back in time.') - return serverSelector - } - } else { - try { - serverSelector = await client.axios.get(ServerURL.secondServer.ip+'/feed/dedicated-server-stats.json?code='+ServerURL.secondServer.code, { - timeout: 2550, - headers: {'User-Agent': `Daggerbot - mp cmd/axios ${client.axios.VERSION}`} - }) - } catch(err) { - embed.setTitle('Host did not respond back in time').setColor(client.config.embedColorRed); - console.log(client.logTime(), 'DagMP:Second failed to fetch, host didn\'t respond in time.'); - interaction.reply('Host didn\'t respond back in time.') - return serverSelector - } - } - console.log(serverSelector) -} - -export default { - run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ - if (interaction.channelId === '468835769092669461' && !client.isStaff(interaction.member) && ['status', 'players'].includes(interaction.options.getSubcommand())) { - interaction.reply(`Please use <#739084625862852715> for \`/mp status/players\` commands to prevent clutter in this channel.`).then(()=>setTimeout(()=>interaction.deleteReply(), 6000)); - return; - } - ({ - status: async()=>{ - const embed0 = new client.embed(); - const FSserver0 = await MPdata(client, interaction, embed0, 'mainServer'); - if (!FSserver0?.data) return console.log('FSserver0 failed - status'); - try { - if (FSserver0.data.server.name.length > 1){ - interaction.reply({embeds: [embed0.setTitle('Status/Details').setColor(client.config.embedColor).addFields( - {name: 'Server name', value: `${FSserver0?.data.server.name.length == 0 ? '\u200b' : `\`${FSserver0?.data.server.name}\``}`, inline: true}, - {name: 'Players', value: `${FSserver0.data.slots.used} out of ${FSserver0.data.slots.capacity}`, inline: true}, - {name: 'Current map', value: `${FSserver0?.data.server.mapName.length == 0 ? '\u200b' : FSserver0.data.server.mapName}`, inline: true}, - {name: 'Version', value: `${FSserver0?.data.server.version.length == 0 ? '\u200b' : FSserver0.data.server.version}`, inline: true}, - {name: 'In-game Time', value: `${('0' + Math.floor((FSserver0.data.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((FSserver0.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true} - )]}) - } else if (FSserver0.data.server.name.length === 0) interaction.reply('Server is currently offline.') - } catch (err){ - console.log(err) - interaction.reply('FSserver0 Error placeholder') - }; - }, - info: async()=>{ - const embed2 = new client.embed().setColor(client.config.embedColor) - const FSserver2 = await MPdata(client, interaction, embed2, 'secondServer') - if (!FSserver2?.data) return console.log('FSserver2 failed - info') - const MPURL = await client.MPServer._content.findById(interaction.guildId); - if (FSserver2.data.server.name.length == 0) embed2.setFooter({text: 'Server is currently offline.'}) - interaction.reply({embeds: [embed2.setDescription([ - `**Server name**: \`${FSserver2?.data.server.name.length === 0 ? '\u200b' : FSserver2?.data.server.name}\``, - '**Password:** `mf4700`', - '**Crossplay server**', - `**Map:** ${FSserver2.data.server.mapName.length == 0 ? 'Null Island' : FSserver2.data.server.mapName}`, - `**Mods:** [Click here](${MPURL.mainServer.ip}/mods.html) **|** [Direct Download](${MPURL.mainServer.ip}/all_mods_download?onlyActive=true)`, - '**Filters:** [Click here](https://discord.com/channels/468835415093411861/468835769092669461/926581585938120724)', - 'Please see <#543494084363288637> for additional information.' - ].join('\n'))]}); - }, - url: async()=>{ - if (client.config.mainServer.id == interaction.guildId) { - 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 client.youNeedRole(interaction, 'mpmanager'); - } - const address = interaction.options.getString('address'); - if (!address){ - try { - const Url = await client.MPServer._content.findById(interaction.guildId); - if (interaction.options.getString('server').includes('mainServer')) { - if (Url.mainServer.ip && Url.mainServer.code) return interaction.reply(Url.mainServer.ip+'/feed/dedicated-server-stats.json?code='+Url.mainServer.code) - } else { - if (Url.secondServer.ip && Url.secondServer.code) return interaction.reply(Url.secondServer.ip+'/feed/dedicated-server-stats.json?code='+Url.secondServer.code) - } - } catch(err){ - console.log(`MPDB :: ${err}`); - 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 { - if (interaction.options.getString('server').includes('mainServer')) { - console.log(`MPDB :: Main Server\'s URL for ${interaction.guild.name} has been modified by ${interaction.member.displayName} (${interaction.member.id})`); - await client.MPServer._content.create({ - _id: interaction.guildId, mainServer: { ip: newURL[0], code: newURL[1] }, secondServer: { ip: 'unknown', code: 'unknown' } - }) - .then(()=>interaction.reply('This guild is not found in database, therefore I have created it for you.')) - .catch((err:Error)=>interaction.reply(`I ran into a brick wall while creating the server data:\n${err.message}`)) - } else { - console.log(`MPDB :: Second Server\'s URL for ${interaction.guild.name} has been modified by ${interaction.member.displayName} (${interaction.member.id})`) - await client.MPServer._content.findOneAndUpdate( - {_id: interaction.guildId},{$set: {secondServer: {ip: newURL[0], code: newURL[1]}}} - ) - .then(()=>interaction.reply('URL for second server successfully updated.')) - .catch((err:Error)=>interaction.reply(`I got hit by a flying fish while updating the server data:\n${err.message}`)) - } - } catch(err) { - if (interaction.options.getString('server').includes('mainServer')) { - const affected = await client.MPServer._content.findByIdAndUpdate({_id: interaction.guildId}, {$set: {mainServer:{ip: newURL[0], code: newURL[1]}}}) - if (affected) return interaction.reply('URL for Main Server successfully updated.') - } else { - const affected = await client.MPServer._content.findByIdAndUpdate({_id: interaction.guildId}, {$set: {secondServer:{ip: newURL[0], code: newURL[1]}}}) - if (affected) return interaction.reply('URL for Second Server successfully updated.') - } - } - } - }, - players: async()=>{ - const data = JSON.parse(readFileSync(path.join('src/database/MPPlayerData.json'), {encoding: 'utf8'})).slice(client.statsGraph) - // handle negative days - data.forEach((change: number, i: number) => { - 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'; //'#111111'; - ctx.fillRect(0, 0, img.width, img.height); - - // grey horizontal lines - ctx.lineWidth = 5; - - let interval_candidates = []; - 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: Array = []; - - ctx.strokeStyle = '#202225'; //'#555B63'; - 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'; //'#3E4245'; - ctx.beginPath(); - ctx.lineTo(graphOrigin[0], y); - ctx.lineTo(graphOrigin[0] + graphSize[0], y); - ctx.stroke(); - ctx.closePath(); - if (even) ctx.strokeStyle = '#202225'; //'#555B63'; - previousY.push(y, i * chosen_interval[0]); - } - - // 30m mark - ctx.setLineDash([8, 16]); - ctx.beginPath(); - const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 30)); - ctx.lineTo(lastMonthStart, graphOrigin[1]); - ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]); - ctx.stroke(); - ctx.closePath(); - ctx.setLineDash([]); - - // draw points - ctx.lineWidth = 5; - - function getYCoordinate(value: number) { - return ((1 - (value / second_graph_top)) * graphSize[1]) + graphOrigin[1]; - } - - function colorAtPlayercount(playercount: number) { - if (playercount === first_graph_top) return client.config.embedColorRed as string; - else if (playercount > 9) return client.config.embedColorYellow as string; - else return client.config.embedColorGreen as string - } - let lastCoords: Array = []; - data.forEach((curPC: number /* current player count */, i: number) => { - if (curPC < 0) curPC = 0; - const x = i * nodeWidth + graphOrigin[0]; - const y = getYCoordinate(curPC); - const nexPC /* next player count */ = data[i + 1]; - const prvPC /* previous player count */ = data[i - 1]; - const curColor = colorAtPlayercount(curPC); // color now - const prvColor = colorAtPlayercount(prvPC); // color at last point - if (curColor !== prvColor && !isNaN(prvPC) && lastCoords.length > 0) { // gradient should be used when the color between now and last point is not the same - // gradient from now to last point - const grd = ctx.createLinearGradient(lastCoords[0], lastCoords[1], x, y); - grd.addColorStop(0, colorAtPlayercount(prvPC)); // prev color at the beginning - grd.addColorStop(1, colorAtPlayercount(curPC)); // cur color at the end - // special case: playercount rises or falls rapidly accross all colors (eg. straight from red to green) - if (curColor !== client.config.embedColorYellow && prvColor !== client.config.embedColorYellow) { - const yellowY = getYCoordinate(10); // y coordinate at which line should be yellow - const stop = (yellowY - lastCoords[1]) / (y - lastCoords[1]); // between 0 and 1, where is yellowY between y and nextPointCoords[1] ? - grd.addColorStop(stop, client.config.embedColorYellow as string); // add a yellow stop to the gradient - } - ctx.strokeStyle = grd; - } else ctx.strokeStyle = colorAtPlayercount(curPC); - ctx.beginPath(); - if (lastCoords.length > 0) 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) return; // no ball because no vertical difference to next or prev point - else { - // ball - ctx.fillStyle = colorAtPlayercount(curPC); - ctx.beginPath(); - ctx.arc(x, y, ctx.lineWidth * 1.3, 0, 2 * Math.PI) - ctx.closePath(); - ctx.fill(); - } - }); - - // draw text - ctx.font = '400 ' + textSize + 'px sans-serif'; - ctx.fillStyle = 'white'; - - // highest value - const maxx = graphOrigin[0] + graphSize[0] + textSize / 2; - const maxy = previousY[0] + (textSize / 3); - ctx.fillText(previousY[1].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); - - // 30m - ctx.fillText('30 mins 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 embed1 = new client.embed(); - const FSserver1 = await MPdata(client, interaction, embed1, 'mainServer') - if (!FSserver1?.data) return console.log('FSserver1 failed - players') - embed1.setTitle(FSserver1?.data.server.name.length === 0 ? 'Offline' : FSserver1?.data.server.name) - .setDescription(`${FSserver1?.data.slots.used}/${FSserver1?.data.slots.capacity}`) - .setColor(FSserver1?.data.server.name.length === 0 ? client.config.embedColorRed : client.config.embedColor) - .setImage('attachment://FSStats.png'); - FSserver1?.data.slots.players.filter(x=>x.isUsed).forEach(player=>embed1.addFields({name: `${player.name} ${player.isAdmin ? '| admin' : ''}`, value: `Farming for ${client.formatPlayerUptime(player.uptime)}`})) - interaction.reply({embeds: [embed1], files: [new client.attachmentBuilder(img.toBuffer(),{name:'FSStats.png'})]}) - }, - maintenance: ()=>{ - if (client.config.mainServer.id == interaction.guildId) { - 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 client.youNeedRole(interaction, 'mpmanager'); - } - const maintenanceMessage = interaction.options.getString('message'); - const activePlayersChannel = '739084625862852715'; - const channel = (client.channels.cache.get(activePlayersChannel) as Discord.TextChannel); - if (channel.permissionOverwrites.cache.get(interaction.guildId).deny.has('SendMessages')) { - channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: true}, {type: 0, reason: `Unlocked by ${interaction.member.displayName}`}); - channel.send({embeds: [new client.embed().setColor(client.config.embedColor).setAuthor({name: interaction.member.displayName, iconURL: interaction.member.displayAvatarURL({size:1024})}).setTitle('🔓 Channel unlocked').setDescription(`**Reason:**\n${maintenanceMessage}`).setTimestamp()]}); - interaction.reply({content: `<#${activePlayersChannel}> has been unlocked!`, ephemeral: true}); - } else if (channel.permissionOverwrites.cache.get(interaction.guildId).allow.has('SendMessages')) { - channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: false}, {type: 0, reason: `Locked by ${interaction.member.displayName}`}); - channel.send({embeds: [new client.embed().setColor(client.config.embedColor).setAuthor({name: interaction.member.displayName, iconURL: interaction.member.displayAvatarURL({size:1024})}).setTitle('🔒 Channel locked').setDescription(`**Reason:**\n${maintenanceMessage}`).setTimestamp()]}); - interaction.reply({content: `<#${activePlayersChannel}> has been locked!`, ephemeral: true}); - } - } - } as any)[interaction.options.getSubcommand()](); - }, - data: new Discord.SlashCommandBuilder() - .setName('mp') - .setDescription('Display MP status and other things') - .addSubcommand(x=>x - .setName('status') - .setDescription('Check server status and details') - .addStringOption(x=>x - .setName('server') - .setDescription('Which server to pull info from') - .setChoices( - {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'}) - .setRequired(true))) - .addSubcommand(x=>x - .setName('players') - .setDescription('Check who\'s playing on the server') - .addStringOption(x=>x - .setName('server') - .setDescription('Which server to pull the info from') - .setChoices( - {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'}) - .setRequired(true))) - .addSubcommand(x=>x - .setName('info') - .setDescription('Provides you with server information such as filters and so on') - .addStringOption(x=>x - .setName('server') - .setDescription('Which server to pull the info from') - .setChoices( - {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'}) - .setRequired(true))) - .addSubcommand(x=>x - .setName('url') - .setDescription('View the URL for this server\'s FSMP server or update the URL') - .addStringOption(x=>x - .setName('server') - .setDescription('Which server to view/update the URL') - .setChoices( - {name: 'Main Server', value: 'mainServer'}, - {name: 'Second Server', value: 'secondServer'}) - .setRequired(true)) - .addStringOption(x=>x - .setName('address') - .setDescription('Insert a \'dedicated-server-stats\' URL'))) - .addSubcommand(x=>x - .setName('maintenance') - .setDescription('Lock/unlock "#mp-active-players" channel when server is unavailable to the public') - .addStringOption(x=>x - .setName('message') - .setDescription('The reason why is the server unavailable for?') - .setRequired(true))) -}