1
0
mirror of https://github.com/toast-ts/Daggerbot-TS.git synced 2024-11-17 00:10:58 -05:00

Merge pull request #18 from AnxietyisReal/mpserver-revamp

Revamp MPLoop and improve other stuff.
This commit is contained in:
Toast 2023-08-20 10:19:26 +10:00 committed by GitHub
commit 71a4f21840
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 515 additions and 468 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@
dist/
src/database/
src/*.json
src/MPLoopRaw.log

124
.pnp.cjs generated
View File

@ -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.51"],\
["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.51"]\
["@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.51"],\
["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.51"],\
["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.51"],\
["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.51", {\
"packageLocation": "./.yarn/cache/discord-api-types-npm-0.37.51-89cec04373-0f6c8a5c31.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.51"]\
["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.51"],\
["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],\

View File

@ -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",

View File

@ -1,122 +1,98 @@
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,ServerName:string)=>{
if (!client.config.botSwitches.mpstats) return;
const msg = await (client.channels.resolve(Channel) as Discord.TextChannel).messages.fetch(Message);
const embed = new client.embed();
let playerData:Array<string> = [];
let error:Boolean;
export default async(client:TClient, Channel:string, Message:string, Server:TServer, ServerName:string)=>{
let MPLoopPrefix = '[MPLoop] ';
//let httpRegex = /^(https?)(\:\/\/)/;
let isServerOnline = false;
const Server = await client.MPServer._content.findById(client.config.mainServer.id);
let playerData:Array<string> = [];
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();
const DSS = {
data: {} as FSData, fetchResult: '' as string
};
const CSG = {
data: {} as FSCareerSavegame, fetchResult: '' as string
};
// 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}));
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)
}
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]}`;
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
if (typeof results[1] === 'string'){
CSG.fetchResult = `DagMP 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));
if (DSS.fetchResult.length != 0){
error = true;
if (DSS.data.slots === undefined) return;
console.log(client.logTime(), DSS.fetchResult);
} else if (CSG.fetchResult.length != 0){
error = true;
console.log(client.logTime(), CSG.fetchResult);
}
if (error){ // Blame Nawdic
embed.setTitle('Host is not responding').setColor(client.config.embedColorRed);
msg.edit({content:null, embeds: [embed]})
return;
const decoPlayer = (player:FSPlayer)=>{
let decorator = player.isAdmin ? ':detective:' : '';
decorator += player.name.includes('Toast') ? '<:toastv2:1132681026662056079>' : '';
return decorator
}
//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 <t:${Math.round(Date.now()/1000)}:t>`);
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 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<FSData>);
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);
const serverIndicatorEmbed =(indicator:string)=>new client.embed().setTitle(`**${ServerName}** is now ${indicator}`).setColor(client.config.embedColorOrange).setTimestamp();
if (!hitDSS ?? !hitCSG){
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]});
}
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.
playersOnServer.forEach(player=>playerData.push(`**${player.name} ${player.isAdmin ? '| admin' : ''}**\nFarming for ${client.formatPlayerUptime(player.uptime)}`));
// 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.
ServerName = client.MPServerCache.name;
if (DSS.data.server.name === 'Official Daggerwin Game Server') client.MPServerCache.name = 'Daggerwin';
//Timescale formatting
function formatTimescale(number:number,digits:number,icon:string){
var n = Number(number);
return n.toLocaleString(undefined, {minimumFractionDigits: digits})+icon
}
if (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';
} else {
if (client.MPServerCache.status === 'offline'){
serverLog.send({embeds:[serverIndicatorEmbed('online')]});
isServerOnline = true
};
client.MPServerCache.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}
);
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}`});
msg.edit({content:'This embed updates every minute.',embeds:[statusEmbed,embed]});
}
if (!isServerOnline){
playerLog();
const Database:Array<number> = 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
// Join/Leave log
function playerLogEmbed(player:FSPlayer,joinLog:boolean){
const logEmbed = new client.embed().setDescription(`**${player.name}${decoPlayer(player)}** ${joinLog ? 'joined' : 'left'} **${client.MPServerCache[ServerName].name}** at <t:${Math.round(Date.now()/1000)}:t>`);
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);
}
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<number> = 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;
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.')]});
client.MPServerCache[ServerName].status = 'offline'
} else {
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) {
msg.edit({content: null, embeds: [new client.embed().setColor(client.config.embedColorRed).setTitle('Host did not respond back in time')]});
throw new Error(`Failed to make a request for ${client.MPServerCache[ServerName].name}`, {cause: err.cause})
}
}
HITALL();
// Hit dem servers in the head every 30 seconds.
}

View File

@ -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,7 +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 = {players: [], status: null, name: null} as MPServerCache;
this.MPServerCache = {} as MPServerCache;
this.suggestion = new suggestion(this);
this.tags = new tags(this);
this.repeatedMessages = {};
@ -105,19 +105,24 @@ 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('Failed to connect to MongoDB'); exec('pm2 stop Daggerbot', {windowsHide:true})})
this.login(this.tokens.main);
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;

View File

@ -3,64 +3,182 @@ 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) {
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
}
import {FSData, TServer} from 'src/typings/interfaces.js';
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;
}
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
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 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<FSData>);
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);
// 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.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()=>{
const embed0 = new client.embed();
const FSserver0 = await MPdata(client, interaction, embed0);
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)
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 == 0 ? 'Null Island' : FSserver2.data.server.mapName}`,
`**Mods:** [Click here](${MPURL.ip}/mods.html) **|** [Direct Download](${MPURL.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'))]});
@ -73,176 +191,40 @@ export default {
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')}`)
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('**Database error:**\nThis server does not have the URL saved, try adding one.')
interaction.reply(`\`\`\`${err}\`\`\``)
}
}else{
} 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<number> = [];
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<number> = [];
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
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}`))
}
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;
} 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.')
}
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) {
@ -260,47 +242,64 @@ export default {
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]})
}*/
} 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('The server to update')
.setRequired(true)
.setChoices(
{name: 'Main Server', value: 'mainServer'},
{name: 'Second Server', value: 'secondServer'}
)))
.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'))
.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 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('The server to update')
.setRequired(true)
.setChoices(
{name: 'Main Server', value: 'mainServer'},
{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)))/*
.addSubcommand(x=>x
.setName('series')
.setDescription('Step-by-step on joining Daggerwin\'s MP series'))*/
.setDescription('The message to display in the channel')))
}

View File

@ -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})}\``)
},

View File

@ -12,8 +12,14 @@
"929807948748832798", "468835415093411861", "1058183358267543552", "549114074273677314"
],
"MPStatsLocation": {
"channel": "543494084363288637",
"message": "1023699243183112192"
"mainServer": {
"channel": "543494084363288637",
"message": "1023699243183112192"
},
"secondServer": {
"channel": "",
"message": ""
}
},
"botSwitches": {
"dailyMsgsBackup": true,

View File

@ -41,7 +41,8 @@ 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'));
client.on('debug', console.log).on('warn', console.log);
// Audio Player event handling
if (client.config.botSwitches.music){
@ -66,11 +67,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);
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], locName)
}, 30000);
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()=>{

View File

@ -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})
}
}

View File

@ -137,8 +137,14 @@ export interface Config {
LRSstart: number,
whitelistedServers: Array<string>,
MPStatsLocation: {
channel: string,
message: string
main: {
channel: string
message: string
},
second: {
channel: string
message: string
}
},
botSwitches: {
dailyMsgsBackup: boolean,
@ -186,8 +192,12 @@ export interface Config {
}
}
}
export interface MPServerCache {
export type MPServerCache = Record<string,{
players: FSPlayer[],
status: 'online' | 'offline' | null,
name: string | null
}>
export interface TServer {
ip: string
code: string
}

View File

@ -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" ]
}

123
yarn.lock
View File

@ -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.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.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"