refactor/project structure and indents for typescript

This commit is contained in:
SowinskiBraeden committed 2025-09-28 11:53:28 -07:00
1 parent df6b9140ec
commit cb84e63a69
105 files changed
+8725 -8721

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
const { EmbedBuilder } = require("discord.js");
const { nearest } = require("../database/destinations");
const { GetWebhook, WebhookSend } = require("./WebhookHandler");
module.exports = {
SendConnectionLogs: async (client, guild, data) => {
if (!client.exists(guild.connectionLogsChannel)) return;
const channel = client.GetChannel(guild.connectionLogsChannel);
if (!channel) return;
let newDt = await client.getDateEST(data.time);
let unixTime = Math.floor(newDt.getTime() / 1000);
let connectionLog = new EmbedBuilder()
.setColor(data.connected ? client.config.Colors.Green : client.config.Colors.Red)
.setDescription(`**${data.connected ? "Connect" : "Disconnect"} Event - <t:${unixTime}>\n${data.player} ${data.connected ? "Connected" : "Disconnected"}**`);
const NAME = "DayZ.R Admin Logs";
const webhook = await GetWebhook(client, NAME, guild.connectionLogsChannel);
if (!data.connected) {
if (data.lastConnectionDate != null) {
let oldUnixTime = Math.floor(data.lastConnectionDate.getTime() / 1000);
let sessionTime = client.secondsToDhms(unixTime - oldUnixTime);
connectionLog.addFields({ name: "**Session Time**", value: `**${sessionTime}**`, inline: false });
} else connectionLog.addFields({ name: "**Session Time**", value: `**Unknown**`, inline: false });
}
// if (client.exists(channel)) await channel.send({ embeds: [connectionLog] });
await WebhookSend(client, webhook, { embeds: [connectionLog] });
},
DetectCombatLog: async (client, guild, data) => {
if (!client.exists(data.lastDamageDate)) return;
if (!client.exists(guild.connectionLogsChannel)) return;
const channel = client.GetChannel(guild.connectionLogsChannel);
if (!channel) return; // Ensure channel exists
const newDt = await client.getDateEST(data.time);
const diffSeconds = Math.round((newDt.getTime() - data.lastDamageDate.getTime()) / 1000);
// If diff is greater than configured time in minutes, not a combat log
// or if death after last combat
if (diffSeconds > (data.combatLogTimer * 60)) return;
if (data.lastDamageDate <= data.lastDeathDate) return;
// If lastHitBy (attacker) died after shooting this player
// then it does not count as combat logging, (the combat ended due to death)
let attacker = await client.dbo.collection("players").findOne({ "gamertag": data.lastHitBy });
if (attacker.lastDeathDate > data.lastDamageDate) return;
let unixTime = Math.floor(newDt.getTime() / 1000);
const destination = nearest(data.pos, guild.Nitrado.Mission);
let combatLog = new EmbedBuilder()
.setColor(client.config.Colors.Red)
.setDescription(`**NOTICE:**\n**${data.player}** has combat logged at <t:${unixTime}> when fighting **${data.lastHitBy}\nLocation [${data.pos[0]}, ${data.pos[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${data.pos[0]};${data.pos[1]})**\n${destination}`);
const NAME = "DayZ.R Admin Logs";
const webhook = await GetWebhook(client, NAME, guild.connectionLogsChannel);
let content = { embeds: [combatLog] };
if (client.exists(guild.adminRole)) content.content = `<@&${guild.adminRole}>`;
WebhookSend(client, webhook, content);
// return channel.send({ embeds: [combatLog] });
}
};
+249
View File
@@ -0,0 +1,249 @@
const { BanPlayer, UnbanPlayer } = require("./NitradoAPI");
const { EmbedBuilder } = require("discord.js");
const { nearest } = require("../database/destinations");
const { GetGuild } = require("../database/guild");
const { GetWebhook, WebhookSend } = require("./WebhookHandler");
// Private functions (only called locally)
const ExpireEvent = async (client, guild, e) => {
let hasMR = (guild.memberRole != "");
const channel = client.GetChannel(e.channel);
if (client.exists(e.channel)) channel.send({ embeds: [new EmbedBuilder().setColor(client.config.Colors.Default).setDescription(`${hasMR ? `<@&${guild.memberRole}>\n` : ""}**The ${e.name} Event has ended!**`)] });
client.dbo.collection("guilds").updateOne({ "server.serverID": guild.serverID }, {
$pull: {
"server.events": e
}
}, (err, res) => {
if (err) return client.sendError(client.GetChannel(guild.adminLogsChannel), err);
});
}
const HandlePlayerTrackEvent = async (client, guild, e) => {
if (!client.exists(e.channel)) return ExpireEvent(client, guild, e); // Expire event since it has invalid channel.
const channel = client.GetChannel(e.channel);
if (!channel) return;
let player = await client.dbo.collection("players").findOne({ "gamertag": e.gamertag });
let newDt = await client.getDateEST(player.time);
let unixTime = Math.floor(newDt.getTime() / 1000);
const destination = nearest(player.pos, guild.Nitrado.Mission);
const trackEvent = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`**${e.name} Event**\n${e.gamertag} was located at **[${player.pos[0]}, ${player.pos[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${player.pos[0]};${player.pos[1]})** at <t:${unixTime}>\n${destination}`);
const NAME = "DayZ.R Player Tracker";
const webhook = await GetWebhook(client, NAME, e.channel);
let content = { embeds: [trackEvent] };
if (client.exists(guild.adminRole)) content.content = `<@&${e.role}>`;
WebhookSend(client, webhook, content);
// if (e.role) channel.send({ content: `<@&${e.role}>`, embeds: [trackEvent] });
// else channel.send({ embeds: [trackEvent] });
let now = new Date();
let diff = ((now - e.creationDate) / 1000) / 60;
let minutesBetweenDates = Math.abs(Math.round(diff));
if (minutesBetweenDates >= e.time) ExpireEvent(client, guild, e);
}
// Public functions (called externally)
module.exports = {
HandleAlarmsAndUAVs: async (client, guild, data) => {
for (let i = 0; i < guild.alarms.length; i++) {
let alarm = guild.alarms[i];
let now = new Date();
if (alarm.uavExpire != null && alarm.uavExpire < now) alarm.disabled = false;
if (alarm.disabled) continue; // ignore if alarm is disabled due to emp
if (alarm.ignoredPlayers.includes(data.playerID)) continue;
let diff = [Math.round(alarm.origin[0] - data.pos[0]), Math.round(alarm.origin[1] - data.pos[1])];
let distance = Math.sqrt(Math.pow(diff[0], 2) + Math.pow(diff[1], 2)).toFixed(2)
if (distance < alarm.radius) {
let newDt = await client.getDateEST(data.time);
let unixTime = Math.floor(newDt.getTime() / 1000);
if (!client.alarmPingQueue.get(guild.serverID).has(alarm.channel)) client.alarmPingQueue.get(guild.serverID).set(alarm.channel, new Map());
let route = alarm.mute ? null : alarm.role;
if (!client.alarmPingQueue.get(guild.serverID).get(alarm.channel).has(route)) client.alarmPingQueue.get(guild.serverID).get(alarm.channel).set(route, []);
if (alarm.rules.includes["ban_on_entry"]) {
client.alarmPingQueue.get(guild.serverID).get(alarm.channel).get(route).push(
new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`**Zone Ping - <t:${unixTime}>**\n**${data.player}** was located within **${distance} meters** of the Zone **${alarm.name}** __and has been banned.__`)
.addFields({ name: "**Location**", value: `**[${data.pos[0]}, ${data.pos[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${data.pos[0]};${data.pos[1]})**`, inline: false })
);
BanPlayer(client, data.player);
return;
}
client.alarmPingQueue.get(guild.serverID).get(alarm.channel).get(route).push(
new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`**Zone Ping - <t:${unixTime}>**\n**${data.player}** was located within **${distance} meters** of the Zone **${alarm.name}**`)
.addFields({ name: "**Location**", value: `**[${data.pos[0]}, ${data.pos[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${data.pos[0]};${data.pos[1]})**`, inline: false })
);
return;
}
}
for (let i = 0; i < guild.uavs.length; i++) {
let uav = guild.uavs[i];
let diff = [Math.round(uav.origin[0] - data.pos[0]), Math.round(uav.origin[1] - data.pos[1])];
let distance = Math.sqrt(Math.pow(diff[0], 2) + Math.pow(diff[1], 2)).toFixed(2);
if (distance < uav.radius) {
let newDt = await client.getDateEST(data.time);
let unixTime = Math.floor(newDt.getTime() / 1000);
const destination = nearest(data.pos, guild.Nitrado.Mission);
let uavEmbed = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`**UAV Detection - <t:${unixTime}>**\n**${data.player}** was spotted in the UAV zone at **[${data.pos[0]}, ${data.pos[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${data.pos[0]};${data.pos[1]})\n${destination}**`)
client.users.fetch(uav.owner, false).then((user) => {
user.send({ embeds: [uavEmbed] });
});
}
}
},
HandleExpiredUAVs: async (client, guild) => {
let uavs = guild.uavs;
let update = false;
for (let i = 0; i < uavs.length; i++) {
let uav = uavs[i];
let now = new Date();
let diff = Math.round((now.getTime() - uav.creationDate.getTime()) / 1000 / 60); // diff minutes
if (diff <= 30) continue;
uavs.splice(i, 1);
update = true;
let expired = new EmbedBuilder().setColor(client.config.Colors.Red).setDescription("**Low Battery**\nUAV has run out of battery and is no longer active.");
client.users.fetch(uav.owner, false).then((user) => {
user.send({ embeds: [expired] });
});
}
if (update) {
client.dbo.collection("guilds").updateOne({ "server.serverID": guild.serverID }, { $set: { "server.uavs": uavs } }, (err, res) => {
if (err) return client.sendError(client.GetChannel(guild.adminLogsChannel), err);
});
}
},
KillInAlarm: async (client, guildId, data) => {
let guild = await GetGuild(client, guildId);
for (let i = 0; i < guild.alarms.length; i++) {
let alarm = guild.alarms[i];
if (alarm.disabled || !alarm.rules.includes("ban_on_kill")) continue; // ignore if alarm is disabled or not ban on kill;
if (alarm.ignoredPlayers.includes(data.killerID)) continue;
let diff = [Math.round(alarm.origin[0] - data.killerPOS[0]), Math.round(alarm.origin[1] - data.killerPOS[1])];
let distance = Math.sqrt(Math.pow(diff[0], 2) + Math.pow(diff[1], 2)).toFixed(2)
if (distance < alarm.radius) {
const channel = client.GetChannel(alarm.channel);
if (!channel) continue;
let newDt = await client.getDateEST(data.time);
let unixTime = Math.floor(newDt.getTime() / 1000);
let alarmEmbed = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`**Zone Ping - <t:${unixTime}>**\n**${data.killer}** was located within **${distance} meters** of the Zone **${alarm.name}** __and has been banned for killing **${data.victim}**.__`)
.addFields({ name: "**Location**", value: `**[${data.killerPOS[0]}, ${data.killerPOS[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${data.killerPOS[0]};${data.killerPOS[1]})**`, inline: false })
const NAME = "DayZ.R Zone Alert";
const webhook = await GetWebhook(client, NAME, alarm.channel);
let content = { content: `<@&${alarm.role}>`, embeds: [alarmEmbed] };
WebhookSend(client, webhook, content);
// channel.send({ content: `<@&${alarm.role}>`, embeds: [alarmEmbed] });
BanPlayer(client, data.killer);
break;
}
}
return;
},
PlaceFireplaceInAlarm: async (client, guild, line) => {
let fireplacePlacement = /(.*) \| Player \"(.*)\" \(id=(.*) pos=<(.*)>\) placed Fireplace/g;
let data = [...line.matchAll(fireplacePlacement)][0];
if (!data) return;
let info = {
time: data[1],
player: data[2],
playerID: data[3],
playerPOS: data[4].split(", ").map(v => parseFloat(v)),
};
for (let i = 0; i < guild.alarms.length; i++) {
let alarm = guild.alarms[i];
if (alarm.disabled || !alarm.rules.includes("ban_on_fireplace_placement")) continue;
if (alarm.ignoredPlayers.includes(info.playerID)) continue;
let diff = [Math.round(alarm.origin[0] - info.playerPOS[0]), Math.round(alarm.origin[1] - info.playerPOS[1])];
let distance = Math.sqrt(Math.pow(diff[0], 2) + Math.pow(diff[1], 2)).toFixed(2);
if (distance < alarm.radius) {
const channel = client.GetChannel(alarm.channel);
if (!channel) return;
let newDt = await client.getDateEST(info.time);
let unixTime = Math.floor(newDt.getTime() / 1000);
let alarmEmbed = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`**Zone Ping - <t:${unixTime}>**\n**${info.player}** was located within **${distance} meters** of the Zone **${alarm.name}** __and has been banned for **placing a fireplace**.__`)
.addFields({ name: "**Location**", value: `**[${info.playerPOS[0]}, ${info.playerPOS[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${info.playerPOS[0]};${info.playerPOS[1]})**`, inline: false })
const NAME = "DayZ.R Zone Alert";
const webhook = await GetWebhook(client, NAME, alarm.channel);
let content = { content: `<@&${alarm.role}>`, embeds: [alarmEmbed] };
WebhookSend(client, webhook, content);
// channel.send({ content: `<@&${alarm.role}>`, embeds: [alarmEmbed] });
BanPlayer(client, info.player);
break;
}
}
return;
},
HandleEvents: async (client, guild) => {
for (let i = 0; i < guild.events.length; i++) {
let event = guild.events[i];
if (event.type == "player-track") HandlePlayerTrackEvent(client, guild, event);
}
},
}
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
calculateNewCombatRating: (Ra, Rb, score) => {
const Ea = 1 / (1 + Math.pow(10, ((Rb - Ra) / 400)));
return Math.round(Ra + 32 * (score - Ea));
},
}
+15
View File
@@ -0,0 +1,15 @@
module.exports = {
CommandOptionTypes: {
SubCommand: 1,
SubCommandGroup: 2,
String: 3,
Integer: 4,
Boolean: 5,
User: 6,
Channel: 7,
Role: 8,
Mentionable: 9,
Float: 10, // AKA Number in Discord"s Documentation
Attachment: 11,
}
};
+19
View File
@@ -0,0 +1,19 @@
const crypto = require("crypto");
module.exports = {
encrypt: (data, EncryptionMethod, Key, EncryptionIV) => {
const cipher = crypto.createCipheriv(EncryptionMethod, Key, EncryptionIV)
return Buffer.from(
cipher.update(data, "utf8", "hex") + cipher.final("hex")
).toString("base64") // Encrypts data and converts to hex and base64
},
decrypt: (data, EncryptionMethod, Key, EncryptionIV) => {
const buff = Buffer.from(data, "base64")
const decipher = crypto.createDecipheriv(EncryptionMethod, Key, EncryptionIV)
return (
decipher.update(buff.toString("utf8"), "hex", "utf8") +
decipher.final("utf8")
) // Decrypts data and converts to utf8
}
}
+290
View File
@@ -0,0 +1,290 @@
const { EmbedBuilder } = require("discord.js");
const { createUser, addUser } = require("../database/user");
const { KillInAlarm } = require("./AlarmsHandler");
const { nearest } = require("../database/destinations");
const { getDefaultPlayer, UpdatePlayer } = require("../database/player");
const { calculateNewCombatRating } = require("./CombatRatingHandler");
const { weapons, weaponClassOf } = require("../database/weapons");
const { GetWebhook, WebhookSend } = require("../util/WebhookHandler");
const Templates = {
Killed: 1,
HitBy: 2,
HitByAndDead: 3,
Explosion: 4,
LandMine: 5,
Melee: 6,
Vehicle: 7,
};
const TemplateExpressions = {
1: /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\) killed by Player \"(.*)\" \(id=(.*) pos=<(.*)>\) with (.*) from (.*) meters /g,
2: /(.*) \| Player \"(.*)\" \(id=(.*) pos=<(.*)>\)\[HP\: (.*)\] hit by Player \"(.*)\" \(id=(.*) pos=<(.*)>\) into (.*) for (.*) damage \((.*)\) with (.*) from (.*) meters /g,
3: /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\)\[HP\: (.*)\] hit by Player \"(.*)\" \(id=(.*) pos=<(.*)>\) into (.*) for (.*) damage \((.*)\) with (.*) from (.*) meters /g,
4: /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\) killed by with (.*)/g,
5: /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\) killed by LandMineTrap/g,
6: /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\)\[HP\: (.*)\] hit by Player \"(.*)\" \(id=(.*) pos=<(.*)>\) into (.*) for (.*) damage \((.*)\) with (.*)/g,
7: /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\)\[HP\: (.*)\] hit by (.*) with TransportHit/g,
};
const Vehicles = {
CivilianSedan: "White Olga",
CivilianSedan_Black: "Black Olga",
CivilianSedan_Wine: "Wine Olga",
Hatchback_02: "Red Gunter",
Hatchback_02_Black: "Black Gunter",
Hatchback_02_Blue: "Blue Gunter",
OffroadHatchBack: "Green ADA 4x4",
OffroadHatchBack_Blue: "Blue ADA 4x4",
OffroadHatchBack_White: "White ADA 4x4",
Sedan_02: "Yellow Sarka",
Sedan_02_Grey: "Grey Sarka",
Sedan_02_Red: "Red Sarka",
Truck_01_Covered: "Green V3S Truck",
Truck_01_Covered_Blue: "Blue V3S Truck",
Truck_01_Covered_Orange: "Orange V3S Truck",
Offroad_02: "M1025 Humvee"
};
module.exports = {
// Update last death date for non PVP deaths
UpdateLastDeathDate: async (NitradoServerID, client, line) => {
let killedByZmb = /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\) killed by (.*)/g;
let diedTemplate = /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\) died\. Stats> Water: (.*) Energy: (.*) Bleed sources: (.*)/g;
let data = line.includes(">) died.") ? [...line.matchAll(diedTemplate)][0] : [...line.matchAll(killedByZmb)][0];
if (!data) return;
let info = {
time: data[1],
victim: data[2],
victimID: data[3],
victimPOS: data[4].split(", ").map(v => parseFloat(v)),
};
const newDt = await client.getDateEST(info.time);
let victimStat = await client.dbo.collection("players").findOne({ "playerID": info.playerID });
if (!client.exists(victimStat)) victimStat = getDefaultPlayer(info.player, info.playerID, NitradoServerID);
victimStat.lastDeathDate = newDt;
await UpdatePlayer(client, victimStat);
return
},
HandleKillfeed: async (NitradoServerID, client, guild, line) => {
const NAME = "DayZ.R Killfeed";
const channel = client.GetChannel(guild.killfeedChannel);
const killedBy = line.includes("hit by Player") && line.includes("(DEAD)") && line.includes("meters") ? Templates.HitByAndDead :
line.includes("hit by Player") && !line.includes("meters") ? Templates.Melee : // Missing meters indicates it was a melee attack.
line.includes("hit by Player") ? Templates.HitBy :
line.includes("killed by Player") ? Templates.Killed :
line.includes("TransportHit") ? Templates.Vehicle :
line.includes("killed by LandMineTrap") ? Templates.LandMine : Templates.Explosion;
let data = [...line.matchAll(TemplateExpressions[killedBy])][0];
if (!data) return;
// Create base data
let info = {
time: data[1],
victim: data[2],
victimID: data[3],
victimPOS: data[4].split(", ").map(v => parseFloat(v)),
};
// Add additional data
if ([Templates.HitBy, Templates.HitByAndDead, Templates.Melee].includes(killedBy)) {
info.killer = data[6];
info.killerID = data[7];
info.killerPOS = data[8].split(", ").map(v => parseFloat(v));
info.bodyPart = data[9];
info.damage = data[10];
info.weapon = data[12];
info.distance = killedBy == Templates.Melee ? 0 : parseFloat(data[13]).toFixed(2);
} else if (killedBy == Templates.Killed) {
info.killer = data[5];
info.killerID = data[6];
info.killerPOS = data[7].split(", ").map(v => parseFloat(v));
info.weapon = data[8];
info.distance = parseFloat(data[9]).toFixed(2);
}
else if (killedBy == Templates.Vehicle) info.causeOfDeath = data[6];
else if (killedBy == Templates.Explosion) info.causeOfDeath = data[5];
else return; // Unknown template;
const newDt = await client.getDateEST(info.time);
const unixTime = Math.floor(newDt.getTime() / 1000);
const showCoords = client.exists(guild.showKillfeedCoords) ? guild.showKillfeedCoords : false; // default to false if no record of configuration.
const showWeapon = client.exists(guild.showKillfeedWeapon) ? guild.showKillfeedWeapon : false; // default to false if no record of configuration.
const destination = nearest(info.victimPOS, guild.Nitrado.Mission);
if ([Templates.LandMine, Templates.Explosion, Templates.Vehicle].includes(killedBy))
if (killedBy == Templates.LandMine || killedBy == Templates.Explosion || killedBy == Templates.Vehicle) {
let victimStat = await client.dbo.collection("players").findOne({ "playerID": info.victimID });
if (!client.exists(victimStat)) victimStat = getDefaultPlayer(info.victim, info.victimID, NitradoServerID);
victimStat.deaths++;
victimStat.deathStreak++;
victimStat.worstDeathStreak = victimStat.deathStreak > victimStat.worstDeathStreak ? victimStat.deathStreak : victimStat.worstDeathStreak;
victimStat.KDR = victimStat.kills / (victimStat.deaths == 0 ? 1 : victimStat.deaths); // prevent division by 0
victimStat.killStreak = 0;
victimStat.lastDeathDate = newDt;
const cod = killedBy == Templates.LandMine ? `Land Mine Trap` :
killedBy == Templates.Vehicle ? Vehicles[info.causeOfDeath] : info.causeOfDeath;
const coord = showCoords ? `\n***Location [${info.victimPOS[0]}, ${info.victimPOS[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${info.victimPOS[0]};${info.victimPOS[1]})***\n${destination}` : "";
const killMessage = killedBy == Templates.Vehicle ? "run over by" : "blew up from";
const killEvent = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`**Death Event** - <t:${unixTime}>\n**${info.victim}** ${killMessage} a **${cod}.**${coord}`);
await UpdatePlayer(client, victimStat);
if (!channel) return;
const webhook = await GetWebhook(client, NAME, guild.killfeedChannel);
WebhookSend(client, webhook, { embeds: [killEvent] });
// if (client.exists(channel)) await channel.send({ embeds: [killEvent] });
return;
}
KillInAlarm(client, guild.serverID, info); // check if kill happened in a no kill zone
if (!client.exists(info.victim) || !client.exists(info.victimID) || !client.exists(info.killer) || !client.exists(info.killerID)) return;
let victimStat = await client.dbo.collection("players").findOne({ "playerID": info.victimID });
let killerStat = await client.dbo.collection("players").findOne({ "playerID": info.killerID });
if (!client.exists(victimStat)) victimStat = getDefaultPlayer(info.victim, info.victimID, NitradoServerID);
if (!client.exists(killerStat)) killerStat = getDefaultPlayer(info.killer, info.killerID, NitradoServerID);
let weapon = info.weapon.includes("Engraved") ? info.weapon.split("Engraved ")[1] :
info.weapon.includes("Sawed-off") ? info.weapon.split("Sawed-off ")[1] :
info.weapon;
// Update killer stats
killerStat.kills++;
killerStat.killStreak++;
killerStat.bestKillStreak = killerStat.killStreak > killerStat.bestKillStreak ? killerStat.killStreak : killerStat.bestKillStreak;
killerStat.KDR = killerStat.kills / (killerStat.deaths == 0 ? 1 : killerStat.deaths); // prevent division by 0
killerStat.longestKill = info.distance > killerStat.longestKill ? info.distance : killerStat.longestKill;
killerStat.deathStreak = 0;
if (!client.exists(killerStat.weaponStats[weapon].kills)) killerStat.weaponStats[weapon].kills = 0;
killerStat.weaponStats[weapon].kills++;
// Update victim stats
victimStat.deaths++;
victimStat.deathStreak++;
victimStat.worstDeathStreak = victimStat.deathStreak > victimStat.worstDeathStreak ? victimStat.deathStreak : victimStat.worstDeathStreak;
victimStat.KDR = victimStat.kills / (victimStat.deaths == 0 ? 1 : victimStat.deaths); // prevent division by 0
victimStat.killStreak = 0;
victimStat.lastDeathDate = newDt;
if (!client.exists(victimStat.weaponStats[weapon].deaths)) victimStat.weaponStats[weapon].death = 0;
victimStat.weaponStats[weapon].deaths++;
// Create defaults for non-existing ratings
if (!client.exists(killerStat.combatRating)) killerStat.combatRating = 800;
if (!client.exists(victimStat.combatRating)) victimStat.combatRating = 800;
if (!client.exists(killerStat.combatRatingHistory)) killerStat.combatRatingHistory = [800];
if (!client.exists(victimStat.combatRatingHistory)) victimStat.combatRatingHistory = [800];
if (!client.exists(killerStat.highestCombatRating)) killerStat.highestCombatRating = Math.max(...killerStat.combatRatingHistory);
if (!client.exists(victimStat.lowestCombatRating)) victimStat.lowestCombatRating = Math.min(...victimStat.combatRatingHistory);
// Calculate new ratings
let killerOldRating = killerStat.combatRating;
let victimOldRating = victimStat.combatRating;
killerStat.combatRating = calculateNewCombatRating(killerStat.combatRating, victimStat.combatRating, client.exists(info.bodyPart) && info.bodyPart.includes("Head") ? 1.25 : 1);
victimStat.combatRating = calculateNewCombatRating(victimStat.combatRating, killerStat.combatRating, 0);
// Update combat rating records
if (killerStat.combatRating > killerStat.highestCombatRating) killerStat.highestCombatRating = killerStat.combatRating;
if (victimStat.combatRating < victimStat.lowestCombatRating) victimStat.lowestCombatRating = victimStat.combatRating;
if (killerStat.combatRatingHistory.length >= 12) killerStat.combatRatingHistory = killerStat.combatRatingHistory.slice(1); // Remove first element (limits history to length 12)
if (victimStat.combatRatingHistory.length >= 12) victimStat.combatRatingHistory = victimStat.combatRatingHistory.slice(1); // Remove first element (limits history to length 12)
killerStat.combatRatingHistory.push(killerStat.combatRating);
victimStat.combatRatingHistory.push(victimStat.combatRating);
let kdiff = killerStat.combatRating - killerOldRating;
let vdiff = victimStat.combatRating - victimOldRating;
let receivedBounty = null;
if (victimStat.bounties.length > 0 && killerStat.discordID != "") {
let totalBounty = 0;
for (let i = 0; i < victimStat.bounties.length; i++) {
totalBounty += victimStat.bounties[i].value;
}
let banking = await client.dbo.collection("users").findOne({ "user.userID": killerStat.discordID }).then(banking => banking);
if (!banking) {
banking = await createUser(interaction.member.user.id, guild.serverID, guild.startingBalance, client)
if (!client.exists(banking)) return client.sendInternalError(interaction, err);
}
banking = banking.user;
if (!client.exists(banking.guilds[guild.serverID])) {
const success = addUser(banking.guilds, guild.serverID, interaction.member.user.id, client, guild.startingBalance);
if (!success) return client.sendInternalError(interaction, "Failed to add bank");
}
const newBalance = banking.guilds[guild.serverID].balance + totalBounty;
await client.dbo.collection("users").updateOne({ "user.userID": killerStat.discordID }, {
$set: {
[`user.guilds.${guild.serverID}.balance`]: newBalance,
}
}, (err, res) => {
if (err) return client.sendError(client.GetChannel(guild.killfeedChannel), `Killfeed Error: Updating killer bank balance\n${err}`);
});
receivedBounty = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`<@${killerStat.discordID}> received **$${totalBounty.toFixed(2).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}** in bounty rewards.`);
victimStat.bounties = []; // clear bounties after claimed
victimStat.bountiesLength = 0;
}
await UpdatePlayer(client, victimStat);
await UpdatePlayer(client, killerStat);
const header = `**Kill Event** - <t:${unixTime}>\n**${info.killer}** killed **${info.victim}**`;
const killData = `\n> **__Kill Data__**\n> Weapon: \` ${info.weapon} \`\n> Distance: \` ${info.distance}m \`\n> Body Part: \` ${info.bodyPart != undefined ? info.bodyPart.split("(")[0] : "N/A"} \`\n> Damage: \` ${info.damage != undefined ? info.damage : "N/A"} \``;
const killerStatsView = `\n**Killer Rating** (${kdiff >= 0 ? "+" : ""}${kdiff}) ${killerStat.combatRating}\n${killerStat.KDR.toFixed(2)} K/D - ${killerStat.kills} Kill${(killerStat.kills == 0 || killerStat.kills > 1) ? "s" : ""} - Killstreak: ${killerStat.killStreak}`;
const victimStatsView = `\n**Victim Rating** (${vdiff >= 0 ? "+" : ""}${vdiff}) ${victimStat.combatRating}\n${victimStat.KDR.toFixed(2)} K/D - ${victimStat.deaths} Death${victimStat.deaths == 0 || victimStat.deaths > 1 ? "s" : ""} - Deathstreak: ${victimStat.deathStreak}`;
const coord = showCoords ? `\n***Location [${info.victimPOS[0]}, ${info.victimPOS[1]}](https://www.izurvive.com/chernarusplussatmap/#location=${info.victimPOS[0]};${info.victimPOS[1]})***\n${destination}` : "";
let killEvent = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setDescription(`${header}${killData}${killerStatsView}${victimStatsView}${coord}`);
if (showWeapon) {
let weaponClass = weaponClassOf(weapon);
killEvent.setThumbnail(weapons[weaponClass][weapon])
}
if (!channel) return;
const webhook = await GetWebhook(client, NAME, guild.killfeedChannel);
WebhookSend(client, webhook, { embeds: [killEvent] });
if (client.exists(receivedBounty) && client.exists(channel)) WebhookSend(client, webhook, { content: `<@${killerStat.discordID}>`, embeds: [receivedBounty] });
// if (client.exists(channel)) await channel.send({ embeds: [killEvent] });
// if (client.exists(receivedBounty) && client.exists(channel)) await channel.send({ content: `<@${killerStat.discordID}>`, embeds: [receivedBounty] });
return;
}
}
+40
View File
@@ -0,0 +1,40 @@
const winston = require("winston");
const colors = require("colors");
class Logger {
constructor(LoggingFile) {
this.logger = winston.createLogger({
transports: [new winston.transports.File({ filename: LoggingFile })],
});
}
log(Text) {
let d = new Date();
this.logger.log({
level: "info",
message:
`${d.getHours()}:${d.getMinutes()} - ${d.getMonth() + 1}:${d.getDate()}:${d.getFullYear()} | Info: ` + Text
});
console.log(
colors.green(
`${d.getMonth() + 1}:${d.getDate()}:${d.getFullYear()} - ${d.getHours()}:${d.getMinutes()}`
) + colors.yellow(" | Info: " + Text)
);
}
error(Text) {
let d = new Date();
this.logger.log({
level: "error",
message:
`${d.getHours()}:${d.getMinutes()} - ${d.getMonth() + 1}:${d.getDate()}:${d.getFullYear()} | Error: ` + Text
});
console.log(
colors.green(
`${d.getMonth() + 1}:${d.getDate()}:${d.getFullYear()} - ${d.getHours()}:${d.getMinutes()}`
) + colors.yellow(" | Error: ") + colors.red(Text)
);
}
}
module.exports = Logger;
+265
View File
@@ -0,0 +1,265 @@
const { EmbedBuilder } = require("discord.js");
const { HandleAlarmsAndUAVs } = require("./AlarmsHandler");
const { SendConnectionLogs, DetectCombatLog } = require("./AdminLogsHandler");
const { getDefaultPlayer } = require("../database/player");
const { FetchServerSettings } = require("./NitradoAPI");
const { UpdatePlayer, insertPVPstats, createWeaponStats } = require("../database/player")
const { Missions } = require("../database/destinations");
const { GetWebhook, WebhookSend, WebhookMessageEdit } = require("./WebhookHandler");
module.exports = {
HandlePlayerLogs: async (NitradoServerID, client, GuildDB, line, combatLogTimer = 5) => {
const connectTemplate = /(.*) \| Player \"(.*)\" is connected \(id=(.*)\)/g;
const disconnectTemplate = /(.*) \| Player \"(.*)\"\(id=(.*)\) has been disconnected/g;
const positionTemplate = /(.*) \| Player \"(.*)\" \(id=(.*) pos=<(.*)>\)/g;
const damageTemplate = /(.*) \| Player \"(.*)\" \(id=(.*) pos=<(.*)>\)\[HP\: (.*)\] hit by Player \"(.*)\" \(id=(.*) pos=<(.*)>\) into (.*) for (.*) damage \((.*)\) with (.*) from (.*) meters /g;
const deadTemplate = /(.*) \| Player \"(.*)\" \(DEAD\) \(id=(.*) pos=<(.*)>\)\[HP\: (.*)\] hit by Player \"(.*)\" \(id=(.*) pos=<(.*)>\) into (.*) for (.*) damage \((.*)\) with (.*) from (.*) meters /g;
if (line.includes(" connected")) {
const data = [...line.matchAll(connectTemplate)][0];
if (!data) return;
const info = {
time: data[1],
player: data[2],
playerID: data[3],
};
if (!client.exists(info.player) || !client.exists(info.playerID)) return;
let playerStat = await client.dbo.collection("players").findOne({ "playerID": info.playerID });
if (!client.exists(playerStat)) playerStat = getDefaultPlayer(info.player, info.playerID, NitradoServerID);
const newDt = await client.getDateEST(info.time);
playerStat.lastConnectionDate = newDt;
playerStat.connected = true;
if (!client.exists(playerStat.connections)) playerStat.connections = 0;
playerStat.connections++;
// Track adjusted sessions this instance has handled (e.g. no bot crashes or restarts).
if (client.playerSessions.get(NitradoServerID).has(info.playerID)) {
// Player is already in a session, update the session"s end time.
const session = client.playerSessions.get(NitradoServerID).get(info.playerID);
session.endTime = newDt; // Update end time.
} else {
// Player is not in a session, create a new session.
const newSession = {
startTime: newDt,
endTime: null, // Initialize end time as null.
};
client.playerSessions.get(NitradoServerID).set(info.playerID, newSession);
}
await SendConnectionLogs(client, GuildDB, {
time: info.time,
player: info.player,
connected: true,
lastConnectionDate: null,
});
await UpdatePlayer(client, playerStat);
}
if (line.includes(" disconnected")) {
const data = [...line.matchAll(disconnectTemplate)][0];
if (!data) return;
const info = {
time: data[1],
player: data[2],
playerID: data[3],
};
if (!client.exists(info.player) || !client.exists(info.playerID)) return;
let playerStat = await client.dbo.collection("players").findOne({ "playerID": info.playerID });
if (!client.exists(playerStat)) playerStat = getDefaultPlayer(info.player, info.playerID, NitradoServerID);
let oldUnixTime;
let sessionTimeSeconds;
const newDt = await client.getDateEST(info.time);
const unixTime = Math.round(newDt.getTime() / 1000); // Seconds
if (playerStat.lastConnectionDate != null) {
oldUnixTime = Math.round(playerStat.lastConnectionDate.getTime() / 1000); // Seconds
sessionTimeSeconds = unixTime - oldUnixTime;
} else sessionTimeSeconds = 0;
if (!client.exists(playerStat.longestSessionTime)) playerStat.longestSessionTime = 0;
playerStat.totalSessionTime = playerStat.totalSessionTime + sessionTimeSeconds;
playerStat.lastSessionTime = sessionTimeSeconds;
playerStat.longestSessionTime = sessionTimeSeconds > playerStat.longestSessionTime ? sessionTimeSeconds : playerStat.longestSessionTime;
playerStat.lastDisconnectionDate = newDt;
playerStat.connected = false;
await SendConnectionLogs(client, GuildDB, {
time: info.time,
player: info.player,
connected: false,
lastConnectionDate: playerStat.lastConnectionDate,
});
if (combatLogTimer != 0) {
await DetectCombatLog(client, GuildDB, {
time: info.time,
player: info.player,
pos: playerStat.pos,
lastDamageDate: playerStat.lastDamageDate,
lastHitBy: playerStat.lastHitBy,
lastDeathDate: playerStat.lastDeathDate,
combatLogTimer: combatLogTimer,
});
}
await UpdatePlayer(client, playerStat);
}
if (line.includes("pos=<") && !line.includes("hit by")) {
const data = [...line.matchAll(positionTemplate)][0];
if (!data) return;
const info = {
time: data[1],
player: data[2],
playerID: data[3],
pos: data[4].split(", ").map(v => parseFloat(v))
};
if (!client.exists(info.player) || !client.exists(info.playerID)) return;
let playerStat = await client.dbo.collection("players").findOne({ "playerID": info.playerID });
if (!client.exists(playerStat)) playerStat = getDefaultPlayer(info.player, info.playerID, NitradoServerID);
if (!client.exists(playerStat.lastConnectionDate)) playerStat.lastConnectionDate = await client.getDateEST(info.time);
playerStat.lastPos = playerStat.pos;
playerStat.pos = info.pos;
playerStat.lastTime = playerStat.time;
playerStat.lastDate = playerStat.date;
playerStat.time = `${info.time} EST`;
playerStat.date = await client.getDateEST(info.time);
if (line.includes("hit by") || line.includes("killed by")) return; // prevent additional information from being fed to Alarms & UAVs
await HandleAlarmsAndUAVs(client, GuildDB, {
time: info.time,
player: info.player,
playerID: info.playerID,
pos: info.pos,
});
await UpdatePlayer(client, playerStat)
}
if (line.includes("hit by Player")) {
const data = line.includes("(DEAD)") ? [...line.matchAll(deadTemplate)][0] : [...line.matchAll(damageTemplate)][0];
if (!data) return;
const info = {
time: data[1],
player: data[2],
playerID: data[3],
attacker: data[6],
attackerID: data[7],
bodyPart: data[9].split("(")[0],
weapon: data[12],
};
if (!client.exists(info.player) || !client.exists(info.playerID) || !client.exists(info.attacker) || !client.exists(info.attackerID)) return;
let playerStat = await client.dbo.collection("players").findOne({ "playerID": info.playerID });
let attackerStat = await client.dbo.collection("players").findOne({ "playerID": info.attackerID });
if (!client.exists(playerStat)) playerStat = getDefaultPlayer(info.player, info.playerID, NitradoServerID);
if (!client.exists(attackerStat)) attackerStat = getDefaultPlayer(info.attacker, info.attackerID, NitradoServerID);
playerStat.lastDamageDate = await client.getDateEST(info.time);
playerStat.lastHitBy = info.attacker;
if (!client.exists(playerStat.shotsLanded)) playerStat = insertPVPstats(playerStat);
if (!client.exists(attackerStat.shotsLanded)) attackerStat = insertPVPstats(attackerStat);
// Update in depth PVP stats if non Melee weapon
if (info.weapon.includes("Engraved")) info.weapon = info.weapon.split("Engraved ")[1];
if (info.weapon.includes("Sawed-off")) info.weapon = info.weapon.split("Sawed-off ")[1];
if (info.weapon in playerStat.weaponStats) {
playerStat.timesShot++;
playerStat.timesShotPerBodyPart[info.bodyPart]++;
if (!client.exists(playerStat.weaponStats[info.weapon])) playerStat = createWeaponStats(playerStat, info.weapon);
playerStat.weaponStats[info.weapon].timesShot++;
playerStat.weaponStats[info.weapon].timesShotPerBodyPart[info.bodyPart]++;
attackerStat.shotsLanded++;
attackerStat.shotsLandedPerBodyPart[info.bodyPart]++;
if (!client.exists(attackerStat.weaponStats[info.weapon])) attackerStat = createWeaponStats(attackerStat, info.weapon);
attackerStat.weaponStats[info.weapon].shotsLanded++;
attackerStat.weaponStats[info.weapon].shotsLandedPerBodyPart[info.bodyPart]++;
}
await UpdatePlayer(client, playerStat);
await UpdatePlayer(client, attackerStat);
}
return;
},
HandleActivePlayersList: async (nitrado_cred, client, guild) => {
client.activePlayersTick = 0; // reset hour tick
if (!client.exists(guild.activePlayersChannel)) return;
const channel = client.GetChannel(guild.activePlayersChannel);
if (!channel) return;
const data = await FetchServerSettings(nitrado_cred, client, "HandleActivePlayersList"); // Fetch server status
const e = data && data !== 1; // Check if data exists
const hostname = e ? data.data.gameserver.settings.config.hostname : "N/A";
const map = Missions[data.data.gameserver.settings.config.mission];
const status = e ? data.data.gameserver.status : "N/A";
const slots = e ? data.data.gameserver.slots : "N/A";
const playersOnline = e ? data.data.gameserver.query.player_current : undefined;
const Statuses = {
"started": { emoji: "🟢", text: "Active" },
"stopped": { emoji: "🔴", text: "Stopped" },
"restarting": { emoji: "↻", text: "Restarting" },
};
const emojiStatus = Statuses[status].emoji || "❓";
const textStatus = Statuses[status].text || "Unknown Status";
let activePlayers = await client.dbo.collection("players").find({ "nitradoServerID": nitrado_cred.ServerID }).toArray().filter(player => player.connected);
let des = activePlayers.length > 0 ? `` : `**No Players Online**`;
for (let i = 0; i < activePlayers.length; i++) {
des += `**- ${activePlayers[i].gamertag}**\n`;
}
const nodes = activePlayers.length === 0;
const serverEmbed = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setTitle(`Online List - \` ${playersOnline === undefined ? activePlayers.length : playersOnline} \` Player${playersOnline !== 1 ? "s" : ""} Online`)
.addFields(
{ name: "Server:", value: `\` ${hostname} \``, inline: false },
{ name: "Map:", value: `\` ${map} \``, inline: true },
{ name: "Status:", value: `\` ${emojiStatus} ${textStatus} \``, inline: true },
{ name: "Slots:", value: `\` ${slots} \``, inline: true }
);
const activePlayersEmbed = new EmbedBuilder()
.setColor(client.config.Colors.Default)
.setTimestamp()
.setTitle(`Players Online:`)
.setDescription(des || (nodes ? "No Players Online :(" : ""));
const NAME = "DayZ.R Admin Logs";
const webhook = await GetWebhook(client, NAME, guild.connectionLogsChannel);
let id = client.playerListMsgIds.get(guild.serverID);
if (id == "") {
id = await WebhookSend(client, webhook, { embeds: [serverEmbed, activePlayersEmbed] }).id;
client.playerListMsgIds.set(guild.serverID, id);
} else {
WebhookMessageEdit(client, webhook, id, { embeds: [serverEmbed, activePlayersEmbed] });
}
}
};
+311
View File
@@ -0,0 +1,311 @@
const { finished } = require("stream/promises");
const concat = require("concat-stream");
const { Readable } = require("stream");
const FormData = require("form-data");
const fs = require("fs");
const maxRetries = 5;
const retryDelay = 5000; // 5 seconds
// Private functions (only called locally)
const UploadNitradoFile = async (nitrado_cred, client, remoteDir, remoteFilename, localFileDir) => {
for (let retries = 0; retries <= maxRetries; retries++) {
try {
const res = await fetch(`https://api.nitrado.net/services/${nitrado_cred.ServerID}/gameservers/file_server/upload?` + new URLSearchParams({
path: remoteDir,
file: remoteFilename
}), {
method: "POST",
headers: {
"Authorization": nitrado_cred.Auth
},
}).then(response => response.json());
let contents = fs.readFileSync(localFileDir, "utf8");
const uploadRes = await fetch(res.data.token.url, {
method: "POST",
headers: {
"Content-Type": "application/binary",
token: res.data.token.token
},
body: contents,
})
if (!uploadRes.ok) {
client.error(`Failed to upload file to Nitrado (${nitrado_cred.ServerID}): status: ${uploadRes.status}, message: ${res.statusText}: UploadNitradoFile`);
if (retries === 2) return 1; // Return error status on the second failed status code.
} else {
return uploadRes;
}
} catch (error) {
client.error(`UploadNitradoFile: Error connecting to server (${nitrado_cred.ServerID}): ${error.message}`);
if (retries === maxRetries) {
client.error(`UploadNitradoFile: Error connecting to server (${nitrado_cred.ServerID}) after ${maxRetries} retries`);
return 1;
}
}
await new Promise(resolve => setTimeout(resolve, retryDelay)); // Delay before retrying
}
}
const HandlePlayerBan = async (nitrado_cred, client, gamertag, ban) => {
const data = await module.exports.FetchServerSettings(nitrado_cred, client, "HandlePlayerBan"); // Fetch server status
if (data && data != 1) {
let bans = data.data.gameserver.settings.general.bans;
if (ban) bans += `\r\n${gamertag}`;
else if (!ban) bans = bans.replace(gamertag, "");
else client.error("Incorrect Ban Option: HandlePlayerBan");
let category = "general";
let key = "bans";
return await module.exports.PostServerSettings(nitrado_cred, client, category, key, bans); // returns 1 (failed) or 0 (not failed)
}
}
const GetRemoteDir = async (nitrado_cred, client, dir = "") => {
const dirParam = client.exists(dir) ? `?dir=${dir}` : "";
for (let retries = 0; retries <= maxRetries; retries++) {
try {
const res = await fetch(`https://api.nitrado.net/services/${nitrado_cred.ServerID}/gameservers/file_server/list${dirParam}`, {
headers: {
"Authorization": nitrado_cred.Auth
}
}).then(response =>
response.json().then(data => data)
).then(res => res);
if (res.status === "error") return 1;
return res.data.entries;
} catch (error) {
client.error(`GetRemoteDir: Error connecting to server (${nitrado_cred.ServerID}): ${error}`);
if (retries == maxRetries) {
client.error(`GetRemoteDir: Error connecting to server (${nitrado_cred.ServerID}) after ${maxRetries} retries`);
return 1;
}
}
await new Promise(resolve => setTimeout(resolve, retryDelay)); // Delay before retrying
}
}
// Public functions (called externally)
module.exports = {
DownloadNitradoFile: async (nitrado_cred, client, filename, outputDir) => {
for (let retries = 0; retries <= maxRetries; retries++) {
try {
const res = await fetch(`https://api.nitrado.net/services/${nitrado_cred.ServerID}/gameservers/file_server/download?file=${filename}`, {
headers: {
"Authorization": nitrado_cred.Auth
}
}).then(response =>
response.json().then(data => data)
).then(res => res);
const stream = fs.createWriteStream(outputDir);
if (!res.data || !res.data.token) {
client.error(`Error downloading File "${filename}": message: ${res.message}: DownloadNitradoFile`);
return 1;
}
const { body } = await fetch(res.data.token.url);
await finished(Readable.fromWeb(body).pipe(stream));
return 0;
} catch (error) {
client.error(`DownloadNitradoFile: Error connecting to server (${nitrado_cred.ServerID}): ${error.message}`);
if (retries === maxRetries) {
client.error(`DownloadNitradoFile: Error connecting to server (${nitrado_cred.ServerID}) after ${maxRetries} retries`);
return 1;
}
}
await new Promise(resolve => setTimeout(resolve, retryDelay)); // Delay before retrying
}
},
/*
Export explicit function names; i.e BanPlayer() & UnbanPlayer()
that call to the private parent function HandlePlayerBan()
rather than write two whole different functions for each.
*/
BanPlayer: async (nitrado_cred, client, gamertag) => await HandlePlayerBan(nitrado_cred, client, gamertag, true),
UnbanPlayer: async (nitrado_cred, client, gamertag) => await HandlePlayerBan(nitrado_cred, client, gamertag, false),
RestartServer: async (nitrado_cred, client, restart_message, message) => {
const params = {
restart_message: restart_message,
message: message
};
for (let retries = 0; retries < maxRetries; retries++) {
try {
const res = await fetch(`https://api.nitrado.net/services/${nitrado_cred.ServerID}/gameservers/restart`, {
method: "POST",
headers: {
"Authorization": nitrado_cred.Auth,
},
body: JSON.stringify(params)
});
if (!res.ok) {
client.error(`Failed to restart Nitrado server (${nitrado_cred.ServerID}): status: ${res.status}, message: ${res.statusText}: RestartServer`);
return 1; // Return error status on failed status code.
} else {
return 0;
}
} catch (error) {
client.error(`RestartServer: Error connecting to server (${nitrado_cred.ServerID}): ${error.message}`);
if (retries === maxRetries) {
client.error(`RestartServer: Error connecting to server (${nitrado_cred.ServerID}) after ${maxRetries} retries`);
return 1;
}
}
await new Promise(resolve => setTimeout(resolve, retryDelay)); // Delay before retrying
}
},
FetchServerSettings: async (nitrado_cred, client, fetcher) => {
for (let retries = 0; retries <= maxRetries; retries++) {
try {
// get current status
const res = await fetch(`https://api.nitrado.net/services/${nitrado_cred.ServerID}/gameservers`, {
headers: {
"Authorization": nitrado_cred.Auth
}
});
if (!res.ok) {
client.error(`Failed to get Nitrado server stats (${nitrado_cred.ServerID}): status: ${res.status}, message: ${res.statusText}: ${fetcher} via FetchServerSettings`);
if (res.status == 401) return 1; // return immediately if unauthorized
if (retries === 2) return 1; // Return error status on the second failed status code.
} else {
const data = await res.json();
return data;
}
} catch (error) {
client.error(`${fetcher} via FetchServerSettings: Error connecting to server (${nitrado_cred.ServerID}): ${error.message}`);
if (retries === maxRetries) {
client.error(`${fetcher} via FetchServerSettings: Error connecting to server (${nitrado_cred.ServerID}) after ${maxRetries} retries`);
return 1;
}
}
await new Promise(resolve => setTimeout(resolve, retryDelay)); // Delay before retrying
}
},
PostServerSettings: async (nitrado_cred, client, category, key, value) => {
for (let retries = 0; retries <= maxRetries; retries++) {
try {
const formData = new FormData();
formData.append("category", category);
formData.append("key", key);
formData.append("value", value);
formData.pipe(concat(data => {
async function postData() {
const res = await fetch(`https://api.nitrado.net/services/${nitrado_cred.ServerID}/gameservers/settings`, {
method: "POST",
credentials: "include",
headers: {
...formData.getHeaders(),
"Authorization": nitrado_cred.Auth
},
body: data,
});
if (!res.ok) {
client.error(`Failed to get post Nitrado server settings (${nitrado_cred.ServerID}): status: ${res.status}, message: ${res.statusText}: PostServerSettings`);
if (retries === 2) return 1; // Return error status on the second failed status code.
} else {
const data = await res.json();
return data;
}
}
postData();
}));
return 0;
} catch (error) {
client.error(`PostServerSettings: Error connecting to server (${nitrado_cred.ServerID}): ${error.message}`);
if (retries === maxRetries) {
client.error(`PostServerSettings: Error connecting to server (${nitrado_cred.ServerID}) after ${maxRetries} retries`);
return 1;
}
}
await new Promise(resolve => setTimeout(resolve, retryDelay)); // Delay before retrying
}
},
CheckServerStatus: async (nitrado_cred, client) => {
const data = await module.exports.FetchServerSettings(nitrado_cred, client, "CheckServerStatus"); // Fetch server status
if (data && data != 1) {
if (data && data.data.gameserver.status === "stopped") {
client.log(`Restart of Nitrado server ${nitrado_cred.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(nitrado_cred, client, restart_message, message);
}
}
},
DisableBaseDamage: async (nitrado_cred, client, preference) => {
const pref = preference ? "1" : "0";
const posted = await module.exports.PostServerSettings(nitrado_cred, client, "config", "disableBaseDamage", pref);
if (posted == 1) return 1;
const remoteDirs = await GetRemoteDir(nitrado_cred, client);
if (remoteDirs == 1) return 1;
const basePath = remoteDirs.filter(dir => dir.type == "dir")[0].path
const remoteDirsFromBase = await GetRemoteDir(nitrado_cred, client, basePath);
if (remoteDirsFromBase == 1) return 1;
const missionPath = remoteDirsFromBase[0].path;
const cfggameplayPath = `${missionPath}/cfggameplay.json`;
const jsonDir = `./logs/cfggameplay.json`;
await module.exports.DownloadNitradoFile(nitrado_cred, client, cfggameplayPath, jsonDir);
let gameplay = JSON.parse(fs.readFileSync(jsonDir));
gameplay.GeneralData.disableBaseDamage = preference;
// write JSON to file
fs.writeFileSync(jsonDir, JSON.stringify(gameplay, null, 2));
const uploaded = await UploadNitradoFile(nitrado_cred, client, missionPath, "cfggameplay.json", jsonDir);
if (uploaded == 1) return 1;
return 0;
},
DisableContainerDamage: async (nitrado_cred, client, preference) => {
const pref = preference ? "1" : "0";
const posted = await module.exports.PostServerSettings(nitrado_cred, client, "config", "disableContainerDamage", pref);
if (posted == 1) return 1;
const remoteDirs = await GetRemoteDir(nitrado_cred, client);
if (remoteDirs == 1) return 1;
const basePath = remoteDirs.filter(dir => dir.type == "dir")[0].path
const remoteDirsFromBase = await GetRemoteDir(nitrado_cred, client, basePath);
if (remoteDirsFromBase == 1) return 1;
const missionPath = remoteDirsFromBase[0].path;
const cfggameplayPath = `${missionPath}/cfggameplay.json`;
const jsonDir = `./logs/cfggameplay.json`;
await module.exports.DownloadNitradoFile(nitrado_cred, client, cfggameplayPath, jsonDir);
let gameplay = JSON.parse(fs.readFileSync(jsonDir));
gameplay.GeneralData.disableContainerDamage = preference;
// write JSON to file
fs.writeFileSync(jsonDir, JSON.stringify(gameplay, null, 2));
const uploaded = await UploadNitradoFile(nitrado_cred, client, missionPath, "cfggameplay.json", jsonDir);
if (uploaded == 1) return 1;
return 0;
},
NitradoCredentialStatus: {
FAILED: "FAILED",
OK: "OK",
},
}
+68
View File
@@ -0,0 +1,68 @@
const fs = require("fs");
const path = require("path");
const { Routes } = require("discord.js");
const { REST } = require("@discordjs/rest");
/**
* Register slash commands for a guild
* @param {require("../structures/DayzRBot")} client
*/
module.exports = {
// Register guild commands
RegisterGuildCommands: async (client, guild) => {
const commands = [];
const commandFiles = fs.readdirSync(path.join(__dirname, "..", "commands")).filter(file => file.endsWith(".js"));
// Place your client and guild ids here
const clientId = client.application.id;
const guildId = guild;
for (const file of commandFiles) {
const command = require(`../commands/${file}`);
if (!command.global) commands.push(command); // don"t include global commands
}
const rest = new REST({ version: "10" }).setToken(client.config.Token);
try {
client.log(`[${guildId}] Started refreshing guild (/) commands.`);
await rest.put(
Routes.applicationGuildCommands(clientId, guildId),
{ body: commands },
);
client.log(`[${guildId}] Successfully reloaded guild (/) commands.`);
} catch (error) {
client.error(error);
}
},
// Register global commands
RegisterGlobalCommands: async (client) => {
const commands = [];
const commandFiles = fs.readdirSync(path.join(__dirname, "..", "commands")).filter(file => file.endsWith(".js"));
const clientId = client.application.id;
for (const file of commandFiles) {
const command = require(`../commands/${file}`);
if (command.global) commands.push(command);
}
const rest = new REST({ version: "10" }).setToken(client.config.Token);
try {
client.log("[global] Started refreshing global (/) commands.");
await rest.put(
Routes.applicationCommands(clientId),
{ body: commands },
);
client.log("[global] Successfully reloaded global (/) commands.");
} catch (error) {
client.error(error);
}
}
};
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
calculateVector: (pos1, pos2) => {
let delta = [Math.round(pos2[0] - pos1[0]), Math.round(pos2[1] - pos1[1])];
let distance = parseFloat(Math.sqrt(Math.pow(delta[0], 2) + Math.pow(delta[1], 2)).toFixed(0));
let thetat = Math.round(Math.atan2(delta[0], delta[1]) / Math.PI * 180);
let theta = (thetat < 0) ? (360 + thetat) : thetat;
let compass = ["S", "SW", "W", "NW", "N", "NE", "E", "SE", "S"];
let dir = compass[Math.round(theta / 45)];
return { distance, theta, dir }
}
}
+56
View File
@@ -0,0 +1,56 @@
const { makeURLSearchParams } = require("@discordjs/rest");
const { REST } = require("@discordjs/rest");
const { Routes } = require("discord.js");
const createWebhook = async (client, channel_id, name, avatar) => {
const rest = new REST({ version: "10" }).setToken(client.config.Token);
return await rest.post(Routes.channelWebhooks(channel_id), {
body: {
name: name,
avatar: avatar
}
});
};
module.exports = {
GetWebhook: async (client, webhookName, channel_id) => {
// Get all webhooks from configured channel
const rest = new REST({ version: "10" }).setToken(client.config.Token);
const webhooks = await rest.get(Routes.channelWebhooks(channel_id));
let webhook = null;
if (webhooks.length == 0) {
// If no webhook exists, create new webhook with given name for this channel
webhook = createWebhook(client, channel_id, webhookName, client.config.AvatarData);
} else {
// Check existing webhooks for one with given name
let exists = false;
for (let i = 0; i < webhooks.length; i++) {
if (webhooks[i].name == webhookName) {
webhook = webhooks[i];
exists = true;
break;
}
}
if (!exists) webhook = createWebhook(client, channel_id, webhookName, client.config.AvatarData);
}
return webhook;
},
WebhookSend: async (client, webhook, content) => {
const rest = new REST({ version: "10" }).setToken(client.config.Token);
return await rest.post(Routes.webhook(webhook.id, webhook.token), {
body: content,
query: makeURLSearchParams({ wait: true })
});
},
WebhookMessageEdit: async (client, webhook, message_id, content) => {
const rest = new REST({ version: "10" }).setToken(client.config.Token);
return rest.patch(Routes.webhookMessage(webhook.id, webhook.token, message_id), {
body: content
});
}
}