diff --git a/commands/admin.js b/commands/admin.js index 1635dfa..2293105 100644 --- a/commands/admin.js +++ b/commands/admin.js @@ -1,6 +1,6 @@ const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder } = require('discord.js'); const bitfieldCalculator = require('discord-bitfield-calculator'); -const { BanPlayer, UnbanPlayer, RestartServer } = require('../util/NitradoAPI'); +const { BanPlayer, UnbanPlayer, RestartServer, CheckServerStatus } = require('../util/NitradoAPI'); const { Armbands } = require('../config/armbandsdb.js'); module.exports = { @@ -142,6 +142,11 @@ module.exports = { description: "Restart the DayZ Server", value: "restart", type: 1, + }, { + name: "auto-restart", + description: "Enable/Disable periodic server checks and restart if stopped.", + value: "auto-restart", + type: 1, }], SlashCommand: { /** @@ -396,7 +401,7 @@ module.exports = { ? banking.guilds[GuildDB.serverID].balance + args[0].options[0].value : banking.guilds[GuildDB.serverID].balance - args[0].options[0].value; - client.dbo.collection("users").updateOne({"user.userID":targetUserID},{$set:{[`user.guilds.${GuildDB.serverID}.balance`]:newBalance}}, (err, res) => { + client.dbo.collection("users").updateOne({"user.userID":targetUserID},{$set:{[`user.guilds.${GuildDB.serverID}.balance`]:newBalance}}, (err, res) => { if (err) return client.sendInternalError(interaction, err); }); @@ -407,8 +412,40 @@ module.exports = { return interaction.send({ embeds: [successEmbed] }); } else if (args[0].name == "restart") { - RestartServer(client); + // Write optional "restart_message" to set in the Nitrado server logs and send a notice "message" to your server community. + restart_message = 'Server being restarted by an admin.'; + message = 'The server was restarted by an admin!'; + + RestartServer(client, restart_message, message); return interaction.send({ embeds: [new EmbedBuilder().setColor(client.config.Colors.Yellow).setDescription('The server will restart shortly.')], flags: (1 << 6) }); + + } else if (args[0].name == "auto-restart") { + msg = 'Auto server restart periodic check enabled.'; + pref = 0; + + // Enable/Disable a 10min periodic server status check. + if (!client.arIntervalId) { + client.arIntervalId = setInterval(CheckServerStatus, client.arInterval, client); + client.log('Enabled and starting periodic Nitrado server status check.'); + pref = 1; + } else { + msg = 'Auto server restart periodic check disabled.' + clearInterval(client.arIntervalId); + client.arIntervalId = 0; + client.log('Disabled periodic Nitrado server status check.'); + } + + // Update DB preference + GuildDB.autoRestart = pref; + client.dbo.collection("guilds").updateOne({ "server.serverID": GuildDB.serverID }, { + $set: { + "server.autoRestart": GuildDB.autoRestart + } + }, function (err, res) { + if (err) return client.sendInternalError(interaction, err); + }); + + return interaction.send({ embeds: [new EmbedBuilder().setColor(client.config.Colors.Yellow).setDescription(msg)] }); } } }, diff --git a/structures/DayzRBot.js b/structures/DayzRBot.js index 5b67e95..fd7cecb 100644 --- a/structures/DayzRBot.js +++ b/structures/DayzRBot.js @@ -6,7 +6,7 @@ const Logger = require("../util/Logger"); const mongoose = require('mongoose'); // custom util imports -const { DownloadNitradoFile } = require('../util/NitradoAPI'); +const { DownloadNitradoFile, CheckServerStatus } = require('../util/NitradoAPI'); const { HandlePlayerLogs, HandleActivePlayersList } = require('../util/LogsHandler'); const { HandleKillfeed } = require('../util/KillfeedHandler'); const { HandleExpiredUAVs, HandleEvents } = require('../util/AlarmsHandler'); @@ -16,6 +16,7 @@ const fs = require('fs'); const readline = require('readline'); const minute = 60000; // 1 minute in milliseconds +const arInterval = 600000; // Set auto-restart interval 10mins (600,000ms) class DayzRBot extends Client { @@ -35,8 +36,10 @@ class DayzRBot extends Client { this.db; this.dbo; - this.connectMongo(this.config.mongoURI, this.config.dbo); this.databaseConnected = false; + this.arInterval = arInterval + this.arIntervalId; // Interval for auto-restart functions + this.autoRestartInit(); this.LoadCommandsAndInteractionHandlers(); this.LoadEvents(); @@ -192,7 +195,7 @@ class DayzRBot extends Client { history.lastLog = lines[lines.length-1]; // write JSON string to a file - await fs.writeFileSync(logHistoryDir, JSON.stringify(history)); + fs.writeFileSync(logHistoryDir, JSON.stringify(history)); } async logsUpdateTimer(c) { @@ -257,6 +260,19 @@ class DayzRBot extends Client { if (failed) process.exit(-1); } + async autoRestartInit() { + // Wait for MongoDB to connect + await this.connectMongo(this.config.mongoURI, this.config.dbo); + + let is_enabled = undefined; + if (this.databaseConnected) is_enabled = await this.dbo.collection("guilds").findOne({"server.autoRestart":1}).then(is_enabled => is_enabled); + + if (is_enabled) { + this.log('Starting periodic Nitrado server status check.'); + this.arIntervalId = setInterval(CheckServerStatus, this.arInterval, this); + } + } + exists(n) {return null != n && undefined != n && "" != n} secondsToDhms(seconds) { @@ -346,6 +362,7 @@ class DayzRBot extends Client { getDefaultSettings(GuildId) { return { serverID: GuildId, + autoRestart: 0, allowedChannels: [], killfeedChannel: "", showKillfeedCoords: false, @@ -417,6 +434,7 @@ class DayzRBot extends Client { return { serverID: GuildId, + autoRestart: guild.server.autoRestart, customChannelStatus: guild.server.allowedChannels.length > 0 ? true : false, allowedChannels: guild.server.allowedChannels, factionArmbands: guild.server.factionArmbands, @@ -429,6 +447,7 @@ class DayzRBot extends Client { events: guild.server.events, uavs: guild.server.uavs, killfeedChannel: guild.server.killfeedChannel, + showKillfeedCoords: guild.server.showKillfeedCoords, connectionLogsChannel: guild.server.connectionLogsChannel, welcomeChannel: guild.server.welcomeChannel, activePlayersChannel: guild.server.activePlayersChannel, diff --git a/util/NitradoAPI.js b/util/NitradoAPI.js index 8edd3a2..454e3e5 100644 --- a/util/NitradoAPI.js +++ b/util/NitradoAPI.js @@ -43,6 +43,7 @@ const HandlePlayerBan = async (client, gamertag, ban) => { } sendList(); })); + return 0; } catch (error) { if (retries === maxRetries) throw new Error(`HandlePlayerBans: Failed to fetch data after ${maxRetries} retries`); } @@ -89,22 +90,63 @@ module.exports = { BanPlayer: async (client, gamertag) => HandlePlayerBan(client, gamertag, true), UnbanPlayer: async (client, gamertag) => HandlePlayerBan(client, gamertag, false), - - RestartServer: async (client) => { + + RestartServer: async (client, restart_message, message) => { + const params = { + restart_message: restart_message, + message: message + }; + // client.log('Restarting server...'); for (let retries = 0; retries < maxRetries; retries++) { try { const res = await fetch(`https://api.nitrado.net/services/${client.config.Nitrado.ServerID}/gameservers/restart`, { method: "POST", headers: { "Authorization": client.config.Nitrado.Auth, - } - }).then(response => - response.json().then(data => data) - ).then(res => res); + }, + body: JSON.stringify(params) + }); + return 0; } catch (error) { + client.error(`Error during restart request: ${error.message}`); if (retries === maxRetries) throw new Error(`RestartServer: Failed to fetch data after ${maxRetries} retries`); } await new Promise(resolve => setTimeout(resolve, retryDelay)); // Delay before retrying } }, -} \ No newline at end of file + + CheckServerStatus: async (client) => { + for (let retries = 0; retries <= maxRetries; retries++) { + try { + // get current status + const res = await fetch(`https://api.nitrado.net/services/${client.config.Nitrado.ServerID}/gameservers`, { + headers: { + "Authorization": client.config.Nitrado.Auth + } + }); + + if (!res.ok) { + const errorText = await res.text(); + client.error(`Failed to get Nitrado server stats (${client.config.Nitrado.ServerID}): status: ${res.status}, message: ${errorText}: CheckServerStatus`); + } else { + const data = await res.json(); + if (data && data.data.gameserver.status === 'stopped') { + client.log(`Restart of Nitrado server ${client.config.Nitrado.ServerID} has been invoked by the bot, the periodic check showed status of "${data.data.gameserver.status}".`); + // Write optional "restart_message" to set in the Nitrado server logs and send a notice "message" to your server community. + restart_message = 'Server being restarted by periodic bot check.'; + message = 'The server was restarted by periodic bot check!'; + + module.exports.RestartServer(client, restart_message, message); + // } else { + // client.log(`Nitrado server ${client.config.Nitrado.ServerID} is ${data.data.gameserver.status}.`); + } + } + return 0; + } catch (error) { + client.error(`Failed to connect to Nitrado (${client.config.Nitrado.ServerID}): ${error.message}`); + if (retries === maxRetries) throw new Error(`CheckServerStatus: Failed to fetch data after ${maxRetries} retries`); + } + await new Promise(resolve => setTimeout(resolve, retryDelay)); // Delay before retrying + } + }, +}