From 741302fafb32cfc3dc5584dc68f3db8184d19afd Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Tue, 15 Aug 2023 20:47:31 +1000 Subject: [PATCH 01/11] Painful progress.. --- src/MPLoop.ts | 155 ++++++++++------ src/client.ts | 5 +- src/commands/mp.ts | 361 ++++++++---------------------------- src/config.json | 10 +- src/index.ts | 7 +- src/models/MPServer.ts | 16 +- src/typings/interfaces.d.ts | 23 ++- tsconfig.json | 2 +- 8 files changed, 216 insertions(+), 363 deletions(-) diff --git a/src/MPLoop.ts b/src/MPLoop.ts index 83a59d5..1453859 100644 --- a/src/MPLoop.ts +++ b/src/MPLoop.ts @@ -5,55 +5,95 @@ import {FSPlayer, FSData, FSCareerSavegame} from './typings/interfaces'; export default async(client:TClient,Channel:string,Message:string,ServerName:string)=>{ if (!client.config.botSwitches.mpstats) return; + const noContentImage = 'https://cdn.discordapp.com/attachments/1118960531135541318/1140906691236479036/68efx1.png'; const msg = await (client.channels.resolve(Channel) as Discord.TextChannel).messages.fetch(Message); const embed = new client.embed(); let playerData:Array = []; let error:Boolean; let isServerOnline = false; - const Server = await client.MPServer._content.findById(client.config.mainServer.id); - - const DSS = { - data: {} as FSData, fetchResult: '' as string - }; - const CSG = { - data: {} as FSCareerSavegame, fetchResult: '' as string - }; - - if (!Server?.ip?.match(/http|https/)) return msg.edit({content: '*Detected an invalid IP\nContact MP Manager or Bot Tech*', embeds: null}); - async function serverData(client:TClient, URL:string){ - return await client.axios.get(URL, {timeout: 5000, maxContentLength: Infinity, headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}).catch((error:Error)=>error.message) + const fetchServer = await client.MPServer._content.findById(client.config.mainServer.id); + + const API = { // Fetch needed data from Farming Simulator server's API endpoints. + DSS: { + data: {} as FSData, + res: '' as string, + endpoint: '/feed/dedicated-server-stats.json?code=' + }, + CSG: { + data: {} as FSCareerSavegame, + res: '' as string, + endpoint: '/feed/dedicated-server-savegame.html?code=', + endpoint_file: '&file=careerSavegame' + } } - await Promise.all([serverData(client, Server.ip+'/feed/dedicated-server-stats.json?code='+Server.code), serverData(client, Server.ip+'/feed/dedicated-server-savegame.html?code='+Server.code+'&file=careerSavegame')]).then(function(results){ - if (typeof results[0] === 'string'){ - DSS.fetchResult = `DagMP DSS failed, ${results[0]}`; + + // Fetch needed data from database server and hit them with a GET request. + if (!fetchServer.mainServer.ip(/http|https/) ?? !fetchServer.secondServer.ip(/http|https/)) return msg.edit({content:'*This server doesn\'t seem to be setup yet!*', embeds:null}); + async function hitServer(client:TClient, URL:string){ + return await client.axios.get(URL, { + timeout: 7500, // Increased the timeout a bit just in case. + maxContentLength: Infinity, + headers: { + 'User-Agent': `Daggerbot/axios ${client.axios.VERSION}` + } + }).catch((err:Error)=>err.message) + } + await Promise.all([ + hitServer(client, fetchServer.mainServer.ip+API.DSS.endpoint+fetchServer.mainServer.code), + hitServer(client, fetchServer.mainServer.ip+API.CSG.endpoint+fetchServer.mainServer.code+API.CSG.endpoint_file), + hitServer(client, fetchServer.secondServer.ip+API.DSS.endpoint+fetchServer.secondServer.code), + hitServer(client, fetchServer.secondServer.ip+API.CSG.endpoint+fetchServer.secondServer.code+API.CSG.endpoint_file) + ]).then(function(results){ + // Main server's DSS + if (typeof results[0] === 'string') { + API.DSS.res = `DagMP:Main DSS failed, ${results[0]}`; embed.addFields({name:'DSS Status',value:results[0]}) - } else if (results[0].status != 200){ - DSS.fetchResult = `DagMP DSS failed with ${results[0].status + ' ' + results[0].statusText}`; - embed.addFields({name:'DSS Status',value:results[0].status + ' ' + results[0].statusText}) - } else DSS.data = results[0].data as FSData + } else if (results[0].status != 200) { + API.DSS.res = `DagMP:Main DSS failed with ${results[0].status +' '+ results[0].statusText}`; + embed.addFields({name:'DSS Status',value:results[0].status +' '+ results[0].statusText}) + } else API.DSS.data = results[0].data as FSData - if (typeof results[1] === 'string'){ - CSG.fetchResult = `DagMP CSG failed, ${results[1]}`; + // Main server's CSG + if (typeof results[1] === 'string') { + API.CSG.res = `DagMP:Main CSG failed, ${results[1]}`; embed.addFields({name:'CSG Status',value:results[1]}) - } else if (results[1].status != 200){ - if (results[1].status === 204) embed.setImage('https://httpcats.com/204.jpg'); - CSG.fetchResult = `DagMP CSG failed with ${results[1].status + ' ' + results[1].statusText}`; - embed.addFields({name:'CSG Status',value:results[1].status + ' ' + results[1].statusText}) - } else CSG.data = (client.xjs.xml2js(results[1].data,{compact:true}) as any).careerSavegame as FSCareerSavegame - }).catch(error=>console.error(error)); + } else if (results[1].status != 200) { + if (results[1].status === 204) embed.setImage(noContentImage); + API.CSG.res = `DagMP:Main CSG failed with ${results[1].status +' '+ results[1].statusText}`; + embed.addFields({name:'CSG Status',value:results[1].status +' '+ results[1].statusText}) + } else API.CSG.data = (client.xjs.xml2js(results[1].data,{compact:true}) as any).careerSavegame as FSCareerSavegame - if (DSS.fetchResult.length != 0){ + // Second server's DSS + if (typeof results[2] === 'string') { + API.DSS.res = `DagMP:Second DSS failed, ${results[2]}`; + embed.addFields({name:'DSS Status',value:results[2]}) + } else if (results[2].status != 200) { + API.DSS.res = `DagMP:Second DSS failed with ${results[2].status +' '+ results[2].statusText}`; + embed.addFields({name:'DSS Status',value:results[2].status +' '+ results[2].statusText}) + } else API.DSS.data = results[2].data as FSData + + // Second server's CSG + if (typeof results[3] === 'string') { + API.CSG.res = `DagMP:Second CSG failed, ${results[3]}`; + embed.addFields({name:'CSG Status',value:results[3]}) + } else if (results[3].status != 200) { + if (results[3].status === 204) embed.setImage(noContentImage); + API.CSG.res = `DagMP:Second CSG failed with ${results[3].status +' '+ results[3].statusText}`; + embed.addFields({name:'CSG Status',value:results[3].status +' '+ results[3].statusText}) + } else API.CSG.data = (client.xjs.xml2js(results[3].data,{compact:true}) as any).careerSavegame as FSCareerSavegame + }).catch((err:Error)=>console.error(err.message)) + + if (API.DSS.res.length != 0) { error = true; - if (DSS.data.slots === undefined) return; - console.log(client.logTime(), DSS.fetchResult); - } else if (CSG.fetchResult.length != 0){ + if (API.DSS.data.slots === undefined) return; + console.log(client.logTime(), API.DSS.res); + } else if (API.CSG.res.length != 0) { error = true; - console.log(client.logTime(), CSG.fetchResult); + console.log(client.logTime(), API.CSG.res); } - if (error){ // Blame Nawdic - embed.setTitle('Host is not responding').setColor(client.config.embedColorRed); - msg.edit({content:null, embeds: [embed]}) - return; + if (error) {// Nawdic broke it in his dream + embed.setTitle('Host did not respond back in time').setColor(client.config.embedColorRed); + return msg.edit({content:null, embeds:[embed]}) } //Timescale formatting @@ -81,42 +121,43 @@ export default async(client:TClient,Channel:string,Message:string,ServerName:str const serverIndicatorEmbed =(indicator:string)=>new client.embed().setTitle(`**${ServerName}** 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 = DSS.data.slots?.players.filter(x=>x.isUsed); - const playersInCache = client.MPServerCache.players; - if (!playersOnServer) return console.error('[MPLoop] Empty filter, ignoring...'); // For the love of god, stop throwing errors everytime. + const playersOnServer = API.DSS.data.slots?.players.filter(x=>x.isUsed); + const playersInCache = client.MPServerCache[ServerName].players; + if (!playersOnServer) return console.error(client.logTime(), '[MPLoop] Empty filter, ignoring...'); // For the love of god, stop throwing errors everytime. playersOnServer.forEach(player=>playerData.push(`**${player.name} ${player.isAdmin ? '| admin' : ''}**\nFarming for ${client.formatPlayerUptime(player.uptime)}`)); - ServerName = client.MPServerCache.name; - if (DSS.data.server.name === 'Official Daggerwin Game Server') client.MPServerCache.name = 'Daggerwin'; + ServerName = client.MPServerCache[ServerName].name; // Truncate unnecessary name for the embed + if (API.DSS.data.server.name === 'Official Daggerwin Game Server') client.MPServerCache['main'].name = 'Daggerwin'; + //Second server name is unknown, will come back and update this later. - if (DSS.data.server.name.length === 0){ + if (API.DSS.data.server.name.length === 0){ embed.setTitle('The server seems to be offline.').setColor(client.config.embedColorRed); msg.edit({content: 'This embed will resume when the server is back online.', embeds: [embed]}); - if (client.MPServerCache.status === 'online') serverLog.send({embeds:[serverIndicatorEmbed('offline')]}); - client.MPServerCache.status = 'offline'; + if (client.MPServerCache[ServerName].status === 'online') serverLog.send({embeds:[serverIndicatorEmbed('offline')]}); + client.MPServerCache[ServerName].status = 'offline'; } else { - if (client.MPServerCache.status === 'offline'){ + if (client.MPServerCache[ServerName].status === 'offline'){ serverLog.send({embeds:[serverIndicatorEmbed('online')]}); isServerOnline = true }; - client.MPServerCache.status = 'online'; + client.MPServerCache[ServerName].status = 'online'; const statusEmbed = new client.embed().setColor(client.config.embedColor).setTitle('Server details').setFields( - {name: 'Current Map', value: DSS.data.server.mapName.length === 0 ? '\u200b' : DSS.data.server.mapName, inline: true}, - {name: 'Version', value: DSS.data.server.version.length === 0 ? '\u200b' : DSS.data.server.version, inline: true}, - {name: 'In-game Time', value: `${('0'+Math.floor((DSS.data.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((DSS.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}, - {name: 'Slot Usage', value: isNaN(Number(CSG.data.slotSystem?._attributes.slotUsage)) === true ? 'Unavailable' : Number(CSG.data.slotSystem?._attributes.slotUsage).toLocaleString('en-us'), inline: true}, - {name: 'Autosave Interval', value: isNaN(Number(CSG.data.settings?.autoSaveInterval._text)) === true ? 'Unavailable' : Number(CSG.data.settings?.autoSaveInterval._text).toFixed(0)+' mins', inline:true}, - {name: 'Timescale', value: isNaN(Number(CSG.data.settings?.timeScale._text)) === true ? 'Unavailable' : formatTimescale(Number(CSG.data.settings?.timeScale._text), 0, 'x'), inline: true} + {name: 'Current Map', value: API.DSS.data.server.mapName.length === 0 ? '\u200b' : API.DSS.data.server.mapName, inline: true}, + {name: 'Version', value: API.DSS.data.server.version.length === 0 ? '\u200b' : API.DSS.data.server.version, inline: true}, + {name: 'In-game Time', value: `${('0'+Math.floor((API.DSS.data.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((API.DSS.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}, + {name: 'Slot Usage', value: isNaN(Number(API.CSG.data.slotSystem?._attributes.slotUsage)) === true ? 'Unavailable' : Number(API.CSG.data.slotSystem?._attributes.slotUsage).toLocaleString('en-us'), inline: true}, + {name: 'Autosave Interval', value: isNaN(Number(API.CSG.data.settings?.autoSaveInterval._text)) === true ? 'Unavailable' : Number(API.CSG.data.settings?.autoSaveInterval._text).toFixed(0)+' mins', inline:true}, + {name: 'Timescale', value: isNaN(Number(API.CSG.data.settings?.timeScale._text)) === true ? 'Unavailable' : formatTimescale(Number(API.CSG.data.settings?.timeScale._text), 0, 'x'), inline: true} ); - embed.setColor(client.config.embedColor).setTitle(DSS.data.server.name).setDescription(DSS.data.slots.used === 0 ? '*No players online*' : playerData.join('\n\n')).setAuthor({name:`${DSS.data.slots.used}/${DSS.data.slots.capacity}`}); + embed.setColor(client.config.embedColor).setTitle(API.DSS.data.server.name).setDescription(API.DSS.data.slots.used === 0 ? '*No players online*' : playerData.join('\n\n')).setAuthor({name:`${API.DSS.data.slots.used}/${API.DSS.data.slots.capacity}`}); msg.edit({content:'This embed updates every minute.',embeds:[statusEmbed,embed]}); } if (!isServerOnline){ playerLog(); - const Database:Array = JSON.parse(readFileSync('src/database/MPPlayerData.json',{encoding:'utf8',flag:'r+'})); - Database.push(DSS.data.slots?.used); - writeFileSync('src/database/MPPlayerData.json', JSON.stringify(Database)); - client.MPServerCache.players = playersOnServer + const Database:Array = JSON.parse(readFileSync(`src/database/${ServerName}PlayerData.json`,{encoding:'utf8',flag:'r+'})); + Database.push(API.DSS.data.slots?.used); + writeFileSync(`src/database/${ServerName}PlayerData.json`, JSON.stringify(Database)); + client.MPServerCache[ServerName].players = playersOnServer } } diff --git a/src/client.ts b/src/client.ts index 4288af6..bb1d0e8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -85,7 +85,10 @@ export default class TClient extends Client { this.punishments = new punishments(this); this.bannedWords = new bannedWords(this); this.MPServer = new MPServer(this); - this.MPServerCache = {players: [], status: null, name: null} as MPServerCache; + this.MPServerCache = { + main: { players: [], status: null, name: null }, + second: { players: [], status: null, name: null } + } as MPServerCache; this.suggestion = new suggestion(this); this.tags = new tags(this); this.repeatedMessages = {}; diff --git a/src/commands/mp.ts b/src/commands/mp.ts index 094bb60..411222a 100644 --- a/src/commands/mp.ts +++ b/src/commands/mp.ts @@ -4,303 +4,90 @@ 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) { - let FSserver; - 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.ip.match(/http|https/)) return interaction.reply(`The server IP for this server is currently invalid, please notify <@&${client.config.mainServer.roles.mpmanager}>`); - - // Fetch dss - try {// I am aware timeout has decreased from 2800 to 2588 to fit within Discord's interaction timeouts (3s) -Toast - FSserver = await client.axios.get(ServerURL.ip+'/feed/dedicated-server-stats.json?code='+ServerURL.code, {timeout: 2588, headers: {'User-Agent': `Daggerbot - mp cmd/axios ${client.axios.VERSION}`}}) - } catch(err) { - // Blame Nawdic - embed.setTitle('Host is not responding.'); - embed.setColor(client.config.embedColorRed); - console.log(client.logTime(), 'DagMP failed to fetch, host didn\'t respond in time.'); - return interaction.reply('Server didn\'t respond in time, please try again later.'); - } return FSserver -} - export default { run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + async function hitEndpoint(){ + const array = (await client.MPServer?._content.find())?.map(x=>x._id).filter(c=>['mainServer','secondServer'].includes(c)); + console.log(array?.map(c=>c)); + + /* const database = { + mainServer: (await client.MPServer._content.findById(interaction.guildId)).mainServer, + secondServer: (await client.MPServer._content.findById(interaction.guildId)).secondServer + } + const endpoint = '/feed/dedicated-server-stats.json?code='; + if (serverSelector === 'mainServer') return database.mainServer.ip+endpoint+database.mainServer.code; + else if (serverSelector === 'secondServer') return database.secondServer.ip+endpoint+database.secondServer.code; + const Server = await client.axios.get(serverSelector, { + timeout: 7500, + headers: {'User-Agent':`Daggerbot - mp cmd/axios ${client.axios.VERSION}`} + }) */ + } + 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); - 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') - }; + hitEndpoint() + interaction.reply('x') }, - info: async()=>{ - const embed2 = new client.embed().setColor(client.config.embedColor) - const FSserver2 = await MPdata(client, interaction, embed2) - 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.ip}/mods.html) **|** [Direct Download](${MPURL.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 (Url.ip && Url.code) return interaction.reply(`${Url.get('ip')}`+'/feed/dedicated-server-stats.json?code='+`${Url.get('code')}`) - } catch(err){ - console.log(`MPDB :: ${err}`); - interaction.reply('**Database error:**\nThis server does not have the URL saved, try adding one.') - } - }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{ - console.log(`MPDB :: URL for ${interaction.guild.name} has been updated by ${interaction.member.displayName} (${interaction.member.id})`); - await client.MPServer._content.create({_id: interaction.guildId, ip: newURL[0], code: newURL[1], timesUpdated: 0}); - return interaction.reply('This server is now linked and URL has been added.'); - } catch(err){ - const affectedValues = await client.MPServer._content.findByIdAndUpdate({_id: interaction.guildId}, {ip: newURL[0], code: newURL[1]}); - await client.MPServer._increment(interaction.guildId); - if (affectedValues) return interaction.reply('URL 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) - 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}); - } - }/*, - series: ()=>{ - const embed3 = new client.embed().setColor(client.config.embedColor).setTitle('How to join the Daggerwin MP series') - .setDescription([ - 'To join the Daggerwin MP series, you first need to:', - '**1:** Note that only PC players can join the MP series due to the mods that are used.', - '**2:** Become a YouTube Member by pressing the `Join` button on [Daggerwin\'s YouTube page](https://www.youtube.com/c/Daggerwin) next to the `Subscribe` button.', - '**3:** Link your YouTube account to your Discord account via Settings>Connections>Add connection. Be sure that you link the same YouTube account you used to become a channel member.', - '**4:** If you don\'t receive the role within a day or so, please message an Admin and they will sort it out.', - '**5:** Take a look in <#511657659364147200> to get information on how to join the server.' - ].join('\n')); - interaction.reply({embeds: [embed3]}) - }*/ + info: async()=>{}, + url: async()=>{}, + players: async()=>{} } 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')) - .addSubcommand(x=>x - .setName('players') - .setDescription('Check who\'s playing on the server')) - .addSubcommand(x=>x - .setName('info') - .setDescription('Provides you with server information such as filters and so on')) - .addSubcommand(x=>x - .setName('url') - .setDescription('View the URL for this server\'s FSMP server or update the URL') - .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)))/* - .addSubcommand(x=>x - .setName('series') - .setDescription('Step-by-step on joining Daggerwin\'s MP series'))*/ + .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))) } diff --git a/src/config.json b/src/config.json index f5e970c..943c242 100644 --- a/src/config.json +++ b/src/config.json @@ -12,8 +12,14 @@ "929807948748832798", "468835415093411861", "1058183358267543552", "549114074273677314" ], "MPStatsLocation": { - "channel": "543494084363288637", - "message": "1023699243183112192" + "main": { + "channel": "543494084363288637", + "message": "1023699243183112192" + }, + "second": { + "channel": "", + "message": "" + } }, "botSwitches": { "dailyMsgsBackup": true, diff --git a/src/index.ts b/src/index.ts index db3eb15..54eb02b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,11 +66,14 @@ if (client.config.botSwitches.music){ } // YouTube Upload notification and Daggerwin MP loop -setInterval(()=>MPLoop(client, client.config.MPStatsLocation.channel, client.config.MPStatsLocation.message, 'Daggerwin'), 60000); +setInterval(()=>{ + MPLoop(client, client.config.MPStatsLocation.main.channel, client.config.MPStatsLocation.main.message, 'Daggerwin') + MPLoop(client, client.config.MPStatsLocation.second.channel, client.config.MPStatsLocation.second.message, 'SecondServer') +}, 60000); setInterval(async()=>{ client.YTLoop('UCQ8k8yTDLITldfWYKDs3xFg', 'Daggerwin', '528967918772551702'); // 528967918772551702 = #videos-and-streams client.YTLoop('UCguI73--UraJpso4NizXNzA', 'Machinery Restorer', '767444045520961567') // 767444045520961567 = #machinery-restorer -}, 600000) +}, 300000) // Event loop for punishments and daily msgs setInterval(async()=>{ diff --git a/src/models/MPServer.ts b/src/models/MPServer.ts index 8d7e8b8..fc83a4d 100644 --- a/src/models/MPServer.ts +++ b/src/models/MPServer.ts @@ -3,9 +3,14 @@ import mongoose from 'mongoose'; const Schema = mongoose.model('mpserver', new mongoose.Schema({ _id: {type: String, required:true}, - ip: {type: String}, - code: {type: String}, - timesUpdated: {type: Number, required: true} + mainServer: {required:true, type: new mongoose.Schema({ + ip: {type: String, required:true}, + code: {type: String, required:true} + }, {versionKey: false})}, + secondServer: {required:true, type: new mongoose.Schema({ + ip: {type: String, required:true}, + code: {type: String, required:true} + }, {versionKey: false})}, }, {versionKey: false})); export default class MPServer extends Schema { @@ -16,9 +21,4 @@ export default class MPServer extends Schema { this.client = client; this._content = Schema; } - async _increment(serverId: string){ - const server = await this._content.findById(serverId) - if (server) await this._content.findByIdAndUpdate(server, {timesUpdated: server.timesUpdated + 1}) - else await this._content.create({_id:serverId, timesUpdated: 1}) - } } diff --git a/src/typings/interfaces.d.ts b/src/typings/interfaces.d.ts index c59d698..4e05c10 100644 --- a/src/typings/interfaces.d.ts +++ b/src/typings/interfaces.d.ts @@ -137,8 +137,14 @@ export interface Config { LRSstart: number, whitelistedServers: Array, MPStatsLocation: { - channel: string, - message: string + main: { + channel: string + message: string + }, + second: { + channel: string + message: string + } }, botSwitches: { dailyMsgsBackup: boolean, @@ -187,7 +193,14 @@ export interface Config { } } export interface MPServerCache { - players: FSPlayer[], - status: 'online' | 'offline' | null, - name: string | null + main: { + players: FSPlayer[], + status: 'online' | 'offline' | null, + name: string | null + }, + second: { + players: FSPlayer[], + status: 'online' | 'offline' | null, + name: string | null + } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4d5971f..82249c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,5 @@ "typeRoots": [ "./src/typings" ], }, "include": [ "src/" ], - "exclude": [ ".yarn/cache", ".yarn/unplugged",".git", "src/config.json", "src/DB-Beta.config.json", "src/Toast-Testbot.config.json" ] + "exclude": [ ".yarn/cache", ".yarn/unplugged",".git", "src/config.json", "src/DB-Beta.config.json", "src/Toast-Testbot.config.json", "src/disabled" ] } From 477dddf10fb4e902de6c7c054cd4c0339ad7b6f5 Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Fri, 18 Aug 2023 03:06:00 +1000 Subject: [PATCH 02/11] Rename client-error to match the rest --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 54eb02b..6305a9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,7 +41,7 @@ function DZ(error:Error, type:string){// Yes, I may have shiternet but I don't n process.on('unhandledRejection', (error: Error)=>DZ(error, 'unhandledRejection')); process.on('uncaughtException', (error: Error)=>DZ(error, 'uncaughtException')); process.on('error', (error: Error)=>DZ(error, 'nodeError')); -client.on('error', (error: Error)=>DZ(error, 'client-error')); +client.on('error', (error: Error)=>DZ(error, 'clientError')); // Audio Player event handling if (client.config.botSwitches.music){ From e81da694248d028aafe0ce19ec69535a3cf56268 Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Fri, 18 Aug 2023 05:09:27 +1000 Subject: [PATCH 03/11] Lock DAPI-Types to specific version --- .pnp.cjs | 18 +++++++++--------- package.json | 3 +++ yarn.lock | 8 ++++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index e4cfa7c..de64a0f 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -105,7 +105,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@discordjs/formatters", "npm:0.3.1"],\ ["@discordjs/util", "npm:1.0.0"],\ ["@sapphire/shapeshift", "npm:3.9.2"],\ - ["discord-api-types", "npm:0.37.51"],\ + ["discord-api-types", "npm:0.37.53"],\ ["fast-deep-equal", "npm:3.1.3"],\ ["ts-mixer", "npm:6.0.3"],\ ["tslib", "npm:2.6.1"]\ @@ -127,7 +127,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./.yarn/cache/@discordjs-formatters-npm-0.3.1-7840a49252-9635568785.zip/node_modules/@discordjs/formatters/",\ "packageDependencies": [\ ["@discordjs/formatters", "npm:0.3.1"],\ - ["discord-api-types", "npm:0.37.51"]\ + ["discord-api-types", "npm:0.37.53"]\ ],\ "linkType": "HARD"\ }]\ @@ -171,7 +171,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@sapphire/async-queue", "npm:1.5.0"],\ ["@sapphire/snowflake", "npm:3.5.1"],\ ["@vladfrangu/async_event_emitter", "npm:2.2.2"],\ - ["discord-api-types", "npm:0.37.51"],\ + ["discord-api-types", "npm:0.37.53"],\ ["magic-bytes.js", "npm:1.0.15"],\ ["tslib", "npm:2.6.1"],\ ["undici", "npm:5.23.0"]\ @@ -194,7 +194,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@discordjs/voice", "npm:0.16.0"],\ ["@types/ws", "npm:8.5.5"],\ - ["discord-api-types", "npm:0.37.51"],\ + ["discord-api-types", "npm:0.37.53"],\ ["prism-media", "virtual:37f8ab283fb7abcf3fda3c5d430948901f33b8d771da32a273933562670e34608b1daf4394905868d946d64bc91643fb158184deae5131f973a7fcfafbc06afc#npm:1.3.5"],\ ["tslib", "npm:2.6.1"],\ ["ws", "virtual:9ce84a0604ad02832e33621299f919cd218b24239fb0c46d80371b76a2e2bb3fa8fbeca210d63d15caaa18cb4bd437d3b404ee91d06cb167e4b2efa32b8e514c#npm:8.13.0"]\ @@ -213,7 +213,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@sapphire/async-queue", "npm:1.5.0"],\ ["@types/ws", "npm:8.5.5"],\ ["@vladfrangu/async_event_emitter", "npm:2.2.2"],\ - ["discord-api-types", "npm:0.37.51"],\ + ["discord-api-types", "npm:0.37.53"],\ ["tslib", "npm:2.6.1"],\ ["ws", "virtual:9ce84a0604ad02832e33621299f919cd218b24239fb0c46d80371b76a2e2bb3fa8fbeca210d63d15caaa18cb4bd437d3b404ee91d06cb167e4b2efa32b8e514c#npm:8.13.0"]\ ],\ @@ -1048,10 +1048,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["discord-api-types", [\ - ["npm:0.37.51", {\ - "packageLocation": "./.yarn/cache/discord-api-types-npm-0.37.51-89cec04373-0f6c8a5c31.zip/node_modules/discord-api-types/",\ + ["npm:0.37.53", {\ + "packageLocation": "./.yarn/cache/discord-api-types-npm-0.37.53-511d5a32e5-cff6bc976f.zip/node_modules/discord-api-types/",\ "packageDependencies": [\ - ["discord-api-types", "npm:0.37.51"]\ + ["discord-api-types", "npm:0.37.53"]\ ],\ "linkType": "HARD"\ }]\ @@ -1104,7 +1104,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@discordjs/ws", "npm:1.0.0"],\ ["@sapphire/snowflake", "npm:3.5.1"],\ ["@types/ws", "npm:8.5.5"],\ - ["discord-api-types", "npm:0.37.51"],\ + ["discord-api-types", "npm:0.37.53"],\ ["fast-deep-equal", "npm:3.1.3"],\ ["lodash.snakecase", "npm:4.1.1"],\ ["tslib", "npm:2.6.1"],\ diff --git a/package.json b/package.json index 314b4b2..2a333ad 100644 --- a/package.json +++ b/package.json @@ -49,5 +49,8 @@ "devDependencies": { "@types/ms": "0.7.31", "@types/node": "20.5.0" + }, + "resolutions": { + "discord-api-types": "0.37.53" } } diff --git a/yarn.lock b/yarn.lock index 9461e5a..e09c13c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -838,10 +838,10 @@ __metadata: languageName: node linkType: hard -"discord-api-types@npm:^0.37.37, discord-api-types@npm:^0.37.41, discord-api-types@npm:^0.37.50": - version: 0.37.51 - resolution: "discord-api-types@npm:0.37.51" - checksum: 0f6c8a5c316896e333ffb7c95ad31eff51053848cff23a9e47849e27b1f92a342df3e90eb304bf1d1410981b64d7d07a327f883722ffe75c064ae096dd3e9721 +"discord-api-types@npm:0.37.53": + version: 0.37.53 + resolution: "discord-api-types@npm:0.37.53" + checksum: cff6bc976f01d6c9550e48ab758cb2a2ac9042272174d4384915b830aecc4a5551f9dafe3ec21382214700b4188c766ab318179458e4e09e9df9f45a3f4902ed languageName: node linkType: hard From 0e7fa1cf12c81df8c75e930082c3e29acca5d6b9 Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Fri, 18 Aug 2023 21:18:33 +1000 Subject: [PATCH 04/11] Ran out of time for today, atleast it's a start. --- .gitignore | 3 +- src/MPLoop.ts | 65 ++++++++- src/client.ts | 2 +- src/disabled/mp.ts | 354 +++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 6 +- 5 files changed, 425 insertions(+), 5 deletions(-) create mode 100644 src/disabled/mp.ts diff --git a/.gitignore b/.gitignore index 8ad232b..d3f0704 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ # Bot stuff dist/ src/database/ -src/*.json \ No newline at end of file +src/*.json +src/MPLoopRaw.log \ No newline at end of file diff --git a/src/MPLoop.ts b/src/MPLoop.ts index 1453859..766934b 100644 --- a/src/MPLoop.ts +++ b/src/MPLoop.ts @@ -3,7 +3,69 @@ import TClient from './client'; import {writeFileSync, readFileSync} from 'node:fs'; import {FSPlayer, FSData, FSCareerSavegame} from './typings/interfaces'; -export default async(client:TClient,Channel:string,Message:string,ServerName:string)=>{ +export default async(client:TClient, Channel:string, Message:string, ServerName:string)=>{ + if (!client.config.botSwitches.mpstats) return; + + const msg = await (client.channels.resolve(Channel) as Discord.TextChannel).messages.fetch(Message); + const database = await client.MPServer._content.findById(client.config.mainServer.id); + const servers = { + main: { + dss: database.mainServer.ip+'/feed/dedicated-server-stats.json?code='+database.mainServer.code, + csg: database.mainServer.ip+'/feed/dedicated-server-savegame.html?code='+database.mainServer.code+'&file=careerSavegame' + }, + second: { + dss: database.secondServer.ip+'/feed/dedicated-server-stats.json?code='+database.secondServer.code, + csg: database.secondServer.ip+'/feed/dedicated-server-savegame.html?code='+database.secondServer.code+'&file=careerSavegame' + } + }; + // Log bot uptime for the sake of debugging. + (client.channels.resolve('1091300529696673792') as Discord.TextChannel).send(client.formatTime(client.uptime, 2, {longNames: true, commas: true})); + + const HITALL = async()=>{ + /* const hitDSS = await Promise.all([ + client.axios.get(servers.main.dss,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot - HITALL/axios ${client.axios.VERSION}`}}), + client.axios.get(servers.second.dss,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot - HITALL/axios ${client.axios.VERSION}`}}) + ]).catch(e=>{throw new Error('hitDSS failed to make a request', {cause: e.cause})}); */ + const hitCSG = await Promise.all([ + client.axios.get(servers.main.csg,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot - HITALL/axios ${client.axios.VERSION}`}}), + client.axios.get(servers.second.csg,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot - HITALL/axios ${client.axios.VERSION}`}}) + ]).catch(e=>{throw new Error('hitCSG failed to make a request', {cause: e.cause})}); + try { + const APIData = { + 'Daggerwin': { + //dss: hitDSS[0].data as FSData, + csg: (client.xjs.xml2js(hitCSG[0].data,{compact:true}) as any).careerSavegame as FSCareerSavegame + }, + 'SecondServer': { + //dss: hitDSS[1].data as FSData, + csg: (client.xjs.xml2js(hitCSG[1].data,{compact:true}) as any).careerSavegame as FSCareerSavegame + } + } as const; + console.log(APIData['Daggerwin'].csg) + console.log(APIData['SecondServer'].csg) + //console.log((APIData.Daggerwin.dss as FSData).server.name) + //console.log((APIData.Daggerwin.csg as FSCareerSavegame).statistics.money) + msg.edit({content: [ + ServerName, + (APIData[ServerName].csg as FSCareerSavegame).settings.savegameName._text + ].join('\n')}) + } catch(err) { + msg.edit({content: err.message}) + throw new Error('HITALL failed to make a promise request', {cause: err.cause}); + } + } + HITALL(); + + /* await Promise.all([ + client.axios.get(servers.main.dss,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}), + client.axios.get(servers.main.csg,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}), + client.axios.get(servers.second.dss,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}), + client.axios.get(servers.second.csg,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}) + ]).then(x=>x.map(x=>x.data)).catch(()=>{throw new Error('[MPLOOP] Failed to make a promise request.')}); + msg.edit({content: ServerName, embeds: []}) */ +} + +/* export default async(client:TClient,Channel:string,Message:string,ServerName:string)=>{ if (!client.config.botSwitches.mpstats) return; const noContentImage = 'https://cdn.discordapp.com/attachments/1118960531135541318/1140906691236479036/68efx1.png'; const msg = await (client.channels.resolve(Channel) as Discord.TextChannel).messages.fetch(Message); @@ -161,3 +223,4 @@ export default async(client:TClient,Channel:string,Message:string,ServerName:str client.MPServerCache[ServerName].players = playersOnServer } } + */ \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index bb1d0e8..7f6151c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -109,7 +109,7 @@ export default class TClient extends Client { tls: false, family: 4 }).then(()=>console.log(this.logTime(), 'Successfully connected to MongoDB')).catch(err=>{console.error(this.logTime(), `Failed to connect to MongoDB\n${err}`); exec('pm2 stop Daggerbot', {windowsHide:true})}) - this.login(this.tokens.main); + this.login(this.tokens.beta); for await (const file of readdirSync('dist/events')){ //console.log('EVENTS:', file) const eventFile = await import(`./events/${file}`); diff --git a/src/disabled/mp.ts b/src/disabled/mp.ts new file mode 100644 index 0000000..c842f4b --- /dev/null +++ b/src/disabled/mp.ts @@ -0,0 +1,354 @@ +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))) +} diff --git a/src/index.ts b/src/index.ts index 6305a9b..790eb5b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,6 +42,7 @@ process.on('unhandledRejection', (error: Error)=>DZ(error, 'unhandledRejection') process.on('uncaughtException', (error: Error)=>DZ(error, 'uncaughtException')); process.on('error', (error: Error)=>DZ(error, 'nodeError')); client.on('error', (error: Error)=>DZ(error, 'clientError')); +client.on('debug', console.log).on('warn', console.log); // Audio Player event handling if (client.config.botSwitches.music){ @@ -66,10 +67,11 @@ if (client.config.botSwitches.music){ } // YouTube Upload notification and Daggerwin MP loop -setInterval(()=>{ +setInterval(async()=>{ + console.log(client.logTime(), 'MPLoop Interval') MPLoop(client, client.config.MPStatsLocation.main.channel, client.config.MPStatsLocation.main.message, 'Daggerwin') MPLoop(client, client.config.MPStatsLocation.second.channel, client.config.MPStatsLocation.second.message, 'SecondServer') -}, 60000); +}, 30000); setInterval(async()=>{ client.YTLoop('UCQ8k8yTDLITldfWYKDs3xFg', 'Daggerwin', '528967918772551702'); // 528967918772551702 = #videos-and-streams client.YTLoop('UCguI73--UraJpso4NizXNzA', 'Machinery Restorer', '767444045520961567') // 767444045520961567 = #machinery-restorer From a020836caf0cde222dd173ec32a4b2bc71c7381b Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Fri, 18 Aug 2023 21:22:36 +1000 Subject: [PATCH 05/11] Update to D.JS 14.13.0 --- .pnp.cjs | 124 ++++++++++++++++++++++++++++++--------------------- package.json | 5 +-- yarn.lock | 123 +++++++++++++++++++++++++++++--------------------- 3 files changed, 147 insertions(+), 105 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index de64a0f..fff0ffb 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -37,7 +37,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["axios", "npm:1.4.0"],\ ["canvas", "npm:2.11.2"],\ ["discord-player", "virtual:20c353e2d6536e37339997f03975c6a660f4d296e664d291bd43620c6162cca8eb5ef90b0998dc9db75ff6862e5da587d0530bae26805f5fadc8f17aaa4ff794#npm:6.6.2"],\ - ["discord.js", "npm:14.13.0-dev.1691971783-188877c50.0"],\ + ["discord.js", "npm:14.13.0"],\ ["libsodium-wrappers", "npm:0.7.11"],\ ["moment", "npm:2.29.4"],\ ["mongoose", "npm:7.4.3"],\ @@ -98,14 +98,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@discordjs/builders", [\ - ["npm:1.6.4", {\ - "packageLocation": "./.yarn/cache/@discordjs-builders-npm-1.6.4-73f25610c1-059ebe77b1.zip/node_modules/@discordjs/builders/",\ + ["npm:1.6.5", {\ + "packageLocation": "./.yarn/cache/@discordjs-builders-npm-1.6.5-9d05c520ba-9c5c4d483a.zip/node_modules/@discordjs/builders/",\ "packageDependencies": [\ - ["@discordjs/builders", "npm:1.6.4"],\ - ["@discordjs/formatters", "npm:0.3.1"],\ - ["@discordjs/util", "npm:1.0.0"],\ + ["@discordjs/builders", "npm:1.6.5"],\ + ["@discordjs/formatters", "npm:0.3.2"],\ + ["@discordjs/util", "npm:1.0.1"],\ ["@sapphire/shapeshift", "npm:3.9.2"],\ - ["discord-api-types", "npm:0.37.53"],\ + ["discord-api-types", "npm:0.37.50"],\ ["fast-deep-equal", "npm:3.1.3"],\ ["ts-mixer", "npm:6.0.3"],\ ["tslib", "npm:2.6.1"]\ @@ -120,14 +120,21 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@discordjs/collection", "npm:1.5.2"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.5.3", {\ + "packageLocation": "./.yarn/cache/@discordjs-collection-npm-1.5.3-ea9e0ca74b-fefed19bea.zip/node_modules/@discordjs/collection/",\ + "packageDependencies": [\ + ["@discordjs/collection", "npm:1.5.3"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@discordjs/formatters", [\ - ["npm:0.3.1", {\ - "packageLocation": "./.yarn/cache/@discordjs-formatters-npm-0.3.1-7840a49252-9635568785.zip/node_modules/@discordjs/formatters/",\ + ["npm:0.3.2", {\ + "packageLocation": "./.yarn/cache/@discordjs-formatters-npm-0.3.2-01b9de30b3-653c88595f.zip/node_modules/@discordjs/formatters/",\ "packageDependencies": [\ - ["@discordjs/formatters", "npm:0.3.1"],\ - ["discord-api-types", "npm:0.37.53"]\ + ["@discordjs/formatters", "npm:0.3.2"],\ + ["discord-api-types", "npm:0.37.50"]\ ],\ "linkType": "HARD"\ }]\ @@ -162,28 +169,28 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@discordjs/rest", [\ - ["npm:2.0.0", {\ - "packageLocation": "./.yarn/cache/@discordjs-rest-npm-2.0.0-124259cee8-df610f6bce.zip/node_modules/@discordjs/rest/",\ + ["npm:2.0.1", {\ + "packageLocation": "./.yarn/cache/@discordjs-rest-npm-2.0.1-0339cfec4f-36e3348929.zip/node_modules/@discordjs/rest/",\ "packageDependencies": [\ - ["@discordjs/rest", "npm:2.0.0"],\ - ["@discordjs/collection", "npm:1.5.2"],\ - ["@discordjs/util", "npm:1.0.0"],\ + ["@discordjs/rest", "npm:2.0.1"],\ + ["@discordjs/collection", "npm:1.5.3"],\ + ["@discordjs/util", "npm:1.0.1"],\ ["@sapphire/async-queue", "npm:1.5.0"],\ ["@sapphire/snowflake", "npm:3.5.1"],\ ["@vladfrangu/async_event_emitter", "npm:2.2.2"],\ - ["discord-api-types", "npm:0.37.53"],\ + ["discord-api-types", "npm:0.37.50"],\ ["magic-bytes.js", "npm:1.0.15"],\ ["tslib", "npm:2.6.1"],\ - ["undici", "npm:5.23.0"]\ + ["undici", "npm:5.22.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@discordjs/util", [\ - ["npm:1.0.0", {\ - "packageLocation": "./.yarn/cache/@discordjs-util-npm-1.0.0-5c10481950-bbd800408c.zip/node_modules/@discordjs/util/",\ + ["npm:1.0.1", {\ + "packageLocation": "./.yarn/cache/@discordjs-util-npm-1.0.1-5845db7f36-b55d5284cd.zip/node_modules/@discordjs/util/",\ "packageDependencies": [\ - ["@discordjs/util", "npm:1.0.0"]\ + ["@discordjs/util", "npm:1.0.1"]\ ],\ "linkType": "HARD"\ }]\ @@ -194,28 +201,28 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@discordjs/voice", "npm:0.16.0"],\ ["@types/ws", "npm:8.5.5"],\ - ["discord-api-types", "npm:0.37.53"],\ + ["discord-api-types", "npm:0.37.54"],\ ["prism-media", "virtual:37f8ab283fb7abcf3fda3c5d430948901f33b8d771da32a273933562670e34608b1daf4394905868d946d64bc91643fb158184deae5131f973a7fcfafbc06afc#npm:1.3.5"],\ ["tslib", "npm:2.6.1"],\ - ["ws", "virtual:9ce84a0604ad02832e33621299f919cd218b24239fb0c46d80371b76a2e2bb3fa8fbeca210d63d15caaa18cb4bd437d3b404ee91d06cb167e4b2efa32b8e514c#npm:8.13.0"]\ + ["ws", "virtual:31c533a2eb37dc1746b7d976d500ca13eb00e91bf180b840d65754f8c6e137fa3abf10279db8d10554ceb81785dfffeb6b725d592c9bfa5b4309edf4781c592e#npm:8.13.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@discordjs/ws", [\ - ["npm:1.0.0", {\ - "packageLocation": "./.yarn/cache/@discordjs-ws-npm-1.0.0-9ce84a0604-1917361918.zip/node_modules/@discordjs/ws/",\ + ["npm:1.0.1", {\ + "packageLocation": "./.yarn/cache/@discordjs-ws-npm-1.0.1-31c533a2eb-d34f176466.zip/node_modules/@discordjs/ws/",\ "packageDependencies": [\ - ["@discordjs/ws", "npm:1.0.0"],\ - ["@discordjs/collection", "npm:1.5.2"],\ - ["@discordjs/rest", "npm:2.0.0"],\ - ["@discordjs/util", "npm:1.0.0"],\ + ["@discordjs/ws", "npm:1.0.1"],\ + ["@discordjs/collection", "npm:1.5.3"],\ + ["@discordjs/rest", "npm:2.0.1"],\ + ["@discordjs/util", "npm:1.0.1"],\ ["@sapphire/async-queue", "npm:1.5.0"],\ ["@types/ws", "npm:8.5.5"],\ ["@vladfrangu/async_event_emitter", "npm:2.2.2"],\ - ["discord-api-types", "npm:0.37.53"],\ + ["discord-api-types", "npm:0.37.50"],\ ["tslib", "npm:2.6.1"],\ - ["ws", "virtual:9ce84a0604ad02832e33621299f919cd218b24239fb0c46d80371b76a2e2bb3fa8fbeca210d63d15caaa18cb4bd437d3b404ee91d06cb167e4b2efa32b8e514c#npm:8.13.0"]\ + ["ws", "virtual:31c533a2eb37dc1746b7d976d500ca13eb00e91bf180b840d65754f8c6e137fa3abf10279db8d10554ceb81785dfffeb6b725d592c9bfa5b4309edf4781c592e#npm:8.13.0"]\ ],\ "linkType": "HARD"\ }]\ @@ -945,7 +952,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["axios", "npm:1.4.0"],\ ["canvas", "npm:2.11.2"],\ ["discord-player", "virtual:20c353e2d6536e37339997f03975c6a660f4d296e664d291bd43620c6162cca8eb5ef90b0998dc9db75ff6862e5da587d0530bae26805f5fadc8f17aaa4ff794#npm:6.6.2"],\ - ["discord.js", "npm:14.13.0-dev.1691971783-188877c50.0"],\ + ["discord.js", "npm:14.13.0"],\ ["libsodium-wrappers", "npm:0.7.11"],\ ["moment", "npm:2.29.4"],\ ["mongoose", "npm:7.4.3"],\ @@ -1048,10 +1055,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["discord-api-types", [\ - ["npm:0.37.53", {\ - "packageLocation": "./.yarn/cache/discord-api-types-npm-0.37.53-511d5a32e5-cff6bc976f.zip/node_modules/discord-api-types/",\ + ["npm:0.37.50", {\ + "packageLocation": "./.yarn/cache/discord-api-types-npm-0.37.50-969b9c186e-08dc5145db.zip/node_modules/discord-api-types/",\ "packageDependencies": [\ - ["discord-api-types", "npm:0.37.53"]\ + ["discord-api-types", "npm:0.37.50"]\ + ],\ + "linkType": "HARD"\ + }],\ + ["npm:0.37.54", {\ + "packageLocation": "./.yarn/cache/discord-api-types-npm-0.37.54-375905cfce-f149961be9.zip/node_modules/discord-api-types/",\ + "packageDependencies": [\ + ["discord-api-types", "npm:0.37.54"]\ ],\ "linkType": "HARD"\ }]\ @@ -1076,7 +1090,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/discord-player__extractor", null],\ ["@types/discord.js", null],\ ["@types/youtube-sr", null],\ - ["discord.js", "npm:14.13.0-dev.1691971783-188877c50.0"],\ + ["discord.js", "npm:14.13.0"],\ ["libsodium-wrappers", "npm:0.7.11"],\ ["youtube-sr", "npm:4.3.4"]\ ],\ @@ -1092,24 +1106,24 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["discord.js", [\ - ["npm:14.13.0-dev.1691971783-188877c50.0", {\ - "packageLocation": "./.yarn/cache/discord.js-npm-14.13.0-dev.1691971783-188877c50.0-96206ff672-3e03a228ec.zip/node_modules/discord.js/",\ + ["npm:14.13.0", {\ + "packageLocation": "./.yarn/cache/discord.js-npm-14.13.0-cee9413e4c-c273645ac2.zip/node_modules/discord.js/",\ "packageDependencies": [\ - ["discord.js", "npm:14.13.0-dev.1691971783-188877c50.0"],\ - ["@discordjs/builders", "npm:1.6.4"],\ - ["@discordjs/collection", "npm:1.5.2"],\ - ["@discordjs/formatters", "npm:0.3.1"],\ - ["@discordjs/rest", "npm:2.0.0"],\ - ["@discordjs/util", "npm:1.0.0"],\ - ["@discordjs/ws", "npm:1.0.0"],\ + ["discord.js", "npm:14.13.0"],\ + ["@discordjs/builders", "npm:1.6.5"],\ + ["@discordjs/collection", "npm:1.5.3"],\ + ["@discordjs/formatters", "npm:0.3.2"],\ + ["@discordjs/rest", "npm:2.0.1"],\ + ["@discordjs/util", "npm:1.0.1"],\ + ["@discordjs/ws", "npm:1.0.1"],\ ["@sapphire/snowflake", "npm:3.5.1"],\ ["@types/ws", "npm:8.5.5"],\ - ["discord-api-types", "npm:0.37.53"],\ + ["discord-api-types", "npm:0.37.50"],\ ["fast-deep-equal", "npm:3.1.3"],\ ["lodash.snakecase", "npm:4.1.1"],\ ["tslib", "npm:2.6.1"],\ - ["undici", "npm:5.23.0"],\ - ["ws", "virtual:9ce84a0604ad02832e33621299f919cd218b24239fb0c46d80371b76a2e2bb3fa8fbeca210d63d15caaa18cb4bd437d3b404ee91d06cb167e4b2efa32b8e514c#npm:8.13.0"]\ + ["undici", "npm:5.22.1"],\ + ["ws", "virtual:31c533a2eb37dc1746b7d976d500ca13eb00e91bf180b840d65754f8c6e137fa3abf10279db8d10554ceb81785dfffeb6b725d592c9bfa5b4309edf4781c592e#npm:8.13.0"]\ ],\ "linkType": "HARD"\ }]\ @@ -2767,6 +2781,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["undici", [\ + ["npm:5.22.1", {\ + "packageLocation": "./.yarn/cache/undici-npm-5.22.1-ff9b0b961e-048a3365f6.zip/node_modules/undici/",\ + "packageDependencies": [\ + ["undici", "npm:5.22.1"],\ + ["busboy", "npm:1.6.0"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:5.23.0", {\ "packageLocation": "./.yarn/cache/undici-npm-5.23.0-eb9e1b02e8-906ca4fb1d.zip/node_modules/undici/",\ "packageDependencies": [\ @@ -2927,10 +2949,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:9ce84a0604ad02832e33621299f919cd218b24239fb0c46d80371b76a2e2bb3fa8fbeca210d63d15caaa18cb4bd437d3b404ee91d06cb167e4b2efa32b8e514c#npm:8.13.0", {\ - "packageLocation": "./.yarn/__virtual__/ws-virtual-3a4860311b/0/cache/ws-npm-8.13.0-26ffa3016a-53e991bbf9.zip/node_modules/ws/",\ + ["virtual:31c533a2eb37dc1746b7d976d500ca13eb00e91bf180b840d65754f8c6e137fa3abf10279db8d10554ceb81785dfffeb6b725d592c9bfa5b4309edf4781c592e#npm:8.13.0", {\ + "packageLocation": "./.yarn/__virtual__/ws-virtual-fbc9218bf1/0/cache/ws-npm-8.13.0-26ffa3016a-53e991bbf9.zip/node_modules/ws/",\ "packageDependencies": [\ - ["ws", "virtual:9ce84a0604ad02832e33621299f919cd218b24239fb0c46d80371b76a2e2bb3fa8fbeca210d63d15caaa18cb4bd437d3b404ee91d06cb167e4b2efa32b8e514c#npm:8.13.0"],\ + ["ws", "virtual:31c533a2eb37dc1746b7d976d500ca13eb00e91bf180b840d65754f8c6e137fa3abf10279db8d10554ceb81785dfffeb6b725d592c9bfa5b4309edf4781c592e#npm:8.13.0"],\ ["@types/bufferutil", null],\ ["@types/utf-8-validate", null],\ ["bufferutil", null],\ diff --git a/package.json b/package.json index 2a333ad..756b330 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "axios": "1.4.0", "canvas": "2.11.2", "discord-player": "6.6.2", - "discord.js": "dev", + "discord.js": "14.13.0", "libsodium-wrappers": "0.7.11", "moment": "2.29.4", "mongoose": "7.4.3", @@ -49,8 +49,5 @@ "devDependencies": { "@types/ms": "0.7.31", "@types/node": "20.5.0" - }, - "resolutions": { - "discord-api-types": "0.37.53" } } diff --git a/yarn.lock b/yarn.lock index e09c13c..7fb94d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -44,34 +44,41 @@ __metadata: languageName: node linkType: hard -"@discordjs/builders@npm:^1.6.4": - version: 1.6.4 - resolution: "@discordjs/builders@npm:1.6.4" +"@discordjs/builders@npm:^1.6.5": + version: 1.6.5 + resolution: "@discordjs/builders@npm:1.6.5" dependencies: - "@discordjs/formatters": ^0.3.1 - "@discordjs/util": ^1.0.0 + "@discordjs/formatters": ^0.3.2 + "@discordjs/util": ^1.0.1 "@sapphire/shapeshift": ^3.9.2 - discord-api-types: ^0.37.50 + discord-api-types: 0.37.50 fast-deep-equal: ^3.1.3 ts-mixer: ^6.0.3 tslib: ^2.6.1 - checksum: 059ebe77b1b955daa1c16d62679c338b34e5c8160dcc446719452b05b61fb8d5f6d7fad8dd9cb7ce2d90c2d1014031f6feedd7c71943cddf04a4e83e4fcccfe3 + checksum: 9c5c4d483a79a7c2f73d661433365f2996ae3bc74f95b70a2a31a26b582b7327d45217a78dfe8e304737661731690ef6e34ade7575f63fe8ab61d70ca53b2279 languageName: node linkType: hard -"@discordjs/collection@npm:^1.1.0, @discordjs/collection@npm:^1.5.2": +"@discordjs/collection@npm:^1.1.0": version: 1.5.2 resolution: "@discordjs/collection@npm:1.5.2" checksum: bf51b951c75c1622f78db4ab48382dc36ef62d264ebf3add07b4a29bef74e84ee1ca65b74e8cf3c9c16eef435dcf05c35465b32642bc7c0bac6872e226d100a7 languageName: node linkType: hard -"@discordjs/formatters@npm:^0.3.1": - version: 0.3.1 - resolution: "@discordjs/formatters@npm:0.3.1" +"@discordjs/collection@npm:^1.5.3": + version: 1.5.3 + resolution: "@discordjs/collection@npm:1.5.3" + checksum: fefed19bea0f69053d195f9d9dc8af07ca5d8c9b1064581e0aa14bda2b70e632b93c164d5ef3e4910f5442369612ff4eec8d52a700aec562510c19b223f67023 + languageName: node + linkType: hard + +"@discordjs/formatters@npm:^0.3.2": + version: 0.3.2 + resolution: "@discordjs/formatters@npm:0.3.2" dependencies: - discord-api-types: ^0.37.41 - checksum: 96355687853a203b280484419cd96a79b79b4b98f0469ec15be073862fb340cbd60ca2ea68f3d4684e17ff0e8c5f04ddcc6dd88188808e01fcbe0e3eea052d6d + discord-api-types: 0.37.50 + checksum: 653c88595fc6c25c1beedcd88b05a3f1241fef69844cc96e45f2cd34fea9ff07892c7f3b57edb4008ad59f7e62bca1b7b35400c6200b07ed42eef7189672d509 languageName: node linkType: hard @@ -104,27 +111,27 @@ __metadata: languageName: node linkType: hard -"@discordjs/rest@npm:^2.0.0": - version: 2.0.0 - resolution: "@discordjs/rest@npm:2.0.0" +"@discordjs/rest@npm:^2.0.1": + version: 2.0.1 + resolution: "@discordjs/rest@npm:2.0.1" dependencies: - "@discordjs/collection": ^1.5.2 - "@discordjs/util": ^1.0.0 + "@discordjs/collection": ^1.5.3 + "@discordjs/util": ^1.0.1 "@sapphire/async-queue": ^1.5.0 "@sapphire/snowflake": ^3.5.1 "@vladfrangu/async_event_emitter": ^2.2.2 - discord-api-types: ^0.37.50 + discord-api-types: 0.37.50 magic-bytes.js: ^1.0.15 tslib: ^2.6.1 - undici: ^5.22.1 - checksum: df610f6bce71d2b72087c8f80caad9073c72017a9b2525ee704f11510d1d39e56b0d3cf2b38a7f5f2e77740bfd6e0221e89f0d2d3bd65f318862fa8c2fd82dda + undici: 5.22.1 + checksum: 36e33489293956e6356e68d69857c2ea910aa376be8c530d6aa640f5887d119d2c0abb736b427353bbdad92c534917fd38adb3a8529c87bdd010c75017ae6b27 languageName: node linkType: hard -"@discordjs/util@npm:^1.0.0": - version: 1.0.0 - resolution: "@discordjs/util@npm:1.0.0" - checksum: bbd800408c9ac20e9f298ea954592d674c92cbf1ec255ae5b07b2d5c444557a8580b3e95359600897db18ffc129eec5cabc37f1ba40ea6df24f2e9666a2dbc54 +"@discordjs/util@npm:^1.0.1": + version: 1.0.1 + resolution: "@discordjs/util@npm:1.0.1" + checksum: b55d5284cd8306b0e77a303c41fa99dcc650babaf9ef2f02ea38b1f8ecc7218a7694128714343379dbf6b2a402a0851e00862c0d974ad07b8e980722f5139d73 languageName: node linkType: hard @@ -141,20 +148,20 @@ __metadata: languageName: node linkType: hard -"@discordjs/ws@npm:^1.0.0": - version: 1.0.0 - resolution: "@discordjs/ws@npm:1.0.0" +"@discordjs/ws@npm:^1.0.1": + version: 1.0.1 + resolution: "@discordjs/ws@npm:1.0.1" dependencies: - "@discordjs/collection": ^1.5.2 - "@discordjs/rest": ^2.0.0 - "@discordjs/util": ^1.0.0 + "@discordjs/collection": ^1.5.3 + "@discordjs/rest": ^2.0.1 + "@discordjs/util": ^1.0.1 "@sapphire/async-queue": ^1.5.0 "@types/ws": ^8.5.5 "@vladfrangu/async_event_emitter": ^2.2.2 - discord-api-types: ^0.37.50 + discord-api-types: 0.37.50 tslib: ^2.6.1 ws: ^8.13.0 - checksum: 1917361918ee3e7b03661c577473157e371ff7d5d66f7f220623e79430bfb97017b0108efb87b391f95779829dc1288d0fc8d2837291b233d36a39d07f4e0dea + checksum: d34f17646606dbac82989c3aa3fddd1e2a23da532b96f1fc130a0ddb6735079523f09a70b560b315f3e6634b6336accc48680539e4c62cf34826d79c6304778a languageName: node linkType: hard @@ -761,7 +768,7 @@ __metadata: axios: 1.4.0 canvas: 2.11.2 discord-player: 6.6.2 - discord.js: dev + discord.js: 14.13.0 libsodium-wrappers: 0.7.11 moment: 2.29.4 mongoose: 7.4.3 @@ -838,10 +845,17 @@ __metadata: languageName: node linkType: hard -"discord-api-types@npm:0.37.53": - version: 0.37.53 - resolution: "discord-api-types@npm:0.37.53" - checksum: cff6bc976f01d6c9550e48ab758cb2a2ac9042272174d4384915b830aecc4a5551f9dafe3ec21382214700b4188c766ab318179458e4e09e9df9f45a3f4902ed +"discord-api-types@npm:0.37.50": + version: 0.37.50 + resolution: "discord-api-types@npm:0.37.50" + checksum: 08dc5145dbefda5f52b479cd42d96ac2b8110300861855e1f92cc8a0a6525a4059e32724cd5237490c286f5afd86797a86823238cd5eee016198560bb36f6d43 + languageName: node + linkType: hard + +"discord-api-types@npm:^0.37.37": + version: 0.37.54 + resolution: "discord-api-types@npm:0.37.54" + checksum: f149961be9129673a0e3764285c8f47da22a30ea3e2466db06ffad44d1823094f7011ffe2d3d89b1b5ddd9940651afc72b3a05f84ce443a59f8480e2578dd1e1 languageName: node linkType: hard @@ -862,25 +876,25 @@ __metadata: languageName: node linkType: hard -"discord.js@npm:dev": - version: 14.13.0-dev.1691971783-188877c50.0 - resolution: "discord.js@npm:14.13.0-dev.1691971783-188877c50.0" +"discord.js@npm:14.13.0": + version: 14.13.0 + resolution: "discord.js@npm:14.13.0" dependencies: - "@discordjs/builders": ^1.6.4 - "@discordjs/collection": ^1.5.2 - "@discordjs/formatters": ^0.3.1 - "@discordjs/rest": ^2.0.0 - "@discordjs/util": ^1.0.0 - "@discordjs/ws": ^1.0.0 + "@discordjs/builders": ^1.6.5 + "@discordjs/collection": ^1.5.3 + "@discordjs/formatters": ^0.3.2 + "@discordjs/rest": ^2.0.1 + "@discordjs/util": ^1.0.1 + "@discordjs/ws": ^1.0.1 "@sapphire/snowflake": ^3.5.1 "@types/ws": ^8.5.5 - discord-api-types: ^0.37.50 + discord-api-types: 0.37.50 fast-deep-equal: ^3.1.3 lodash.snakecase: ^4.1.1 tslib: ^2.6.1 - undici: ^5.22.1 + undici: 5.22.1 ws: ^8.13.0 - checksum: 3e03a228ec2a87951d2fa92723325806e8a850ff3c8377d2a3ebc064f5bf04fb5bfe06b58e8b02ffd466631e7f61f044db4933af22d29a8a040c1067e89c63bd + checksum: c273645ac2f92a5052914261c40d04f7fbf81f8d2542f7f0ec9b2e5f9006ff7436d7c6254db924a12826b7f3b49cbfdd577807a0a4ed396036e106f39701a167 languageName: node linkType: hard @@ -2324,6 +2338,15 @@ __metadata: languageName: node linkType: hard +"undici@npm:5.22.1": + version: 5.22.1 + resolution: "undici@npm:5.22.1" + dependencies: + busboy: ^1.6.0 + checksum: 048a3365f622be44fb319316cedfaa241c59cf7f3368ae7667a12323447e1822e8cc3d00f6956c852d1478a6fde1cbbe753f49e05f2fdaed229693e716ebaf35 + languageName: node + linkType: hard + "undici@npm:^5.22.1, undici@npm:^5.8.2": version: 5.23.0 resolution: "undici@npm:5.23.0" From 62648d7b5bfe2f1001cda65a493bbcc543b67c2c Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Sat, 19 Aug 2023 01:02:39 +1000 Subject: [PATCH 06/11] Update client.ts --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 7f6151c..2754905 100644 --- a/src/client.ts +++ b/src/client.ts @@ -108,7 +108,7 @@ export default class TClient extends Client { socketTimeoutMS: 30000, tls: false, family: 4 - }).then(()=>console.log(this.logTime(), 'Successfully connected to MongoDB')).catch(err=>{console.error(this.logTime(), `Failed to connect to MongoDB\n${err}`); exec('pm2 stop Daggerbot', {windowsHide:true})}) + }).then(()=>console.log(this.logTime(), 'Successfully connected to MongoDB')).catch(()=>{throw new Error(this.logTime(), `Failed to connect to MongoDB`); exec('pm2 stop Daggerbot', {windowsHide:true})}) this.login(this.tokens.beta); for await (const file of readdirSync('dist/events')){ //console.log('EVENTS:', file) From baa020ae1d05d00b8657e04aa0413e618106630b Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Sat, 19 Aug 2023 01:03:56 +1000 Subject: [PATCH 07/11] Remove timestamp --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 2754905..20acf84 100644 --- a/src/client.ts +++ b/src/client.ts @@ -108,7 +108,7 @@ export default class TClient extends Client { socketTimeoutMS: 30000, tls: false, family: 4 - }).then(()=>console.log(this.logTime(), 'Successfully connected to MongoDB')).catch(()=>{throw new Error(this.logTime(), `Failed to connect to MongoDB`); exec('pm2 stop Daggerbot', {windowsHide:true})}) + }).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); for await (const file of readdirSync('dist/events')){ //console.log('EVENTS:', file) From c4e1c00b9ec872a22058e286718f9cf5316e7867 Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Sat, 19 Aug 2023 09:13:20 +1000 Subject: [PATCH 08/11] Wait until bot receives WS heartbeat --- src/commands/ping.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/ping.ts b/src/commands/ping.ts index a123ac9..d0435a6 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -2,6 +2,7 @@ import Discord from 'discord.js'; import TClient from '../client.js'; export default { async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ + if (client.uptime < 15000) return interaction.reply('I just restarted, wait 15 seconds and try again.') const msg = await interaction.reply({content: 'Pinging...', fetchReply: true}) msg.edit(`Websocket: \`${client.formatTime(client.ws.ping, 3, {longNames: false, commas: true})}\`\nBot: \`${client.formatTime(msg.createdTimestamp - interaction.createdTimestamp, 3, {longNames: false, commas: true})}\``) }, From 269e8de44c0efc6b99a4998847a9d57ff2a015e0 Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Sat, 19 Aug 2023 10:54:17 +1000 Subject: [PATCH 09/11] After few trial and errors, I finally got it!! --- src/MPLoop.ts | 72 +++++++++++++++++-------------------------------- src/config.json | 4 +-- src/index.ts | 7 +++-- 3 files changed, 29 insertions(+), 54 deletions(-) diff --git a/src/MPLoop.ts b/src/MPLoop.ts index 766934b..2a13408 100644 --- a/src/MPLoop.ts +++ b/src/MPLoop.ts @@ -1,68 +1,44 @@ +interface TServer { + ip: string + code: string +} import Discord from 'discord.js'; import TClient from './client'; import {writeFileSync, readFileSync} from 'node:fs'; import {FSPlayer, FSData, FSCareerSavegame} from './typings/interfaces'; -export default async(client:TClient, Channel:string, Message:string, ServerName:string)=>{ +export default async(client:TClient, Channel:string, Message:string, Server:TServer)=>{ if (!client.config.botSwitches.mpstats) return; - + let MPLoopPrefix = '[MPLoop] '; + let noContentImage = 'https://cdn.discordapp.com/attachments/1118960531135541318/1140906691236479036/68efx1.png'; const msg = await (client.channels.resolve(Channel) as Discord.TextChannel).messages.fetch(Message); - const database = await client.MPServer._content.findById(client.config.mainServer.id); - const servers = { - main: { - dss: database.mainServer.ip+'/feed/dedicated-server-stats.json?code='+database.mainServer.code, - csg: database.mainServer.ip+'/feed/dedicated-server-savegame.html?code='+database.mainServer.code+'&file=careerSavegame' - }, - second: { - dss: database.secondServer.ip+'/feed/dedicated-server-stats.json?code='+database.secondServer.code, - csg: database.secondServer.ip+'/feed/dedicated-server-savegame.html?code='+database.secondServer.code+'&file=careerSavegame' - } - }; + // Log bot uptime for the sake of debugging. (client.channels.resolve('1091300529696673792') as Discord.TextChannel).send(client.formatTime(client.uptime, 2, {longNames: true, commas: true})); const HITALL = async()=>{ - /* const hitDSS = await Promise.all([ - client.axios.get(servers.main.dss,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot - HITALL/axios ${client.axios.VERSION}`}}), - client.axios.get(servers.second.dss,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot - HITALL/axios ${client.axios.VERSION}`}}) - ]).catch(e=>{throw new Error('hitDSS failed to make a request', {cause: e.cause})}); */ - const hitCSG = await Promise.all([ - client.axios.get(servers.main.csg,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot - HITALL/axios ${client.axios.VERSION}`}}), - client.axios.get(servers.second.csg,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot - HITALL/axios ${client.axios.VERSION}`}}) - ]).catch(e=>{throw new Error('hitCSG failed to make a request', {cause: e.cause})}); + let sessionInit = {signal: AbortSignal.timeout(7500),headers:{'User-Agent':`Daggerbot - HITALL/fetch`}}; try { - const APIData = { - 'Daggerwin': { - //dss: hitDSS[0].data as FSData, - csg: (client.xjs.xml2js(hitCSG[0].data,{compact:true}) as any).careerSavegame as FSCareerSavegame - }, - 'SecondServer': { - //dss: hitDSS[1].data as FSData, - csg: (client.xjs.xml2js(hitCSG[1].data,{compact:true}) as any).careerSavegame as FSCareerSavegame - } - } as const; - console.log(APIData['Daggerwin'].csg) - console.log(APIData['SecondServer'].csg) - //console.log((APIData.Daggerwin.dss as FSData).server.name) - //console.log((APIData.Daggerwin.csg as FSCareerSavegame).statistics.money) + const hitDSS = await fetch(Server.ip+'/feed/dedicated-server-stats.json?code='+Server.code, sessionInit).then(r=>r.json() as Promise); + const hitCSG = await fetch(Server.ip+'/feed/dedicated-server-savegame.html?code='+Server.code+'&file=careerSavegame', sessionInit).then(async r=>{ + if (r.status === 204) return msg.edit({embeds: [new client.embed().setImage(noContentImage)]}); + else return (client.xjs.xml2js(await r.text(), {compact: true}) as any).careerSavegame as FSCareerSavegame + }).catch(()=>console.log(client.logTime(), `${MPLoopPrefix}CSG failed for ${Server.ip.replace(/^(https?)(\:\/\/)/, '')}`)); + + if (!hitDSS ?? !hitCSG){ + if (hitDSS && !hitDSS.slots) return new Error(`${MPLoopPrefix}DSS failed for unknown slots table`); + return msg.edit({embeds: [new client.embed().setColor(client.config.embedColorRed).setTitle('Host did not respond back in time')]}); + } + msg.edit({content: [ - ServerName, - (APIData[ServerName].csg as FSCareerSavegame).settings.savegameName._text - ].join('\n')}) + Server.ip.replace(/^(https?)(\:\/\/)/, ''), + hitDSS.server.name.length > 0 ? hitDSS.server.name : 'Offline' + ].join('\n'), embeds: []}) } catch(err) { - msg.edit({content: err.message}) - throw new Error('HITALL failed to make a promise request', {cause: err.cause}); + throw new Error(`${MPLoopPrefix}Failed to make a promise request.`, {cause: err.cause}) } } HITALL(); - - /* await Promise.all([ - client.axios.get(servers.main.dss,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}), - client.axios.get(servers.main.csg,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}), - client.axios.get(servers.second.dss,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}), - client.axios.get(servers.second.csg,{timeout:7500,maxContentLength:Infinity,headers:{'User-Agent':`Daggerbot/axios ${client.axios.VERSION}`}}) - ]).then(x=>x.map(x=>x.data)).catch(()=>{throw new Error('[MPLOOP] Failed to make a promise request.')}); - msg.edit({content: ServerName, embeds: []}) */ } /* export default async(client:TClient,Channel:string,Message:string,ServerName:string)=>{ diff --git a/src/config.json b/src/config.json index 943c242..2da93c0 100644 --- a/src/config.json +++ b/src/config.json @@ -12,11 +12,11 @@ "929807948748832798", "468835415093411861", "1058183358267543552", "549114074273677314" ], "MPStatsLocation": { - "main": { + "mainServer": { "channel": "543494084363288637", "message": "1023699243183112192" }, - "second": { + "secondServer": { "channel": "", "message": "" } diff --git a/src/index.ts b/src/index.ts index 790eb5b..a37b4a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -68,10 +68,9 @@ if (client.config.botSwitches.music){ // YouTube Upload notification and Daggerwin MP loop setInterval(async()=>{ - console.log(client.logTime(), 'MPLoop Interval') - MPLoop(client, client.config.MPStatsLocation.main.channel, client.config.MPStatsLocation.main.message, 'Daggerwin') - MPLoop(client, client.config.MPStatsLocation.second.channel, client.config.MPStatsLocation.second.message, 'SecondServer') -}, 30000); + const serverlake = (await client.MPServer._content.findById(client.config.mainServer.id)); + for await (const [locName, locArea] of Object.entries(client.config.MPStatsLocation)) await MPLoop(client, locArea.channel, locArea.message, serverlake[locName]) +}, 10000); setInterval(async()=>{ client.YTLoop('UCQ8k8yTDLITldfWYKDs3xFg', 'Daggerwin', '528967918772551702'); // 528967918772551702 = #videos-and-streams client.YTLoop('UCguI73--UraJpso4NizXNzA', 'Machinery Restorer', '767444045520961567') // 767444045520961567 = #machinery-restorer From 67a728dc09fa7ce5878cbcba6e8d63d089c39fd5 Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Sat, 19 Aug 2023 22:50:05 +1000 Subject: [PATCH 10/11] I am so broken and confusion --- src/MPLoop.ts | 266 ++++++---------- src/client.ts | 18 +- src/commands/mp.ts | 591 +++++++++++++++++++++++++++++++----- src/index.ts | 6 +- src/typings/interfaces.d.ts | 19 +- 5 files changed, 621 insertions(+), 279 deletions(-) diff --git a/src/MPLoop.ts b/src/MPLoop.ts index 2a13408..eae2e71 100644 --- a/src/MPLoop.ts +++ b/src/MPLoop.ts @@ -1,202 +1,110 @@ -interface TServer { - ip: string - code: string -} import Discord from 'discord.js'; import TClient from './client'; import {writeFileSync, readFileSync} from 'node:fs'; -import {FSPlayer, FSData, FSCareerSavegame} from './typings/interfaces'; +import {FSPlayer, FSData, FSCareerSavegame, TServer} from './typings/interfaces'; -export default async(client:TClient, Channel:string, Message:string, Server:TServer)=>{ - if (!client.config.botSwitches.mpstats) return; +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 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); + const serverErrorEmbed = new client.embed().setColor(client.config.embedColorRed).setTitle('Host did not respond back in time'); + const genericEmbed = new client.embed(); // Log bot uptime for the sake of debugging. (client.channels.resolve('1091300529696673792') as Discord.TextChannel).send(client.formatTime(client.uptime, 2, {longNames: true, commas: true})); + const decoPlayer = (player:FSPlayer)=>{ + let decorator = player.isAdmin ? ':detective:' : ''; + 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 { const hitDSS = await fetch(Server.ip+'/feed/dedicated-server-stats.json?code='+Server.code, sessionInit).then(r=>r.json() as Promise); - const hitCSG = await fetch(Server.ip+'/feed/dedicated-server-savegame.html?code='+Server.code+'&file=careerSavegame', sessionInit).then(async r=>{ - if (r.status === 204) return msg.edit({embeds: [new client.embed().setImage(noContentImage)]}); - else return (client.xjs.xml2js(await r.text(), {compact: true}) as any).careerSavegame as FSCareerSavegame - }).catch(()=>console.log(client.logTime(), `${MPLoopPrefix}CSG failed for ${Server.ip.replace(/^(https?)(\:\/\/)/, '')}`)); - + 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 for unknown slots table`); - return msg.edit({embeds: [new client.embed().setColor(client.config.embedColorRed).setTitle('Host did not respond back in time')]}); + if (hitDSS && !hitDSS.slots) return new Error(`${MPLoopPrefix}DSS failed with unknown slots table for ${ServerName}`); + 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]}); } - msg.edit({content: [ - Server.ip.replace(/^(https?)(\:\/\/)/, ''), - hitDSS.server.name.length > 0 ? hitDSS.server.name : 'Offline' - ].join('\n'), embeds: []}) + //Timescale formatting + function formatTimescale(number:number,digits:number,icon:string){ + var n = Number(number); + return n.toLocaleString(undefined, {minimumFractionDigits: digits})+icon + } + + // Join/Leave log + function playerLogEmbed(player:FSPlayer,joinLog:boolean){ + const logEmbed = new client.embed().setDescription(`**${player.name}${decoPlayer(player)}** ${joinLog ? 'joined' : 'left'} **${placeholder}** 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)}`)); + + 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){ + 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' */ + } else { + /* if (client.MPServerCache[ServerName]?.status === 'offline' ?? null){ + serverLog.send({embeds:[serverIndicatorEmbed('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}, + {name: 'In-game Time', value: `${('0'+Math.floor((hitDSS.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((hitDSS.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}, + {name: 'Slot Usage', value: isNaN(Number(hitCSG.slotSystem?._attributes.slotUsage)) === true ? 'Unavailable' : Number(hitCSG.slotSystem?._attributes.slotUsage).toLocaleString('en-us'), inline: true}, + {name: 'Autosave Interval', value: isNaN(Number(hitCSG.settings?.autoSaveInterval._text)) === true ? 'Unavailable' : Number(hitCSG.settings?.autoSaveInterval._text).toFixed(0)+' mins', inline:true}, + {name: 'Timescale', value: isNaN(Number(hitCSG.settings?.timeScale._text)) === true ? 'Unavailable' : formatTimescale(Number(hitCSG.settings?.timeScale._text), 0, 'x'), inline: true} + ); + const playersEmbed = new client.embed().setColor(client.config.embedColor).setTitle(hitDSS.server.name).setDescription(hitDSS.slots.used < 1 ? '*No players online*' : playerData.join('\n\n')).setAuthor({name:`${hitDSS.slots.used}/${hitDSS.slots.capacity}`}); + msg.edit({content:'This embed updates every 30 seconds.',embeds:[serverDetails, playersEmbed]}); + } } catch(err) { - throw new Error(`${MPLoopPrefix}Failed to make a promise request.`, {cause: err.cause}) + 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}) } } HITALL(); +// Hit dem servers in the head every 30 seconds. } - -/* export default async(client:TClient,Channel:string,Message:string,ServerName:string)=>{ - if (!client.config.botSwitches.mpstats) return; - const noContentImage = 'https://cdn.discordapp.com/attachments/1118960531135541318/1140906691236479036/68efx1.png'; - const msg = await (client.channels.resolve(Channel) as Discord.TextChannel).messages.fetch(Message); - const embed = new client.embed(); - let playerData:Array = []; - let error:Boolean; - let isServerOnline = false; - const fetchServer = await client.MPServer._content.findById(client.config.mainServer.id); - - const API = { // Fetch needed data from Farming Simulator server's API endpoints. - DSS: { - data: {} as FSData, - res: '' as string, - endpoint: '/feed/dedicated-server-stats.json?code=' - }, - CSG: { - data: {} as FSCareerSavegame, - res: '' as string, - endpoint: '/feed/dedicated-server-savegame.html?code=', - endpoint_file: '&file=careerSavegame' - } - } - - // Fetch needed data from database server and hit them with a GET request. - if (!fetchServer.mainServer.ip(/http|https/) ?? !fetchServer.secondServer.ip(/http|https/)) return msg.edit({content:'*This server doesn\'t seem to be setup yet!*', embeds:null}); - async function hitServer(client:TClient, URL:string){ - return await client.axios.get(URL, { - timeout: 7500, // Increased the timeout a bit just in case. - maxContentLength: Infinity, - headers: { - 'User-Agent': `Daggerbot/axios ${client.axios.VERSION}` - } - }).catch((err:Error)=>err.message) - } - await Promise.all([ - hitServer(client, fetchServer.mainServer.ip+API.DSS.endpoint+fetchServer.mainServer.code), - hitServer(client, fetchServer.mainServer.ip+API.CSG.endpoint+fetchServer.mainServer.code+API.CSG.endpoint_file), - hitServer(client, fetchServer.secondServer.ip+API.DSS.endpoint+fetchServer.secondServer.code), - hitServer(client, fetchServer.secondServer.ip+API.CSG.endpoint+fetchServer.secondServer.code+API.CSG.endpoint_file) - ]).then(function(results){ - // Main server's DSS - if (typeof results[0] === 'string') { - API.DSS.res = `DagMP:Main DSS failed, ${results[0]}`; - embed.addFields({name:'DSS Status',value:results[0]}) - } else if (results[0].status != 200) { - API.DSS.res = `DagMP:Main DSS failed with ${results[0].status +' '+ results[0].statusText}`; - embed.addFields({name:'DSS Status',value:results[0].status +' '+ results[0].statusText}) - } else API.DSS.data = results[0].data as FSData - - // Main server's CSG - if (typeof results[1] === 'string') { - API.CSG.res = `DagMP:Main CSG failed, ${results[1]}`; - embed.addFields({name:'CSG Status',value:results[1]}) - } else if (results[1].status != 200) { - if (results[1].status === 204) embed.setImage(noContentImage); - API.CSG.res = `DagMP:Main CSG failed with ${results[1].status +' '+ results[1].statusText}`; - embed.addFields({name:'CSG Status',value:results[1].status +' '+ results[1].statusText}) - } else API.CSG.data = (client.xjs.xml2js(results[1].data,{compact:true}) as any).careerSavegame as FSCareerSavegame - - // Second server's DSS - if (typeof results[2] === 'string') { - API.DSS.res = `DagMP:Second DSS failed, ${results[2]}`; - embed.addFields({name:'DSS Status',value:results[2]}) - } else if (results[2].status != 200) { - API.DSS.res = `DagMP:Second DSS failed with ${results[2].status +' '+ results[2].statusText}`; - embed.addFields({name:'DSS Status',value:results[2].status +' '+ results[2].statusText}) - } else API.DSS.data = results[2].data as FSData - - // Second server's CSG - if (typeof results[3] === 'string') { - API.CSG.res = `DagMP:Second CSG failed, ${results[3]}`; - embed.addFields({name:'CSG Status',value:results[3]}) - } else if (results[3].status != 200) { - if (results[3].status === 204) embed.setImage(noContentImage); - API.CSG.res = `DagMP:Second CSG failed with ${results[3].status +' '+ results[3].statusText}`; - embed.addFields({name:'CSG Status',value:results[3].status +' '+ results[3].statusText}) - } else API.CSG.data = (client.xjs.xml2js(results[3].data,{compact:true}) as any).careerSavegame as FSCareerSavegame - }).catch((err:Error)=>console.error(err.message)) - - if (API.DSS.res.length != 0) { - error = true; - if (API.DSS.data.slots === undefined) return; - console.log(client.logTime(), API.DSS.res); - } else if (API.CSG.res.length != 0) { - error = true; - console.log(client.logTime(), API.CSG.res); - } - if (error) {// Nawdic broke it in his dream - embed.setTitle('Host did not respond back in time').setColor(client.config.embedColorRed); - return msg.edit({content:null, embeds:[embed]}) - } - - //Timescale formatting - function formatTimescale(number:number,digits:number,icon:string){ - var n = Number(number); - return n.toLocaleString(undefined, {minimumFractionDigits: digits})+icon - } - // Join/Leave log - function playerLogEmbed(player:FSPlayer,joinLog:boolean){ - const logEmbed = new client.embed().setDescription(`**${player.name} ${player.isAdmin ? '| admin' : ''}** ${joinLog ? 'joined' : 'left'} **${ServerName}** 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 as number) > 60010) 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)]})); - } - - const serverIndicatorEmbed =(indicator:string)=>new client.embed().setTitle(`**${ServerName}** 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 = API.DSS.data.slots?.players.filter(x=>x.isUsed); - const playersInCache = client.MPServerCache[ServerName].players; - if (!playersOnServer) return console.error(client.logTime(), '[MPLoop] Empty filter, ignoring...'); // For the love of god, stop throwing errors everytime. - playersOnServer.forEach(player=>playerData.push(`**${player.name} ${player.isAdmin ? '| admin' : ''}**\nFarming for ${client.formatPlayerUptime(player.uptime)}`)); - - ServerName = client.MPServerCache[ServerName].name; // Truncate unnecessary name for the embed - if (API.DSS.data.server.name === 'Official Daggerwin Game Server') client.MPServerCache['main'].name = 'Daggerwin'; - //Second server name is unknown, will come back and update this later. - - if (API.DSS.data.server.name.length === 0){ - embed.setTitle('The server seems to be offline.').setColor(client.config.embedColorRed); - msg.edit({content: 'This embed will resume when the server is back online.', embeds: [embed]}); - if (client.MPServerCache[ServerName].status === 'online') serverLog.send({embeds:[serverIndicatorEmbed('offline')]}); - client.MPServerCache[ServerName].status = 'offline'; - } else { - if (client.MPServerCache[ServerName].status === 'offline'){ - serverLog.send({embeds:[serverIndicatorEmbed('online')]}); - isServerOnline = true - }; - client.MPServerCache[ServerName].status = 'online'; - const statusEmbed = new client.embed().setColor(client.config.embedColor).setTitle('Server details').setFields( - {name: 'Current Map', value: API.DSS.data.server.mapName.length === 0 ? '\u200b' : API.DSS.data.server.mapName, inline: true}, - {name: 'Version', value: API.DSS.data.server.version.length === 0 ? '\u200b' : API.DSS.data.server.version, inline: true}, - {name: 'In-game Time', value: `${('0'+Math.floor((API.DSS.data.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((API.DSS.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}, - {name: 'Slot Usage', value: isNaN(Number(API.CSG.data.slotSystem?._attributes.slotUsage)) === true ? 'Unavailable' : Number(API.CSG.data.slotSystem?._attributes.slotUsage).toLocaleString('en-us'), inline: true}, - {name: 'Autosave Interval', value: isNaN(Number(API.CSG.data.settings?.autoSaveInterval._text)) === true ? 'Unavailable' : Number(API.CSG.data.settings?.autoSaveInterval._text).toFixed(0)+' mins', inline:true}, - {name: 'Timescale', value: isNaN(Number(API.CSG.data.settings?.timeScale._text)) === true ? 'Unavailable' : formatTimescale(Number(API.CSG.data.settings?.timeScale._text), 0, 'x'), inline: true} - ); - embed.setColor(client.config.embedColor).setTitle(API.DSS.data.server.name).setDescription(API.DSS.data.slots.used === 0 ? '*No players online*' : playerData.join('\n\n')).setAuthor({name:`${API.DSS.data.slots.used}/${API.DSS.data.slots.capacity}`}); - msg.edit({content:'This embed updates every minute.',embeds:[statusEmbed,embed]}); - } - - if (!isServerOnline){ - playerLog(); - const Database:Array = JSON.parse(readFileSync(`src/database/${ServerName}PlayerData.json`,{encoding:'utf8',flag:'r+'})); - Database.push(API.DSS.data.slots?.used); - writeFileSync(`src/database/${ServerName}PlayerData.json`, JSON.stringify(Database)); - client.MPServerCache[ServerName].players = playersOnServer - } -} - */ \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index 20acf84..d98f73e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2,7 +2,7 @@ import Discord, {Client, WebhookClient, GatewayIntentBits, Partials} from 'disco import {readFileSync, readdirSync} from 'node:fs'; import {exec} from 'node:child_process'; import mongoose from 'mongoose'; -import {formatTimeOpt, Tokens, Config, repeatedMessages, MPServerCache} from './typings/interfaces'; +import {formatTimeOpt, Tokens, Config, repeatedMessages, type MPServerCache} from './typings/interfaces'; import bannedWords from './models/bannedWords.js'; import userLevels from './models/userLevels.js'; import suggestion from './models/suggestion.js'; @@ -45,7 +45,7 @@ export default class TClient extends Client { bonkCount: bonkCount; bannedWords: bannedWords; MPServer: MPServer; - MPServerCache: MPServerCache; + MPServerCache: MPServerCache = {}; suggestion: suggestion; tags: tags; repeatedMessages: repeatedMessages; @@ -85,10 +85,7 @@ export default class TClient extends Client { this.punishments = new punishments(this); this.bannedWords = new bannedWords(this); this.MPServer = new MPServer(this); - this.MPServerCache = { - main: { players: [], status: null, name: null }, - second: { players: [], status: null, name: null } - } as MPServerCache; + this.MPServerCache = {} as MPServerCache; this.suggestion = new suggestion(this); this.tags = new tags(this); this.repeatedMessages = {}; @@ -111,16 +108,21 @@ export default class TClient extends Client { }).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); for await (const file of readdirSync('dist/events')){ - //console.log('EVENTS:', file) const eventFile = await import(`./events/${file}`); this.on(file.replace('.js',''), async(...args)=>eventFile.default.run(this,...args)) } for await (const file of readdirSync('dist/commands')){ - //console.log('COMMANDS:', file) const command = await import(`./commands/${file}`); this.commands.set(command.default.data.name,{command, uses: 0}); this.registry.push(command.default.data.toJSON()) } + for (const naming of Object.keys(this.config.MPStatsLocation)){ + this.MPServerCache[naming] = { + players: [], + status: null, + name: null + } + } } formatTime(integer: number, accuracy = 1, options?: formatTimeOpt){ let achievedAccuracy = 0; diff --git a/src/commands/mp.ts b/src/commands/mp.ts index 411222a..309fad0 100644 --- a/src/commands/mp.ts +++ b/src/commands/mp.ts @@ -3,91 +3,526 @@ import TClient from '../client.js'; import path from 'node:path'; import canvas from 'canvas'; import {readFileSync} from 'node:fs'; +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.') + 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); + ({ + 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(); + }; + } + + // 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; + + for (const player of endpoint.slots.players.filter(x=>x.isUsed)){ + let decorator = player.isAdmin ? ':detective:' : ''; + decorator += player.name.includes('Toast') ? '<:toastv2:1132681026662056079>' : ''; + playerData.push(`**${player.name}${decorator}**\nFarming for ${client.formatPlayerUptime(player.uptime)}`) + } + + 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'})]}) + }, + 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'>){ - async function hitEndpoint(){ - const array = (await client.MPServer?._content.find())?.map(x=>x._id).filter(c=>['mainServer','secondServer'].includes(c)); - console.log(array?.map(c=>c)); - - /* const database = { - mainServer: (await client.MPServer._content.findById(interaction.guildId)).mainServer, - secondServer: (await client.MPServer._content.findById(interaction.guildId)).secondServer - } - const endpoint = '/feed/dedicated-server-stats.json?code='; - if (serverSelector === 'mainServer') return database.mainServer.ip+endpoint+database.mainServer.code; - else if (serverSelector === 'secondServer') return database.secondServer.ip+endpoint+database.secondServer.code; - const Server = await client.axios.get(serverSelector, { - timeout: 7500, - headers: {'User-Agent':`Daggerbot - mp cmd/axios ${client.axios.VERSION}`} - }) */ - } - - 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; - } + 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()=>{ - hitEndpoint() - interaction.reply('x') + 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()=>{}, - url: async()=>{}, - players: async()=>{} + 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 < 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)`, + '**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/${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'); + } + 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))) + .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))) } + */ \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index a37b4a4..7ce5a50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,10 +67,10 @@ if (client.config.botSwitches.music){ } // YouTube Upload notification and Daggerwin MP loop -setInterval(async()=>{ +if (client.config.botSwitches.mpstats) setInterval(async()=>{ const serverlake = (await client.MPServer._content.findById(client.config.mainServer.id)); - for await (const [locName, locArea] of Object.entries(client.config.MPStatsLocation)) await MPLoop(client, locArea.channel, locArea.message, serverlake[locName]) -}, 10000); + for await (const [locName, locArea] of Object.entries(client.config.MPStatsLocation)) await MPLoop(client, locArea.channel, locArea.message, serverlake[locName], locName) +}, 30000); setInterval(async()=>{ client.YTLoop('UCQ8k8yTDLITldfWYKDs3xFg', 'Daggerwin', '528967918772551702'); // 528967918772551702 = #videos-and-streams client.YTLoop('UCguI73--UraJpso4NizXNzA', 'Machinery Restorer', '767444045520961567') // 767444045520961567 = #machinery-restorer diff --git a/src/typings/interfaces.d.ts b/src/typings/interfaces.d.ts index 4e05c10..850ad13 100644 --- a/src/typings/interfaces.d.ts +++ b/src/typings/interfaces.d.ts @@ -192,15 +192,12 @@ export interface Config { } } } -export interface MPServerCache { - main: { - players: FSPlayer[], - status: 'online' | 'offline' | null, - name: string | null - }, - second: { - players: FSPlayer[], - status: 'online' | 'offline' | null, - name: string | null - } +export type MPServerCache = Record +export interface TServer { + ip: string + code: string } \ No newline at end of file From d99032f81b39c3b111ac476550e794caee320918 Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Sun, 20 Aug 2023 10:17:22 +1000 Subject: [PATCH 11/11] 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))) -}