convert entry files to ts but there are lots of errors
This commit is contained in:
17 files changed
+1554
-982
No files matched your search
Generated
+44
-6
@@ -18,6 +18,7 @@
|
||||
"dotenv": "^16.0.3",
|
||||
"form-data": "^4.0.0",
|
||||
"mongodb": "^4.12.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -3352,6 +3353,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -3511,7 +3524,6 @@
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -4047,7 +4059,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -4284,6 +4295,20 @@
|
||||
"strip-json-comments": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
||||
"integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json5": "^2.2.2",
|
||||
"minimist": "^1.2.6",
|
||||
"strip-bom": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig/node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
@@ -6977,6 +7002,11 @@
|
||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||
},
|
||||
"keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -7096,8 +7126,7 @@
|
||||
"minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
@@ -7426,8 +7455,7 @@
|
||||
"strip-bom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
@@ -7578,6 +7606,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tsconfig-paths": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
||||
"integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
|
||||
"requires": {
|
||||
"json5": "^2.2.2",
|
||||
"minimist": "^1.2.6",
|
||||
"strip-bom": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
|
||||
|
||||
-559
@@ -1,559 +0,0 @@
|
||||
const { RegisterGlobalCommands, RegisterGuildCommands } = require("./util/RegisterSlashCommands");
|
||||
const { Collection, Client, EmbedBuilder, Routes, InteractionResponseType, InteractionType, GatewayDispatchEvents } = require("discord.js");
|
||||
const MongoClient = require("mongodb").MongoClient;
|
||||
const { REST } = require("@discordjs/rest");
|
||||
const Logger = require("./util/Logger");
|
||||
const crypto = require("crypto");
|
||||
|
||||
// custom util imports
|
||||
const { DownloadNitradoFile, CheckServerStatus, FetchServerSettings, PostServerSettings, NitradoCredentialStatus } = require("./util/NitradoAPI");
|
||||
const { HandlePlayerLogs, HandleActivePlayersList } = require("./util/LogsHandler");
|
||||
const { HandleKillfeed, UpdateLastDeathDate } = require("./util/KillfeedHandler");
|
||||
const { HandleExpiredUAVs, HandleEvents, PlaceFireplaceInAlarm } = require("./util/AlarmsHandler");
|
||||
const { decrypt } = require("./util/Cryptic");
|
||||
const { GetWebhook, WebhookSend } = require("./util/WebhookHandler");
|
||||
|
||||
// Data structures imports
|
||||
const { getDefaultPlayer, UpdatePlayer } = require("./database/player");
|
||||
const { Missions } = require("./database/destinations");
|
||||
const { GetGuild } = require("./database/guild");
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const readline = require("readline");
|
||||
|
||||
const MINUTE = 60000; // 1 minute in milliseconds
|
||||
const AUTO_RESTART_INTERVAL = 600000; // Set auto-restart interval 10 minutes (600,000ms)
|
||||
|
||||
class DayZR extends Client {
|
||||
|
||||
constructor(options, config) {
|
||||
super(options);
|
||||
|
||||
this.config = config;
|
||||
this.commands = new Collection();
|
||||
this.interactionHandlers = new Collection();
|
||||
this.logger = new Logger(path.join(__dirname, "..", "logs", "Logs.log"));
|
||||
this.timer = this.config.Dev == "PROD." ? MINUTE * 5 : MINUTE / 4;
|
||||
|
||||
if (
|
||||
this.config.Token === "" ||
|
||||
this.config.SecretKey === "" ||
|
||||
this.config.SecretIv === ""
|
||||
) {
|
||||
throw new TypeError(
|
||||
"The config.js is not filled out. Please make sure nothing is blank, otherwise the bot will not work properly."
|
||||
);
|
||||
}
|
||||
|
||||
if (!["DEV.", "PROD."].includes(this.config.Dev)) {
|
||||
throw new TypeError(
|
||||
"The Dev version in the config.js does not match the allowed cases of \"DEV.\" or \"PROD.\""
|
||||
);
|
||||
}
|
||||
|
||||
// Generate secret hash with crypto to use for encryption
|
||||
this.key = crypto
|
||||
.createHash("sha512")
|
||||
.update(this.config.SecretKey)
|
||||
.digest("hex")
|
||||
.substring(0, 32);
|
||||
|
||||
this.encryptionIV = crypto
|
||||
.createHash("sha512")
|
||||
.update(this.config.SecretIv)
|
||||
.digest("hex")
|
||||
.substring(0, 16);
|
||||
|
||||
this.db;
|
||||
this.dbo;
|
||||
|
||||
this.databaseConnected = false;
|
||||
this.arInterval = AUTO_RESTART_INTERVAL;
|
||||
this.arIntervalIds = new Map();
|
||||
this.playerSessions = new Map();
|
||||
this.logHistory = new Map();
|
||||
this.alarmPingQueue = new Map();
|
||||
this.playerListMsgIds = new Map();
|
||||
|
||||
this.initialize();
|
||||
this.LoadCommandsAndInteractionHandlers();
|
||||
this.LoadEvents();
|
||||
|
||||
this.Ready = false;
|
||||
this.activePlayersTick = 11;
|
||||
|
||||
this.ws.on(GatewayDispatchEvents.InteractionCreate, async (interaction) => {
|
||||
const start = new Date().getTime();
|
||||
if (interaction.type == InteractionType.ApplicationCommand) {
|
||||
let GuildDB = await GetGuild(this, interaction.guild_id);
|
||||
|
||||
if (this.exists(GuildDB.Nitrado) && this.exists(GuildDB.Nitrado.Auth)) {
|
||||
GuildDB.Nitrado.Auth = decrypt(
|
||||
GuildDB.Nitrado.Auth,
|
||||
this.config.EncryptionMethod,
|
||||
this.key,
|
||||
this.encryptionIV
|
||||
);
|
||||
}
|
||||
|
||||
const command = interaction.data.name.toLowerCase();
|
||||
const args = interaction.data.options;
|
||||
|
||||
// Free unused armbands for related commands
|
||||
if (["armbands", "claim", "factions"].includes(command)) {
|
||||
for (const [factionID, data] of Object.entries(GuildDB.factionArmbands)) {
|
||||
const guild = this.guilds.cache.get(GuildDB.serverID);
|
||||
const role = guild.roles.cache.find(role => role.id == factionID);
|
||||
if (!role) {
|
||||
let query = {
|
||||
$pull: { "server.usedArmbands": data.armband },
|
||||
$unset: { [`server.factionArmbands.${factionID}`]: "" },
|
||||
};
|
||||
this.dbo.collection("guilds").updateOne({ "server.serverID": GuildDB.serverID }, query, (err, res) => {
|
||||
if (err) return this.sendInternalError(interaction, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.log(`Interaction [${interaction.guild_id}] - ${command}`);
|
||||
|
||||
const rest = new REST({ version: "10" }).setToken(this.config.Token);
|
||||
|
||||
// Easy to send response so ;)
|
||||
interaction.guild = await this.guilds.fetch(interaction.guild_id);
|
||||
const handleCallback = async (interactionType, message) => {
|
||||
return await rest.post(Routes.interactionCallback(interaction.id, interaction.token), {
|
||||
body: {
|
||||
type: interactionType,
|
||||
data: message,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Nicely name our custom callback functions and pass correct type because discord is picky with numbers...
|
||||
interaction.send = async (message) => handleCallback(InteractionResponseType.ChannelMessageWithSource, message);
|
||||
interaction.deferReply = async (message) => handleCallback(InteractionResponseType.DeferredChannelMessageWithSource, message);
|
||||
interaction.showModal = async (message) => handleCallback(InteractionResponseType.Modal, message);
|
||||
|
||||
interaction.editReply = async (message) => {
|
||||
return await rest.patch(Routes.webhookMessage(this.application.id, interaction.token), {
|
||||
body: message,
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.databaseConnected) {
|
||||
let dbFailedEmbed = new EmbedBuilder()
|
||||
.setDescription(`**Internal Error:**\nUh Oh D: Its not you, its me.\nThe bot has failed to connect to the database 5 times!\nContact the Developers\nhttps://discord.gg/YCXhvy9uZw`)
|
||||
.setColor(this.config.Colors.Red)
|
||||
|
||||
return interaction.send({ embeds: [dbFailedEmbed] });
|
||||
}
|
||||
|
||||
let cmd = this.commands.get(command);
|
||||
try {
|
||||
cmd.SlashCommand.run(this, interaction, args, { GuildDB }, start); // start is only used in ping / stats command
|
||||
} catch (err) {
|
||||
this.sendInternalError(interaction, err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
log(Text) { this.logger.log(Text); }
|
||||
error(Text) { this.logger.error(Text); }
|
||||
|
||||
async getDateEST(time) {
|
||||
let timeArray = time.split(" ")[0].split(":");
|
||||
let t = new Date(); // Get current date & time (UTC)
|
||||
let f = new Date(t.getTime() - 4 * 3600000); // Convert UTC into EST time to roll back the day as necessary
|
||||
f.setUTCHours(timeArray[0], timeArray[1], timeArray[2]); // Apply the supplied EST time to the converted date (EST is the timezone produced from the Nitrado logs).
|
||||
return new Date(f.getTime() + 4 * 3600000); // Add EST time offset to return timestamp in UTC
|
||||
}
|
||||
|
||||
async readLogs(guild) {
|
||||
const fileStream = fs.createReadStream(`./logs/${guild.Nitrado.ServerID}-logs.ADM`);
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: fileStream,
|
||||
crlfDelay: Infinity
|
||||
});
|
||||
let lines = [];
|
||||
for await (const line of rl) { lines.push(line); }
|
||||
|
||||
let logIndex = lines.indexOf(this.logHistory.get(guild.Nitrado.ServerID));
|
||||
|
||||
if (this.playerSessions.get(guild.Nitrado.ServerID).size === 0) {
|
||||
let players = await this.dbo.collection("players").find({ "nitradoServerID": guild.Nitrado.ServerID }) // Get all players of this server
|
||||
.toArray().then(all => all.filter(p => p.connected).map(p => p.connected = false)); // assume all players who were previously connected are not connected on init only.
|
||||
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
await UpdatePlayer(this, players[i])
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = logIndex + 1; i < lines.length; i++) {
|
||||
// Handle lines to skip
|
||||
if (lines[i].includes("| ####")) continue;
|
||||
if (lines[i].includes("(id=Unknown") || lines[i].includes("Player \"Unknown Entity\"")) continue;
|
||||
if ((i - 1) >= 0 && lines[i] == lines[i - 1]) continue; // continue if this line is a duplicate of the last line
|
||||
|
||||
// Handle general logs
|
||||
if (lines[i].includes("connected") || lines[i].includes("pos=<")) await HandlePlayerLogs(guild.Nitrado.ServerID, this, guild, lines[i], guild.combatLogTimer);
|
||||
if (lines[i].includes("killed by Zmb") || lines[i].includes(">) died.")) await UpdateLastDeathDate(guild.Nitrado.ServerID, this, lines[i]); // Updates users last death date for non PVP deaths.
|
||||
if (lines[i].includes(") placed Fireplace")) await PlaceFireplaceInAlarm(this, guild, lines[i]);
|
||||
|
||||
// Handle killfeed logs
|
||||
if (
|
||||
(lines[i].includes("killed by with") || lines[i].includes("killed by LandMineTrap")) || // Handle explosive deaths
|
||||
(!(i + 1 >= lines.length) && lines[i + 1].includes("killed by") && lines[i].includes("TransportHit")) || // Handle vehicle deaths
|
||||
(!(i + 1 >= lines.length) && lines[i + 1].includes("killed by Player") && lines[i].includes("hit by Player")) || // Handle PVP deaths
|
||||
(lines[i].includes("killed by Player") && !lines[i - 1].includes("hit by Player")) // Handle deaths missing hit by log
|
||||
) await HandleKillfeed(guild.Nitrado.ServerID, this, guild, lines[i]);
|
||||
}
|
||||
|
||||
// Handle alarm pings
|
||||
const maxEmbed = 10;
|
||||
|
||||
this.alarmPingQueue.forEach(queue => {
|
||||
queue.forEach(async (data, channel_id) => {
|
||||
const channel = this.GetChannel(channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
const NAME = "DayZ.R Zone Alert";
|
||||
const webhook = await GetWebhook(this, NAME, channel_id);
|
||||
|
||||
data.forEach(async (embeds, role) => {
|
||||
let embedArrays = [];
|
||||
while (embeds.length > 0)
|
||||
embedArrays.push(embeds.splice(0, maxEmbed));
|
||||
|
||||
for (let i = 0; i < embedArrays.length; i++) {
|
||||
let content = { embeds: embedArrays[i] };
|
||||
if (role != "-no-role-ping-") content.content = `<@&${role}>`;
|
||||
WebhookSend(this, webhook, content);
|
||||
|
||||
// if (role == "-no-role-ping-") channel.send({ embeds: embedArrays[i] });
|
||||
// else channel.send({ content: `<@&${role}>`, embeds: embedArrays[i] });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.alarmPingQueue.set(guild.serverID, new Map()); // Clear alarm queue for this guild
|
||||
|
||||
const playerTemplate = /(.*) \| Player \"(.*)\" \(id=(.*) pos=<(.*)>\)/g;
|
||||
let previouslyConnected = await this.dbo.collection("players").find({ "nitradoServerID": guild.Nitrado.ServerID })
|
||||
.toArray().then(players => players.filter(p => p.connected)); // All players with connection log captured above and no disconnect log
|
||||
let lastDetectedTime;
|
||||
|
||||
for (let i = lines.length - 1; i > 0; i--) {
|
||||
if (lines[i].includes("PlayerList log:")) {
|
||||
for (let j = i + 1; j < lines.length; j++) {
|
||||
let line = lines[j];
|
||||
if (line.includes("| ####")) break;
|
||||
|
||||
let data = [...line.matchAll(playerTemplate)][0];
|
||||
if (!data) continue;
|
||||
|
||||
let info = {
|
||||
time: data[1],
|
||||
player: data[2],
|
||||
playerID: data[3],
|
||||
};
|
||||
|
||||
if (!this.exists(info.player) || !this.exists(info.playerID)) continue; // Skip this player if the player does not exist.
|
||||
|
||||
lastDetectedTime = await this.getDateEST(info.time);
|
||||
|
||||
let playerStat = await this.dbo.collection("players").findOne({ "playerID": info.playerID });
|
||||
if (!this.exists(playerStat)) playerStat = getDefaultPlayer(info.player, info.playerID, guild.Nitrado.ServerID);
|
||||
|
||||
if (!previouslyConnected.includes(playerStat) && this.exists(playerStat.lastDisconnectionDate) && playerStat.lastDisconnectionDate !== null && playerStat.lastDisconnectionDate.getTime() > lastDetectedTime.getTime()) continue; // Skip this player if the lastDisconnectionDate time is later than the player log entry.
|
||||
|
||||
// Track adjusted sessions this instance has handled (e.g. no bot crashes or restarts).
|
||||
if (this.playerSessions.get(guild.Nitrado.ServerID).has(info.playerID)) {
|
||||
// Player is already in a session, update the session"s end time.
|
||||
const session = this.playerSessions.get(guild.Nitrado.ServerID).get(info.playerID);
|
||||
session.endTime = lastDetectedTime; // Update end time.
|
||||
} else {
|
||||
// Player is not in a session, create a new session.
|
||||
const newSession = {
|
||||
startTime: lastDetectedTime,
|
||||
endTime: null, // Initialize end time as null.
|
||||
};
|
||||
this.playerSessions.get(guild.Nitrado.ServerID).set(info.playerID, newSession);
|
||||
|
||||
// Check if the player has been marked as connected before, but only if a session doesn"t exist
|
||||
// in the map, indicating the connection was discovered in the logs during this session.
|
||||
if (!previouslyConnected.includes(playerStat)) {
|
||||
playerStat.connected = true;
|
||||
playerStat.lastConnectionDate = lastDetectedTime; // Update last connection date.
|
||||
}
|
||||
}
|
||||
|
||||
await UpdatePlayer(this, playerStat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const lastLine = lines[lines.length - 1]
|
||||
this.logHistory.set(guild.Nitrado.ServerID, lastLine);
|
||||
this.dbo.collection("guilds").updateOne({ "server.serverID": guild.serverID }, { $set: { "server.lastLog": lastLine } }, (err, res) => {
|
||||
if (err) this.error(`Failed to save last log to guild config [${guild.serverID}] for nitrado server [${guild.Nitrado.ServerID}]`);
|
||||
});
|
||||
}
|
||||
|
||||
async logsUpdateTimer(c) {
|
||||
c.activePlayersTick++;
|
||||
|
||||
c.guilds.cache.forEach(async (guild) => {
|
||||
let GuildDB = await GetGuild(c, guild.id);
|
||||
|
||||
/*
|
||||
Note to self:
|
||||
return statements do not prematurely exit out of a forEach loop like it does in a for loop.
|
||||
*/
|
||||
|
||||
if (!c.exists(GuildDB.Nitrado)) return; // Continue if no nitrado credentials
|
||||
if (GuildDB.Nitrado.Status == NitradoCredentialStatus.FAILED) return; // Continue if these credentials are marked as failed
|
||||
|
||||
const NitradoCred = {
|
||||
ServerID: GuildDB.Nitrado.ServerID,
|
||||
UserID: GuildDB.Nitrado.UserID,
|
||||
Auth: decrypt(
|
||||
GuildDB.Nitrado.Auth,
|
||||
c.config.EncryptionMethod,
|
||||
c.key,
|
||||
c.encryptionIV
|
||||
),
|
||||
};
|
||||
|
||||
const response = await FetchServerSettings(NitradoCred, c, "logsUpdateTimer").then(res => res);
|
||||
if (response == 1) {
|
||||
c.dbo.collection("guilds").updateOne({ "server.serverID": GuildDB.serverID }, { $set: { "Nitrado.Status": NitradoCredentialStatus.FAILED } }, (err, _) => {
|
||||
if (err) this.error(`Failed to update Nitrado status to failed. [${GuildDB.serverID}]`);
|
||||
});
|
||||
return;
|
||||
};
|
||||
const settings = response.data.gameserver;
|
||||
|
||||
// Update Nitrado DayZ Mission if change is detected
|
||||
if (GuildDB.Nitrado.Mission !== Missions[settings.settings.config.mission]) {
|
||||
c.dbo.collection("guilds").updateOne({ "server.serverID": GuildDB.serverID }, { $set: { "Nitrado.Mission": Missions[settings.settings.config.mission] } }, (err, res) => {
|
||||
if (err) this.error(`Failed to save mission to guild config [${GuildDB.serverID}] for nitrado server [${GuildDB.Nitrado.ServerID}]`);
|
||||
});
|
||||
}
|
||||
|
||||
GuildDB.Nitrado.Mission = Missions[settings.settings.config.mission];
|
||||
|
||||
if (settings.game_specific.log_files.length == 0) return; // Ignore if no log files on Nitrado server
|
||||
const filename = settings.game_specific.log_files.sort((a, b) => a.length - b.length)[0];
|
||||
const path = `${settings.game_specific.path.slice(0, -1)}${filename.split(settings.game)[1]}`;
|
||||
|
||||
// Ensure Player List is logged for next update
|
||||
const playerListEnabled = parseInt(settings.settings.config.adminLogPlayerList)
|
||||
if (!playerListEnabled) PostServerSettings(NitradoCred, c, "config", "adminLogPlayerList", "1")
|
||||
|
||||
await DownloadNitradoFile(NitradoCred, c, path, `./logs/${NitradoCred.ServerID}-logs.ADM`).then(async (status) => {
|
||||
if (status == 1) return c.error(`Failed to Download Nitrado Log Files - [${NitradoCred.ServerID}]`);
|
||||
await c.readLogs(GuildDB).then(async () => {
|
||||
HandleExpiredUAVs(c, GuildDB);
|
||||
HandleEvents(c, GuildDB)
|
||||
if (c.activePlayersTick == 12) await HandleActivePlayersList(NitradoCred, c, GuildDB);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async connectMongo(mongoURI, dbo) {
|
||||
let failed = false;
|
||||
|
||||
let dbLogDir = path.join(__dirname, "..", "logs", "database-logs.json");
|
||||
let databaselogs;
|
||||
try {
|
||||
databaselogs = JSON.parse(fs.readFileSync(dbLogDir));
|
||||
} catch (err) {
|
||||
databaselogs = {
|
||||
attempts: 0,
|
||||
connected: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (databaselogs.attempts >= 5) {
|
||||
this.error("Failed to connect to mongodb after multiple attempts");
|
||||
return; // prevent further attempts
|
||||
}
|
||||
|
||||
try {
|
||||
// Connect to Mongo database.
|
||||
this.db = await MongoClient.connect(mongoURI, { connectTimeoutMS: 1000 });
|
||||
this.dbo = this.db.db(dbo);
|
||||
this.log("Successfully connected to mongoDB");
|
||||
databaselogs.connected = true;
|
||||
databaselogs.attempts = 0; // reset attempts
|
||||
this.databaseConnected = true;
|
||||
} catch (err) {
|
||||
databaselogs.attempts++;
|
||||
databaselogs.connected = false;
|
||||
let db = mongoURI.includes("@") ? mongoURI.split("@")[1] : mongoURI.split("//")[1];
|
||||
db = db.includes("/") ? db.split("/")[0] : db;
|
||||
this.error(`Failed to connect to mongodb (mongodb://${db}/${dbo}): attempt ${databaselogs.attempts} - ${err}`);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
// write JSON string to a file
|
||||
fs.writeFileSync(dbLogDir, JSON.stringify(databaselogs));
|
||||
|
||||
if (failed) process.exit(-1);
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
// Wait for MongoDB to connect
|
||||
await this.connectMongo(this.config.mongoURI, this.config.dbo);
|
||||
|
||||
if (!this.databaseConnected) return;
|
||||
|
||||
let guilds = await this.dbo.collection("guilds").find({}).toArray();
|
||||
|
||||
/*
|
||||
Initialize auto restart for enabled servers
|
||||
Initialize last logs
|
||||
Initialize Player Sessions
|
||||
*/
|
||||
for (let i = 0; i < guilds.length; i++) {
|
||||
if (!this.exists(guilds[i].Nitrado)) continue;
|
||||
if (guilds[i].server.autoRestart) {
|
||||
const NitradoCred = {
|
||||
ServerID: guilds[i].Nitrado.ServerID,
|
||||
UserID: guilds[i].Nitrado.UserID,
|
||||
Auth: decrypt(
|
||||
guilds[i].Nitrado.Auth,
|
||||
this.config.EncryptionMethod,
|
||||
this.key,
|
||||
this.encryptionIV
|
||||
)
|
||||
};
|
||||
this.arIntervalIds.set(guilds[i].server.serverID, setInterval(CheckServerStatus, this.arInterval, NitradoCred, this))
|
||||
}
|
||||
this.logHistory.set(guilds[i].Nitrado.ServerID, guilds[i].server.lastLog); // Using Nitrado Server ID over guild ID in case of future support for multiple nitrado servers in a single guild
|
||||
this.playerSessions.set(guilds[i].Nitrado.ServerID, new Map()); // Same reason here as named above.
|
||||
this.alarmPingQueue.set(guilds[i].server.serverID, new Map()); // Initialize alarm queue to be empty
|
||||
this.playerListMsgIds.set(guilds[i].server.serverID, ""); // Initialize player list message ids
|
||||
this.log(`[${guilds[i].server.serverID}] Initialized existing Nitrado Server: (${guilds[i].Nitrado.ServerID})`);
|
||||
}
|
||||
}
|
||||
|
||||
async initNewNitradoServer(guildId, Nitrado) {
|
||||
let guild = await GetGuild(this, guildId)
|
||||
|
||||
if (guild.autoRestart) this.arIntervalIds.set(guildId, setInterval(CheckServerStatus, this.arInterval, Nitrado, this))
|
||||
this.logHistory.set(Nitrado.ServerID, guild.lastLog);
|
||||
this.playerSessions.set(Nitrado.ServerID, new Map());
|
||||
this.alarmPingQueue.set(guildId, new Map());
|
||||
this.playerListMsgIds.set(guildId, "");
|
||||
this.log(`[${guildId}] Initialized new Nitrado`);
|
||||
}
|
||||
|
||||
exists(n) { return typeof (n) == "number" ? !isNaN(n) : null != n && undefined != n && "" != n }
|
||||
|
||||
secondsToDhms(seconds) {
|
||||
seconds = Number(seconds);
|
||||
const d = Math.floor(seconds / (3600 * 24));
|
||||
const h = Math.floor(seconds % (3600 * 24) / 3600);
|
||||
const m = Math.floor(seconds % 3600 / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
|
||||
const dDisplay = d > 0 ? d + (d == 1 ? " day, " : " days, ") : "";
|
||||
const hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : "";
|
||||
const mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes, ") : "";
|
||||
const sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
|
||||
return dDisplay + hDisplay + mDisplay + sDisplay;
|
||||
}
|
||||
|
||||
LoadCommandsAndInteractionHandlers() {
|
||||
let CommandsDir = path.join(__dirname, "commands");
|
||||
fs.readdir(CommandsDir, (err, files) => {
|
||||
if (err) this.error(err);
|
||||
else
|
||||
files.forEach((file) => {
|
||||
let cmd = require(CommandsDir + "/" + file);
|
||||
if (!this.exists(cmd.name) || !this.exists(cmd.description))
|
||||
return this.error(
|
||||
"Unable to load Command: " +
|
||||
file.split(".")[0] +
|
||||
", Reason: File doesn't had name/desciption"
|
||||
);
|
||||
this.commands.set(file.split(".")[0].toLowerCase(), cmd);
|
||||
if (this.exists(cmd.Interactions)) {
|
||||
for (let [interaction, handler] of Object.entries(cmd.Interactions)) {
|
||||
this.interactionHandlers.set(interaction, handler);
|
||||
}
|
||||
}
|
||||
this.log("Command Loaded: " + file.split(".")[0]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
LoadEvents() {
|
||||
let EventsDir = path.join(__dirname, "events");
|
||||
fs.readdir(EventsDir, (err, files) => {
|
||||
if (err) this.error(err);
|
||||
else
|
||||
files.forEach((file) => {
|
||||
const event = require(EventsDir + "/" + file);
|
||||
if (["interactionCreate", "guildMemberAdd"].includes(file.split(".")[0])) this.on(file.split(".")[0], i => event(this, i));
|
||||
else this.on(file.split(".")[0], event.bind(null, this));
|
||||
this.log("Event Loaded: " + file.split(".")[0]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Allows shorter lines of code elsewhere
|
||||
GetChannel(channel_id) { return this.channels.cache.get(channel_id); }
|
||||
|
||||
sendError(Channel, Error) {
|
||||
this.error(Error);
|
||||
let embed = new EmbedBuilder()
|
||||
.setColor(this.config.Red)
|
||||
.setDescription(Error);
|
||||
|
||||
Channel.send(embed);
|
||||
}
|
||||
|
||||
// Handles internal errors for slash commands. E.g failed to update database from slash command.
|
||||
sendInternalError(Interaction, Error) {
|
||||
this.error(Error);
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(`**Internal Error:**\nUh Oh D: Its not you, its me.\nThis command has crashed\nContact the Developers\nhttps://discord.gg/YCXhvy9uZw`)
|
||||
.setColor(this.config.Colors.Red)
|
||||
|
||||
try {
|
||||
Interaction.send({ embeds: [embed] });
|
||||
} catch {
|
||||
Interaction.update({ embeds: [embed], components: [] });
|
||||
}
|
||||
}
|
||||
|
||||
// Calls register for guild and global commands
|
||||
RegisterSlashCommands() {
|
||||
RegisterGlobalCommands(this);
|
||||
let p = Promise.resolve()
|
||||
this.guilds.cache.forEach((guild) => {
|
||||
p = p.then(() => {
|
||||
RegisterGuildCommands(this, guild.id);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
build() {
|
||||
this.login(this.config.Token);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DayZR;
|
||||
+926
@@ -0,0 +1,926 @@
|
||||
// Discord.js & REST API
|
||||
import {
|
||||
Collection,
|
||||
Client,
|
||||
EmbedBuilder,
|
||||
Routes,
|
||||
InteractionResponseType,
|
||||
InteractionType,
|
||||
GatewayDispatchEvents,
|
||||
ClientOptions,
|
||||
Guild,
|
||||
ChatInputCommandInteraction,
|
||||
Snowflake,
|
||||
Role,
|
||||
Message,
|
||||
TextBasedChannel,
|
||||
ButtonInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
Channel,
|
||||
} from "discord.js";
|
||||
import { REST } from "@discordjs/rest";
|
||||
|
||||
// MongoDB
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
// Node.js built-ins
|
||||
import * as path from "path";
|
||||
import * as fs from "node:fs";
|
||||
import * as readline from "node:readline";
|
||||
import * as crypto from "crypto";
|
||||
import { Readable } from "node:stream";
|
||||
|
||||
// Custom utilities
|
||||
import Logger from "./util/Logger";
|
||||
import { RegisterGlobalCommands, RegisterGuildCommands } from "./util/RegisterSlashCommands";
|
||||
import {
|
||||
DownloadNitradoFile,
|
||||
CheckServerStatus,
|
||||
FetchServerSettings,
|
||||
PostServerSettings,
|
||||
NitradoCredentialStatus,
|
||||
} from "./util/NitradoAPI";
|
||||
import { HandlePlayerLogs, HandleActivePlayersList } from "./util/LogsHandler";
|
||||
import { HandleKillfeed, UpdateLastDeathDate } from "./util/KillfeedHandler";
|
||||
import { HandleExpiredUAVs, HandleEvents, PlaceFireplaceInAlarm } from "./util/AlarmsHandler";
|
||||
import { decrypt } from "./util/Cryptic";
|
||||
import { GetWebhook, WebhookSend } from "./util/WebhookHandler";
|
||||
import { Config, Colors } from "./config/config";
|
||||
|
||||
// Database structures
|
||||
import { getDefaultPlayer, UpdatePlayer, Player } from "./database/player";
|
||||
import { Missions, MissionKey } from "./database/destinations";
|
||||
import { GetGuild, GuildConfig, NitradoConfig } from "./database/guild";
|
||||
|
||||
const MINUTE = 60000; // 1 minute in milliseconds
|
||||
const AUTO_RESTART_INTERVAL = 600000; // Set auto-restart interval 10 minutes (600,000ms)
|
||||
const STARTING_PLAYERS_TICK = 11;
|
||||
|
||||
// TODO: move this interface to LogsHandler when converted to ts
|
||||
interface PlayerSession {
|
||||
startTime: Date;
|
||||
endTime: Date | null;
|
||||
};
|
||||
|
||||
// TODO: put this somewhere else
|
||||
interface CommandOptions {
|
||||
|
||||
};
|
||||
|
||||
interface InteractionHandler {
|
||||
run: (
|
||||
client: DayZR,
|
||||
interaction: ChatInputCommandInteraction,
|
||||
GuildDB: GuildConfig
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
interface Command {
|
||||
name: string;
|
||||
debug: boolean;
|
||||
global: boolean;
|
||||
description: string;
|
||||
usage: string;
|
||||
permissions: {
|
||||
channel: Array<string>;
|
||||
member: Array<string>;
|
||||
};
|
||||
options: Array<CommandOptions>;
|
||||
SlashCommand: {
|
||||
run: (
|
||||
client: DayZR,
|
||||
interaction: ChatInputCommandInteraction,
|
||||
args: any, // Unsure of what this should be, come back to later
|
||||
GuildDB: GuildConfig,
|
||||
start?: number
|
||||
) => Promise<void>;
|
||||
};
|
||||
Interactions?: {
|
||||
[InteractoinName: string]: InteractionHandler;
|
||||
}
|
||||
}
|
||||
|
||||
export default class DayZR extends Client
|
||||
{
|
||||
|
||||
private config: Config;
|
||||
private logger: Logger;
|
||||
|
||||
private commands: Collection<string, Command>;
|
||||
private interactionHandlers: Collection<string, InteractionHandler>;
|
||||
|
||||
private key: string;
|
||||
private encryptionIV: string;
|
||||
private db: any;
|
||||
public dbo: any;
|
||||
public databaseConnected: boolean;
|
||||
|
||||
private arInterval: number;
|
||||
private arIntervalIds: Map<string, NodeJS.Timeout>;
|
||||
private playerSessions: Map<string, Map<string, PlayerSession>>;
|
||||
private logHistory: Map<string, string | null>;
|
||||
private alarmPingQueue: Map<string, Map<string, Map<string, Array<EmbedBuilder>>>>;
|
||||
private playerListMsgIds: Map<string, string>;
|
||||
|
||||
private Ready: boolean;
|
||||
private activePlayersTick: number;
|
||||
private timer: number;
|
||||
|
||||
constructor(options: ClientOptions, config: Config)
|
||||
{
|
||||
super(options);
|
||||
|
||||
this.config = config;
|
||||
this.commands = new Collection();
|
||||
this.interactionHandlers = new Collection();
|
||||
this.logger = new Logger(path.join(__dirname, "..", "logs", "Logs.log"));
|
||||
this.timer = this.config.Dev == "PROD." ? MINUTE * 5 : MINUTE / 4;
|
||||
|
||||
if (
|
||||
this.config.Token === "" ||
|
||||
this.config.SecretKey === "" ||
|
||||
this.config.SecretIv === ""
|
||||
) {
|
||||
throw new TypeError(
|
||||
"The config.js is not filled out. Please make sure nothing is blank, otherwise the bot will not work properly."
|
||||
);
|
||||
}
|
||||
|
||||
if (!["DEV.", "PROD."].includes(this.config.Dev)) {
|
||||
throw new TypeError(
|
||||
"The Dev version in the config.js does not match the allowed cases of \"DEV.\" or \"PROD.\""
|
||||
);
|
||||
}
|
||||
|
||||
// Generate secret hash with crypto to use for encryption
|
||||
this.key = crypto
|
||||
.createHash("sha512")
|
||||
.update(this.config.SecretKey)
|
||||
.digest("hex")
|
||||
.substring(0, 32);
|
||||
|
||||
this.encryptionIV = crypto
|
||||
.createHash("sha512")
|
||||
.update(this.config.SecretIv)
|
||||
.digest("hex")
|
||||
.substring(0, 16);
|
||||
|
||||
this.databaseConnected = false;
|
||||
this.arInterval = AUTO_RESTART_INTERVAL;
|
||||
this.arIntervalIds = new Map();
|
||||
this.playerSessions = new Map();
|
||||
this.logHistory = new Map();
|
||||
this.alarmPingQueue = new Map();
|
||||
this.playerListMsgIds = new Map();
|
||||
|
||||
this.initialize();
|
||||
this.LoadCommandsAndInteractionHandlers();
|
||||
this.LoadEvents();
|
||||
|
||||
this.Ready = false;
|
||||
this.activePlayersTick = STARTING_PLAYERS_TICK;
|
||||
|
||||
this.ws.on(
|
||||
GatewayDispatchEvents.InteractionCreate,
|
||||
async (interaction: ChatInputCommandInteraction) =>
|
||||
{
|
||||
|
||||
const start: number = new Date().getTime();
|
||||
|
||||
if (interaction.type == InteractionType.ApplicationCommand)
|
||||
{
|
||||
if (!interaction.guildId) return;
|
||||
|
||||
let GuildDB: GuildConfig = await GetGuild(this, interaction.guildId);
|
||||
|
||||
if (this.exists(GuildDB.Nitrado) && this.exists(GuildDB.Nitrado.Auth))
|
||||
{
|
||||
GuildDB.Nitrado.Auth = decrypt(
|
||||
GuildDB.Nitrado.Auth,
|
||||
this.config.EncryptionMethod,
|
||||
this.key,
|
||||
this.encryptionIV
|
||||
);
|
||||
}
|
||||
|
||||
const command: string = interaction.commandName;
|
||||
const args: any = interaction.options;
|
||||
|
||||
// Free unused armbands for related commands
|
||||
if (["armbands", "claim", "factions"].includes(command))
|
||||
{
|
||||
|
||||
for (const [factionID, data] of Object.entries(GuildDB.factionArmbands))
|
||||
{
|
||||
|
||||
const guild: Guild | undefined = this.guilds.cache.get(GuildDB.serverID);
|
||||
|
||||
if (!guild) continue;
|
||||
|
||||
const role: Role | undefined = guild.roles.cache.find(role => role.id == factionID);
|
||||
|
||||
if (!role)
|
||||
{
|
||||
let query = {
|
||||
$pull: { "server.usedArmbands": data.armband },
|
||||
$unset: { [`server.factionArmbands.${factionID}`]: "" },
|
||||
};
|
||||
|
||||
this.dbo.collection("guilds").updateOne(
|
||||
{
|
||||
"server.serverID": GuildDB.serverID
|
||||
},
|
||||
query,
|
||||
(err: string) =>
|
||||
{
|
||||
if (err) return this.sendInternalError(interaction, err);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.log(`Interaction [${interaction.guildId}] - ${command}`);
|
||||
|
||||
const rest = new REST({ version: "10" }).setToken(this.config.Token);
|
||||
|
||||
const normalizeMessage = (message: any) =>
|
||||
{
|
||||
if (message?.embeds) {
|
||||
message = {
|
||||
...message,
|
||||
embeds: message.embeds.map((embed: EmbedBuilder | any) =>
|
||||
embed instanceof EmbedBuilder ? embed.toJSON() : embed
|
||||
),
|
||||
};
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
// Easy to send response so ;)
|
||||
const handleCallback = async (interactionType: InteractionResponseType, message: any): Promise<unknown> =>
|
||||
{
|
||||
message = normalizeMessage(message);
|
||||
|
||||
return await rest.post(Routes.interactionCallback(interaction.id, interaction.token),
|
||||
{
|
||||
body: {
|
||||
type: interactionType,
|
||||
data: message,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Nicely name our custom callback functions and pass correct type because discord is picky with numbers...
|
||||
interaction.send = async (message: Message): Promise<unknown> => handleCallback(InteractionResponseType.ChannelMessageWithSource, message);
|
||||
interaction.deferReply = async (message: Message): Promise<unknown> => handleCallback(InteractionResponseType.DeferredChannelMessageWithSource, message);
|
||||
interaction.showModal = async (message: Message): Promise<unknown> => handleCallback(InteractionResponseType.Modal, message);
|
||||
|
||||
interaction.editReply = async (message: Message): Promise<unknown> =>
|
||||
{
|
||||
if (!this.application) return;
|
||||
return await rest.patch(Routes.webhookMessage(this.application.id, interaction.token), {
|
||||
body: message,
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.databaseConnected)
|
||||
{
|
||||
const dbFailedEmbed: EmbedBuilder = new EmbedBuilder()
|
||||
.setDescription(`**Internal Error:**\nUh Oh D: Its not you, its me.\nThe bot has failed to connect to the database 5 times!\nContact the Developers\nhttps://discord.gg/YCXhvy9uZw`)
|
||||
.setColor(Colors.Red)
|
||||
|
||||
return interaction.send({ embeds: [dbFailedEmbed] });
|
||||
}
|
||||
|
||||
let cmd: Command | undefined = this.commands.get(command);
|
||||
if (!cmd) return;
|
||||
try {
|
||||
cmd.SlashCommand.run(this, interaction, args, GuildDB, start); // start is only used in ping / stats command
|
||||
} catch (err: any) {
|
||||
this.sendInternalError(interaction, err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private log(text: string): void { this.logger.log(text); }
|
||||
public error(text: string): void { this.logger.error(text); }
|
||||
|
||||
public getDateEST(time: string): Date
|
||||
{
|
||||
// TODO: fix magic numbers <------------------------------------------------------------------------------------
|
||||
let timeArray: Array<number> = time.split(" ")[0].split(":").map((v) => parseInt(v));
|
||||
let t: Date = new Date(); // Get current date & time (UTC)
|
||||
let f: Date = new Date(t.getTime() - 4 * 3600000); // Convert UTC into EST time to roll back the day as necessary
|
||||
|
||||
f.setUTCHours(timeArray[0], timeArray[1], timeArray[2]); // Apply the supplied EST time to the converted date (EST is the timezone produced from the Nitrado logs).
|
||||
return new Date(f.getTime() + 4 * 3600000); // Add EST time offset to return timestamp in UTC
|
||||
}
|
||||
|
||||
private async readLogs(guild: GuildConfig): Promise<void>
|
||||
{
|
||||
|
||||
if (!guild.Nitrado) return;
|
||||
|
||||
const fileStream: fs.ReadStream = fs.createReadStream(`./logs/${guild.Nitrado.ServerID}-logs.ADM`);
|
||||
|
||||
const rl: readline.Interface = readline.createInterface({
|
||||
input: fileStream as Readable,
|
||||
crlfDelay: Infinity
|
||||
});
|
||||
let lines: Array<string> = [];
|
||||
for await (const line of rl) { lines.push(line); }
|
||||
|
||||
const lastLog: string | null | undefined = this.logHistory.get(guild.Nitrado.ServerID);
|
||||
|
||||
let logIndex = -1;
|
||||
if (lastLog != null)
|
||||
{
|
||||
logIndex = lines.indexOf(lastLog);
|
||||
}
|
||||
|
||||
if (this.playerSessions.get(guild.Nitrado.ServerID)?.size === 0)
|
||||
{
|
||||
|
||||
// Get all players of this server
|
||||
let players: Array<Player> = await this.dbo.collection("players").find(
|
||||
{
|
||||
"nitradoServerID": guild.Nitrado.ServerID
|
||||
}
|
||||
).toArray().then((all: Array<Player>) =>
|
||||
{
|
||||
// assume all players who were previously connected are not connected on init only.
|
||||
return all.filter(p => p.connected).map(p => p.connected = false);
|
||||
}
|
||||
);
|
||||
|
||||
for (let i = 0; i < players.length; i++)
|
||||
{
|
||||
await UpdatePlayer(this, players[i])
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = logIndex + 1; i < lines.length; i++)
|
||||
{
|
||||
// TODO: tidy this up its just bad looking
|
||||
|
||||
// Handle lines to skip
|
||||
if (lines[i].includes("| ####")) continue;
|
||||
if (lines[i].includes("(id=Unknown") || lines[i].includes("Player \"Unknown Entity\"")) continue;
|
||||
if ((i - 1) >= 0 && lines[i] == lines[i - 1]) continue; // continue if this line is a duplicate of the last line
|
||||
|
||||
// Handle general logs
|
||||
if (lines[i].includes("connected") || lines[i].includes("pos=<")) await HandlePlayerLogs(guild.Nitrado.ServerID, this, guild, lines[i], guild.combatLogTimer);
|
||||
if (lines[i].includes("killed by Zmb") || lines[i].includes(">) died.")) await UpdateLastDeathDate(guild.Nitrado.ServerID, this, lines[i]); // Updates users last death date for non PVP deaths.
|
||||
if (lines[i].includes(") placed Fireplace")) await PlaceFireplaceInAlarm(this, guild, lines[i]);
|
||||
|
||||
// Handle killfeed logs
|
||||
if (
|
||||
(lines[i].includes("killed by with") || lines[i].includes("killed by LandMineTrap")) || // Handle explosive deaths
|
||||
(!(i + 1 >= lines.length) && lines[i + 1].includes("killed by") && lines[i].includes("TransportHit")) || // Handle vehicle deaths
|
||||
(!(i + 1 >= lines.length) && lines[i + 1].includes("killed by Player") && lines[i].includes("hit by Player")) || // Handle PVP deaths
|
||||
(lines[i].includes("killed by Player") && !lines[i - 1].includes("hit by Player")) // Handle deaths missing hit by log
|
||||
) await HandleKillfeed(guild.Nitrado.ServerID, this, guild, lines[i]);
|
||||
}
|
||||
|
||||
// Handle alarm pings
|
||||
const maxEmbed = 10;
|
||||
|
||||
this.alarmPingQueue.forEach(queue =>
|
||||
{
|
||||
queue.forEach(async (data, channel_id) =>
|
||||
{
|
||||
const channel = this.GetChannel(channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
const NAME = "DayZ.R Zone Alert";
|
||||
const webhook = await GetWebhook(this, NAME, channel_id);
|
||||
|
||||
data.forEach(async (embeds, role) =>
|
||||
{
|
||||
let embedArrays: Array<Array<EmbedBuilder>> = [];
|
||||
while (embeds.length > 0)
|
||||
{
|
||||
embedArrays.push(embeds.splice(0, maxEmbed));
|
||||
}
|
||||
|
||||
for (let i = 0; i < embedArrays.length; i++)
|
||||
{
|
||||
interface AlarmEmbed
|
||||
{
|
||||
embeds: any;
|
||||
content?: string;
|
||||
};
|
||||
|
||||
let content: AlarmEmbed = { embeds: embedArrays[i] };
|
||||
|
||||
if (role != "-no-role-ping-") content.content = `<@&${role}>`;
|
||||
WebhookSend(this, webhook, content);
|
||||
|
||||
// if (role == "-no-role-ping-") channel.send({ embeds: embedArrays[i] });
|
||||
// else channel.send({ content: `<@&${role}>`, embeds: embedArrays[i] });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.alarmPingQueue.set(guild.serverID, new Map()); // Clear alarm queue for this guild
|
||||
|
||||
const playerTemplate = /(.*) \| Player \"(.*)\" \(id=(.*) pos=<(.*)>\)/g;
|
||||
let previouslyConnected = await this.dbo.collection("players").find(
|
||||
{
|
||||
"nitradoServerID": guild.Nitrado.ServerID
|
||||
}
|
||||
).toArray().then((players: Array<Player>) => {
|
||||
// All players with connection log captured above and no disconnect log
|
||||
return players.filter(p => p.connected);
|
||||
});
|
||||
|
||||
let lastDetectedTime;
|
||||
|
||||
for (let i = lines.length - 1; i > 0; i--)
|
||||
{
|
||||
|
||||
if (lines[i].includes("PlayerList log:"))
|
||||
{
|
||||
|
||||
for (let j = i + 1; j < lines.length; j++)
|
||||
{
|
||||
|
||||
let line = lines[j];
|
||||
if (line.includes("| ####")) break;
|
||||
|
||||
let data = [...line.matchAll(playerTemplate)][0];
|
||||
if (!data) continue;
|
||||
|
||||
let info = {
|
||||
time: data[1],
|
||||
player: data[2],
|
||||
playerID: data[3],
|
||||
};
|
||||
|
||||
// Skip this player if the player does not exist.
|
||||
if (!this.exists(info.player) || !this.exists(info.playerID)) continue;
|
||||
|
||||
lastDetectedTime = await this.getDateEST(info.time);
|
||||
|
||||
let playerStat = await this.dbo.collection("players").findOne({ "playerID": info.playerID });
|
||||
if (!this.exists(playerStat)) playerStat = getDefaultPlayer(info.player, info.playerID, guild.Nitrado.ServerID);
|
||||
|
||||
// Skip this player if the lastDisconnectionDate time is later than the player log entry.
|
||||
if (!previouslyConnected.includes(playerStat) &&
|
||||
this.exists(playerStat.lastDisconnectionDate) &&
|
||||
playerStat.lastDisconnectionDate !== null &&
|
||||
playerStat.lastDisconnectionDate.getTime() > lastDetectedTime.getTime()
|
||||
) continue;
|
||||
|
||||
|
||||
// Track adjusted sessions this instance has handled (e.g. no bot crashes or restarts).
|
||||
if (this.playerSessions.get(guild.Nitrado.ServerID)?.has(info.playerID))
|
||||
{
|
||||
|
||||
// Player is already in a session, update the session"s end time.
|
||||
let session: PlayerSession | undefined = this.playerSessions.get(guild.Nitrado.ServerID)?.get(info.playerID);
|
||||
|
||||
if (!session) continue;
|
||||
|
||||
session.endTime = lastDetectedTime; // Update end time.
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Player is not in a session, create a new session.
|
||||
const newSession = {
|
||||
startTime: lastDetectedTime,
|
||||
endTime: null, // Initialize end time as null.
|
||||
};
|
||||
|
||||
this.playerSessions.get(guild.Nitrado.ServerID)?.set(info.playerID, newSession);
|
||||
|
||||
// Check if the player has been marked as connected before, but only if a session doesn"t exist
|
||||
// in the map, indicating the connection was discovered in the logs during this session.
|
||||
if (!previouslyConnected.includes(playerStat))
|
||||
{
|
||||
playerStat.connected = true;
|
||||
playerStat.lastConnectionDate = lastDetectedTime; // Update last connection date.
|
||||
}
|
||||
}
|
||||
|
||||
await UpdatePlayer(this, playerStat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const lastLine = lines[lines.length - 1];
|
||||
this.logHistory.set(guild.Nitrado.ServerID, lastLine);
|
||||
|
||||
this.dbo.collection("guilds").updateOne(
|
||||
{
|
||||
"server.serverID": guild.serverID
|
||||
},
|
||||
{
|
||||
$set: { "server.lastLog": lastLine }
|
||||
},
|
||||
(err: string) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
this.error(`Failed to save last log to guild config [${guild.serverID}] for nitrado server [${guild.Nitrado?.ServerID}]`);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async logsUpdateTimer(c: DayZR): Promise<void>
|
||||
{
|
||||
c.activePlayersTick++;
|
||||
|
||||
c.guilds.cache.forEach(async (guild) =>
|
||||
{
|
||||
let GuildDB = await GetGuild(c, guild.id);
|
||||
|
||||
/*
|
||||
Note to self:
|
||||
return statements do not prematurely exit out of a forEach loop like it does in a for loop.
|
||||
*/
|
||||
|
||||
// Continue if no nitrado credentials
|
||||
if (!c.exists(GuildDB.Nitrado)) return;
|
||||
|
||||
// Continue if these credentials are marked as failed
|
||||
if (GuildDB.Nitrado.Status == NitradoCredentialStatus.FAILED) return;
|
||||
|
||||
const NitradoCred = {
|
||||
ServerID: GuildDB.Nitrado.ServerID,
|
||||
UserID: GuildDB.Nitrado.UserID,
|
||||
Auth: decrypt(
|
||||
GuildDB.Nitrado.Auth,
|
||||
c.config.EncryptionMethod,
|
||||
c.key,
|
||||
c.encryptionIV
|
||||
),
|
||||
};
|
||||
|
||||
const response = await FetchServerSettings(NitradoCred, c, "logsUpdateTimer").then(res => res);
|
||||
|
||||
// TODO: fix magic number
|
||||
if (response == 1)
|
||||
{
|
||||
c.dbo.collection("guilds").updateOne(
|
||||
{
|
||||
"server.serverID": GuildDB.serverID
|
||||
},
|
||||
{
|
||||
$set: { "Nitrado.Status": NitradoCredentialStatus.FAILED }
|
||||
},
|
||||
(err: string) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
this.error(`Failed to update Nitrado status to failed. [${GuildDB.serverID}]`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
const settings = response.data.gameserver;
|
||||
|
||||
// Update Nitrado DayZ Mission if change is detected
|
||||
let missionKey: MissionKey = settings.settings.config.mission;
|
||||
if (GuildDB.Nitrado.Mission !== Missions[missionKey])
|
||||
{
|
||||
c.dbo.collection("guilds").updateOne(
|
||||
{
|
||||
"server.serverID": GuildDB.serverID
|
||||
},
|
||||
{
|
||||
$set: { "Nitrado.Mission": Missions[missionKey] }
|
||||
},
|
||||
(err: string) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
this.error(`Failed to save mission to guild config [${GuildDB.serverID}] for nitrado server [${GuildDB.Nitrado?.ServerID}]`)
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
GuildDB.Nitrado.Mission = Missions[missionKey];
|
||||
|
||||
// Ignore if no log files on Nitrado server
|
||||
if (settings.game_specific.log_files.length == 0) return;
|
||||
|
||||
// Get file with longest file name (target log file)
|
||||
const filename = settings.game_specific.log_files.sort((a: string, b: string) => a.length - b.length)[0];
|
||||
const path = `${settings.game_specific.path.slice(0, -1)}${filename.split(settings.game)[1]}`;
|
||||
|
||||
// Ensure Player List is logged for next update
|
||||
const playerListEnabled = parseInt(settings.settings.config.adminLogPlayerList)
|
||||
if (!playerListEnabled) PostServerSettings(NitradoCred, c, "config", "adminLogPlayerList", "1")
|
||||
|
||||
await DownloadNitradoFile(NitradoCred, c, path, `./logs/${NitradoCred.ServerID}-logs.ADM`)
|
||||
.then(async (status) =>
|
||||
{
|
||||
if (status == 1) return c.error(`Failed to Download Nitrado Log Files - [${NitradoCred.ServerID}]`);
|
||||
|
||||
await c.readLogs(GuildDB).then(async () =>
|
||||
{
|
||||
HandleExpiredUAVs(c, GuildDB);
|
||||
HandleEvents(c, GuildDB)
|
||||
|
||||
// TODO: fix magic number
|
||||
if (c.activePlayersTick == 12) await HandleActivePlayersList(NitradoCred, c, GuildDB);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async connectMongo(mongoURI: string, dbo: string): Promise<void>
|
||||
{
|
||||
|
||||
let failed: boolean = false;
|
||||
let dbLogDir: string = path.join(__dirname, "..", "logs", "database-logs.json");
|
||||
|
||||
interface DatabaseLog
|
||||
{
|
||||
attempts: number;
|
||||
connected: boolean;
|
||||
};
|
||||
|
||||
let databaselogs: DatabaseLog;
|
||||
|
||||
try
|
||||
{
|
||||
databaselogs = JSON.parse(
|
||||
fs.readFileSync(dbLogDir).toString("utf-8")
|
||||
);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
databaselogs = {
|
||||
attempts: 0,
|
||||
connected: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (databaselogs.attempts >= 5)
|
||||
{
|
||||
this.error("Failed to connect to mongodb after multiple attempts");
|
||||
return; // prevent further attempts
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Connect to Mongo database.
|
||||
this.db = new MongoClient(mongoURI, { connectTimeoutMS: 1000 });
|
||||
await this.db.connect();
|
||||
|
||||
this.dbo = this.db.db(dbo);
|
||||
this.log("Successfully connected to mongoDB");
|
||||
|
||||
databaselogs.connected = true;
|
||||
databaselogs.attempts = 0; // reset attempts
|
||||
this.databaseConnected = true;
|
||||
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
databaselogs.attempts++;
|
||||
databaselogs.connected = false;
|
||||
|
||||
let db = mongoURI.includes("@") ? mongoURI.split("@")[1] : mongoURI.split("//")[1];
|
||||
db = db.includes("/") ? db.split("/")[0] : db;
|
||||
|
||||
this.error(`Failed to connect to mongodb (mongodb://${db}/${dbo}): attempt ${databaselogs.attempts} - ${err}`);
|
||||
|
||||
failed = true;
|
||||
}
|
||||
|
||||
// write JSON string to a file
|
||||
fs.writeFileSync(dbLogDir, JSON.stringify(databaselogs));
|
||||
|
||||
if (failed) process.exit(-1);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void>
|
||||
{
|
||||
|
||||
// Wait for MongoDB to connect
|
||||
await this.connectMongo(this.config.MongoURI, this.config.DBO);
|
||||
|
||||
if (!this.databaseConnected) return;
|
||||
|
||||
let guilds = await this.dbo.collection("guilds").find({}).toArray();
|
||||
|
||||
/*
|
||||
Initialize auto restart for enabled servers
|
||||
Initialize last logs
|
||||
Initialize Player Sessions
|
||||
*/
|
||||
for (let i = 0; i < guilds.length; i++)
|
||||
{
|
||||
if (!this.exists(guilds[i].Nitrado)) continue;
|
||||
|
||||
if (guilds[i].server.autoRestart)
|
||||
{
|
||||
const NitradoCred = {
|
||||
ServerID: guilds[i].Nitrado.ServerID,
|
||||
UserID: guilds[i].Nitrado.UserID,
|
||||
Auth: decrypt(
|
||||
guilds[i].Nitrado.Auth,
|
||||
this.config.EncryptionMethod,
|
||||
this.key,
|
||||
this.encryptionIV
|
||||
)
|
||||
};
|
||||
this.arIntervalIds.set(guilds[i].server.serverID, setInterval(CheckServerStatus, this.arInterval, NitradoCred, this))
|
||||
}
|
||||
this.logHistory.set(guilds[i].Nitrado.ServerID, guilds[i].server.lastLog); // Using Nitrado Server ID over guild ID in case of future support for multiple nitrado servers in a single guild
|
||||
this.playerSessions.set(guilds[i].Nitrado.ServerID, new Map()); // Same reason here as named above.
|
||||
this.alarmPingQueue.set(guilds[i].server.serverID, new Map()); // Initialize alarm queue to be empty
|
||||
this.playerListMsgIds.set(guilds[i].server.serverID, ""); // Initialize player list message ids
|
||||
this.log(`[${guilds[i].server.serverID}] Initialized existing Nitrado Server: (${guilds[i].Nitrado.ServerID})`);
|
||||
}
|
||||
}
|
||||
|
||||
async initNewNitradoServer(
|
||||
guildId: Snowflake,
|
||||
Nitrado: NitradoConfig
|
||||
): Promise<void>
|
||||
{
|
||||
let guild = await GetGuild(this, guildId)
|
||||
|
||||
if (guild.autoRestart)
|
||||
{
|
||||
this.arIntervalIds.set(guildId, setInterval(CheckServerStatus, this.arInterval, Nitrado, this))
|
||||
}
|
||||
|
||||
this.logHistory.set(Nitrado.ServerID, guild.lastLog);
|
||||
this.playerSessions.set(Nitrado.ServerID, new Map());
|
||||
this.alarmPingQueue.set(guildId, new Map());
|
||||
this.playerListMsgIds.set(guildId, "");
|
||||
this.log(`[${guildId}] Initialized new Nitrado`);
|
||||
}
|
||||
|
||||
exists<T> (n: T | null | undefined | "" | number): n is T
|
||||
{
|
||||
return typeof n === "number" ? !isNaN(n) : n !== null && n !== undefined && n !== "";
|
||||
}
|
||||
|
||||
secondsToDhms(seconds: number): string
|
||||
{
|
||||
|
||||
// TODO; holy magic numbers, my prof is gonna assasinate me if he sees this
|
||||
seconds = Number(seconds);
|
||||
const d = Math.floor(seconds / (3600 * 24));
|
||||
const h = Math.floor(seconds % (3600 * 24) / 3600);
|
||||
const m = Math.floor(seconds % 3600 / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
|
||||
const dDisplay = d > 0 ? d + (d == 1 ? " day, " : " days, ") : "";
|
||||
const hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : "";
|
||||
const mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes, ") : "";
|
||||
const sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
|
||||
|
||||
return dDisplay + hDisplay + mDisplay + sDisplay;
|
||||
}
|
||||
|
||||
LoadCommandsAndInteractionHandlers(): void
|
||||
{
|
||||
let CommandsDir = path.join(__dirname, "commands");
|
||||
|
||||
fs.readdir(CommandsDir, (err, files) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
this.error(err.message);
|
||||
}
|
||||
else
|
||||
{
|
||||
files.forEach((file) =>
|
||||
{
|
||||
let cmd: Command = require(CommandsDir + "/" + file);
|
||||
if (!this.exists(cmd.name) || !this.exists(cmd.description))
|
||||
{
|
||||
return this.error(
|
||||
"Unable to load Command: " +
|
||||
file.split(".")[0] +
|
||||
", Reason: File doesn't had name/desciption"
|
||||
);
|
||||
}
|
||||
|
||||
this.commands.set(file.split(".")[0].toLowerCase(), cmd);
|
||||
|
||||
if (this.exists(cmd.Interactions))
|
||||
{
|
||||
for (let [interaction, handler] of Object.entries(cmd.Interactions))
|
||||
{
|
||||
this.interactionHandlers.set(interaction, handler);
|
||||
}
|
||||
}
|
||||
|
||||
this.log("Command Loaded: " + file.split(".")[0]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LoadEvents(): void
|
||||
{
|
||||
let EventsDir = path.join(__dirname, "events");
|
||||
|
||||
fs.readdir(EventsDir, (err, files) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
this.error(err.message);
|
||||
}
|
||||
else
|
||||
{
|
||||
files.forEach((file) =>
|
||||
{
|
||||
const event = require(EventsDir + "/" + file);
|
||||
|
||||
if (["interactionCreate", "guildMemberAdd"].includes(file.split(".")[0]))
|
||||
{
|
||||
this.on(file.split(".")[0], i => event(this, i));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.on(file.split(".")[0], event.bind(null, this));
|
||||
}
|
||||
|
||||
this.log("Event Loaded: " + file.split(".")[0]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Allows shorter lines of code elsewhere
|
||||
GetChannel(channel_id: Snowflake): Channel | undefined
|
||||
{
|
||||
return this.channels.cache.get(channel_id);
|
||||
}
|
||||
|
||||
sendError(channel: TextBasedChannel, error: string): void
|
||||
{
|
||||
this.error(error);
|
||||
let embed: EmbedBuilder = new EmbedBuilder()
|
||||
.setColor(Colors.Red)
|
||||
.setDescription(error);
|
||||
|
||||
|
||||
channel.send({ embeds: [embed] });
|
||||
}
|
||||
|
||||
// Handles internal errors for slash commands. E.g failed to update database from slash command.
|
||||
sendInternalError(
|
||||
interaction: ChatInputCommandInteraction | ButtonInteraction | StringSelectMenuInteraction,
|
||||
error: string
|
||||
): void
|
||||
{
|
||||
this.error(error);
|
||||
const embed: EmbedBuilder = new EmbedBuilder()
|
||||
.setDescription(`**Internal Error:**\nUh Oh D: Its not you, its me.\nThis command has crashed\nContact the Developers\nhttps://discord.gg/YCXhvy9uZw`)
|
||||
.setColor(Colors.Red)
|
||||
|
||||
if (interaction.isChatInputCommand())
|
||||
{
|
||||
interaction.send({ embeds: [embed] });
|
||||
}
|
||||
else if (interaction.isButton() || interaction.isStringSelectMenu())
|
||||
{
|
||||
interaction.update({ embeds: [embed], components: [] });
|
||||
}
|
||||
}
|
||||
|
||||
// Calls register for guild and global commands
|
||||
RegisterSlashCommands(): void
|
||||
{
|
||||
RegisterGlobalCommands(this);
|
||||
|
||||
let p = Promise.resolve()
|
||||
|
||||
this.guilds.cache.forEach((guild) =>
|
||||
{
|
||||
p = p.then(() =>
|
||||
{
|
||||
RegisterGuildCommands(this, guild.id);
|
||||
return new Promise((resolve) =>
|
||||
{
|
||||
setTimeout(resolve, 500); // <<--- TODO: MAGIC NUMBER!
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
build(): void
|
||||
{
|
||||
this.login(this.config.Token);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ export const enum DevelopmentBranch {
|
||||
Prod = "PROD.",
|
||||
};
|
||||
|
||||
export const enum Colors {
|
||||
// switch to const later once removed Colors from config
|
||||
export enum Colors {
|
||||
Default = "#8a7c72",
|
||||
DarkRed = "#ba0f0f",
|
||||
Red = "#f55c5c",
|
||||
@@ -25,6 +26,7 @@ export interface Config {
|
||||
Dev: DevelopmentBranch;
|
||||
Version: string;
|
||||
Admins: Array<string>;
|
||||
Colors: typeof Colors;
|
||||
SupportServer: string;
|
||||
Token: string;
|
||||
SecretKey: string;
|
||||
@@ -43,6 +45,7 @@ export const config: Config = {
|
||||
Dev: <DevelopmentBranch>process.env.Dev || DevelopmentBranch.Dev,
|
||||
Version: version,
|
||||
Admins: ["362791661274660874", "329371697570381824"], // Admins of the bot
|
||||
Colors: Colors, // only here for backwards compatibility with commands/*
|
||||
SupportServer: "https://discord.gg/KVFJCvvFtK", // Support Server Link
|
||||
Token: process.env.token || "", //Discord Bot Token
|
||||
SecretKey: process.env.key || "01234567891",
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
module.exports = {
|
||||
Armbands: [
|
||||
{
|
||||
name: "Black",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/82/ArmbandBlack.png/revision/latest?cb=20161127174754"
|
||||
},
|
||||
{
|
||||
name: "Blue",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/bd/ArmbandBlue.png/revision/latest?cb=20161127174803"
|
||||
},
|
||||
{
|
||||
name: "Green",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/ce/ArmbandGreen.png/revision/latest?cb=20161127174812"
|
||||
},
|
||||
{
|
||||
name: "Orange",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e6/ArmbandOrange.png/revision/latest?cb=20161127174846"
|
||||
},
|
||||
{
|
||||
name: "Pink",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/f7/ArmbandPink.png/revision/latest?cb=20161127174854"
|
||||
},
|
||||
{
|
||||
name: "Red",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/1/14/Armband.png/revision/latest?cb=20161127174901"
|
||||
},
|
||||
{
|
||||
name: "Yellow",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/81/ArmbandYellow.png/revision/latest?cb=20161127174918"
|
||||
},
|
||||
{
|
||||
name: "White",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c7/Armband_White.png/revision/latest?cb=20161127174926"
|
||||
},
|
||||
{
|
||||
name: "Altis",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ee/Flag_alti_co.png/revision/latest?cb=20200820222622"
|
||||
},
|
||||
{
|
||||
name: "Asiain Pacific Alliance (APA)",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c2/Flag_apa_co.png/revision/latest?cb=20200820222623"
|
||||
},
|
||||
{
|
||||
name: "Bear",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e1/Flag_bear_co.png/revision/latest?cb=20200820222626"
|
||||
},
|
||||
{
|
||||
name: "Bohemia Interactive",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ee/Flag_bi_co.png/revision/latest?cb=20200820222627"
|
||||
},
|
||||
{
|
||||
name: "Brain",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/7/7d/Flag_brain_co.png/revision/latest?cb=20200820222628"
|
||||
},
|
||||
{
|
||||
name: "Chernarussian Defence Forces (CDF)",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/d/d6/Flag_cdf_co.png/revision/latest?cb=20200820222629"
|
||||
},
|
||||
{
|
||||
name: "Chedaki (CHED)",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/96/Flag_ched_co.png/revision/latest?cb=20200820222630"
|
||||
},
|
||||
{
|
||||
name: "CHEL",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/98/Flag_chel_co.png/revision/latest?cb=20200820222631"
|
||||
},
|
||||
{
|
||||
name: "Chernarus",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ef/Flag_chern_co.png/revision/latest?cb=20200820222632"
|
||||
},
|
||||
{
|
||||
name: "Chernarus Mining Corporation (CMC)",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/d/da/Flag_cmc_co.png/revision/latest?cb=20200820222634"
|
||||
},
|
||||
{
|
||||
name: "Rooster",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/4/44/Flag_cock_co.png/revision/latest?cb=20200820222635"
|
||||
},
|
||||
{
|
||||
name: "DayZ",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b2/Flag_dayz_co.png/revision/latest?cb=20200820222636"
|
||||
},
|
||||
{
|
||||
name: "North Sahrani (DROS)",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/2/24/Flag_dros_co.png/revision/latest?cb=20200820222637"
|
||||
},
|
||||
{
|
||||
name: "Fawn",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/d/d2/Flag_fawn_co.png/revision/latest/scale-to-width-down/1000?cb=20200820222639"
|
||||
},
|
||||
{
|
||||
name: "Pirates",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/ab/Flag_jolly_co.png/revision/latest?cb=20200820222643"
|
||||
},
|
||||
{
|
||||
name: "Cannibals",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/4/42/Flag_jolly_c_co.png/revision/latest?cb=20200820222641"
|
||||
},
|
||||
{
|
||||
name: "South Sahrani (KOS)",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a1/Flag_kos_co.png/revision/latest?cb=20200820222644"
|
||||
},
|
||||
{
|
||||
name: "Livonia Army (LDF)",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c1/Flag_ldf_co.png/revision/latest?cb=20200820222645"
|
||||
},
|
||||
{
|
||||
name: "Livonia",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e6/Flag_livo_co.png/revision/latest?cb=20200820222647"
|
||||
},
|
||||
{
|
||||
name: "NAPA",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e4/Flag_napa_co.png/revision/latest?cb=20200820222648"
|
||||
},
|
||||
{
|
||||
name: "Livonia Police",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/63/Flag_police_co.png/revision/latest?cb=20200820222649"
|
||||
},
|
||||
{
|
||||
name: "TEC",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ea/Flag_tec_co.png/revision/latest?cb=20200820222650"
|
||||
},
|
||||
{
|
||||
name: "United Earth Coalition (UEC)",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/ca/Flag_uec_co.png/revision/latest?cb=20200820222651"
|
||||
},
|
||||
{
|
||||
name: "Wolf",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b2/Flag_wolf_co.png/revision/latest?cb=20200820222653"
|
||||
},
|
||||
{
|
||||
name: "Zenit Radio Station",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/0/05/Flag_zenit_co.png/revision/latest?cb=20200820222654"
|
||||
},
|
||||
{
|
||||
name: "Zombie Hunters",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/97/Flag_zhunters_co.png/revision/latest?cb=20200820222621"
|
||||
},
|
||||
{
|
||||
name: "RSTA",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/2/20/Flag_rsta_co.png/revision/latest/scale-to-width-down/1000?cb=20210216191221"
|
||||
},
|
||||
{
|
||||
name: "Refuge",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/8e/Flag_refuge_co.png/revision/latest/scale-to-width-down/1000?cb=20210216191205"
|
||||
},
|
||||
{
|
||||
name: "Snake",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/5/54/Flag_snake_co.png/revision/latest/scale-to-width-down/1000?cb=20210216191234"
|
||||
},
|
||||
{
|
||||
name: "Zagorky",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/7/75/Flag_zagorky_co.png/revision/latest/scale-to-width-down/1000?cb=20230619164704"
|
||||
},
|
||||
{
|
||||
name: "Crook",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c8/Flag_crook_co.png/revision/latest/scale-to-width-down/1000?cb=20230619164705"
|
||||
},
|
||||
{
|
||||
name: "Rex",
|
||||
url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c5/Flag_rex_co.png/revision/latest/scale-to-width-down/1000?cb=20230619164706"
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
export enum ArmbandName
|
||||
{
|
||||
Black = "Black",
|
||||
Blue = "Blue",
|
||||
Green = "Green",
|
||||
Orange = "Orange",
|
||||
Pink = "Pink",
|
||||
Red = "Red",
|
||||
Yellow = "Yellow",
|
||||
White = "White",
|
||||
Altis = "Altis",
|
||||
AsiainPacificAllianceAPA = "Asiain Pacific Alliance (APA)",
|
||||
Bear = "Bear",
|
||||
BohemiaInteractive = "Bohemia Interactive",
|
||||
Brain = "Brain",
|
||||
ChernarussianDefenceForcesCDF = "Chernarussian Defence Forces (CDF)",
|
||||
ChedakiCHED = "Chedaki (CHED)",
|
||||
CHEL = "CHEL",
|
||||
Chernarus = "Chernarus",
|
||||
ChernarusMiningCorporationCMC = "Chernarus Mining Corporation (CMC)",
|
||||
Rooster = "Rooster",
|
||||
DayZ = "DayZ",
|
||||
NorthSahraniDROS = "North Sahrani (DROS)",
|
||||
Fawn = "Fawn",
|
||||
Pirates = "Pirates",
|
||||
Cannibals = "Cannibals",
|
||||
SouthSahraniKOS = "South Sahrani (KOS)",
|
||||
LivoniaArmyLDF = "Livonia Army (LDF)",
|
||||
Livonia = "Livonia",
|
||||
NAPA = "NAPA",
|
||||
LivoniaPolice = "Livonia Police",
|
||||
TEC = "TEC",
|
||||
UnitedEarthCoalitionUEC = "United Earth Coalition (UEC)",
|
||||
Wolf = "Wolf",
|
||||
ZenitRadioStation = "Zenit Radio Station",
|
||||
ZombieHunters = "Zombie Hunters",
|
||||
RSTA = "RSTA",
|
||||
Refuge = "Refuge",
|
||||
Snake = "Snake",
|
||||
Zagorky = "Zagorky",
|
||||
Crook = "Crook",
|
||||
Rex = "Rex",
|
||||
};
|
||||
|
||||
export interface Armband
|
||||
{
|
||||
name: ArmbandName;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const Armbands: Armband[] =
|
||||
[
|
||||
{ name: ArmbandName.Black, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/82/ArmbandBlack.png/revision/latest?cb=20161127174754" },
|
||||
{ name: ArmbandName.Blue, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/bd/ArmbandBlue.png/revision/latest?cb=20161127174803" },
|
||||
{ name: ArmbandName.Green, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/ce/ArmbandGreen.png/revision/latest?cb=20161127174812" },
|
||||
{ name: ArmbandName.Orange, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e6/ArmbandOrange.png/revision/latest?cb=20161127174846" },
|
||||
{ name: ArmbandName.Pink, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/f7/ArmbandPink.png/revision/latest?cb=20161127174854" },
|
||||
{ name: ArmbandName.Red, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/1/14/Armband.png/revision/latest?cb=20161127174901" },
|
||||
{ name: ArmbandName.Yellow, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/81/ArmbandYellow.png/revision/latest?cb=20161127174918" },
|
||||
{ name: ArmbandName.White, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c7/Armband_White.png/revision/latest?cb=20161127174926" },
|
||||
{ name: ArmbandName.Altis, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ee/Flag_alti_co.png/revision/latest?cb=20200820222622" },
|
||||
{ name: ArmbandName.AsiainPacificAllianceAPA, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c2/Flag_apa_co.png/revision/latest?cb=20200820222623" },
|
||||
{ name: ArmbandName.Bear, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e1/Flag_bear_co.png/revision/latest?cb=20200820222626" },
|
||||
{ name: ArmbandName.BohemiaInteractive, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ee/Flag_bi_co.png/revision/latest?cb=20200820222627" },
|
||||
{ name: ArmbandName.Brain, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/7/7d/Flag_brain_co.png/revision/latest?cb=20200820222628" },
|
||||
{ name: ArmbandName.ChernarussianDefenceForcesCDF, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/d/d6/Flag_cdf_co.png/revision/latest?cb=20200820222629" },
|
||||
{ name: ArmbandName.ChedakiCHED, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/96/Flag_ched_co.png/revision/latest?cb=20200820222630" },
|
||||
{ name: ArmbandName.CHEL, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/98/Flag_chel_co.png/revision/latest?cb=20200820222631" },
|
||||
{ name: ArmbandName.Chernarus, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ef/Flag_chern_co.png/revision/latest?cb=20200820222632" },
|
||||
{ name: ArmbandName.ChernarusMiningCorporationCMC, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/d/da/Flag_cmc_co.png/revision/latest?cb=20200820222634" },
|
||||
{ name: ArmbandName.Rooster, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/4/44/Flag_cock_co.png/revision/latest?cb=20200820222635" },
|
||||
{ name: ArmbandName.DayZ, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b2/Flag_dayz_co.png/revision/latest?cb=20200820222636" },
|
||||
{ name: ArmbandName.NorthSahraniDROS, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/2/24/Flag_dros_co.png/revision/latest?cb=20200820222637" },
|
||||
{ name: ArmbandName.Fawn, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/d/d2/Flag_fawn_co.png/revision/latest/scale-to-width-down/1000?cb=20200820222639" },
|
||||
{ name: ArmbandName.Pirates, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/ab/Flag_jolly_co.png/revision/latest?cb=20200820222643" },
|
||||
{ name: ArmbandName.Cannibals, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/4/42/Flag_jolly_c_co.png/revision/latest?cb=20200820222641" },
|
||||
{ name: ArmbandName.SouthSahraniKOS, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a1/Flag_kos_co.png/revision/latest?cb=20200820222644" },
|
||||
{ name: ArmbandName.LivoniaArmyLDF, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c1/Flag_ldf_co.png/revision/latest?cb=20200820222645" },
|
||||
{ name: ArmbandName.Livonia, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e6/Flag_livo_co.png/revision/latest?cb=20200820222647" },
|
||||
{ name: ArmbandName.NAPA, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e4/Flag_napa_co.png/revision/latest?cb=20200820222648" },
|
||||
{ name: ArmbandName.LivoniaPolice, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/63/Flag_police_co.png/revision/latest?cb=20200820222649" },
|
||||
{ name: ArmbandName.TEC, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ea/Flag_tec_co.png/revision/latest?cb=20200820222650" },
|
||||
{ name: ArmbandName.UnitedEarthCoalitionUEC, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/ca/Flag_uec_co.png/revision/latest?cb=20200820222651" },
|
||||
{ name: ArmbandName.Wolf, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b2/Flag_wolf_co.png/revision/latest?cb=20200820222653" },
|
||||
{ name: ArmbandName.ZenitRadioStation, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/0/05/Flag_zenit_co.png/revision/latest?cb=20200820222654" },
|
||||
{ name: ArmbandName.ZombieHunters, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/97/Flag_zhunters_co.png/revision/latest?cb=20200820222621" },
|
||||
{ name: ArmbandName.RSTA, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/2/20/Flag_rsta_co.png/revision/latest/scale-to-width-down/1000?cb=20210216191221" },
|
||||
{ name: ArmbandName.Refuge, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/8e/Flag_refuge_co.png/revision/latest/scale-to-width-down/1000?cb=20210216191205" },
|
||||
{ name: ArmbandName.Snake, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/5/54/Flag_snake_co.png/revision/latest/scale-to-width-down/1000?cb=20210216191234" },
|
||||
{ name: ArmbandName.Zagorky, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/7/75/Flag_zagorky_co.png/revision/latest/scale-to-width-down/1000?cb=20230619164704" },
|
||||
{ name: ArmbandName.Crook, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c8/Flag_crook_co.png/revision/latest/scale-to-width-down/1000?cb=20230619164705" },
|
||||
{ name: ArmbandName.Rex, url: "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c5/Flag_rex_co.png/revision/latest/scale-to-width-down/1000?cb=20230619164706" },
|
||||
];
|
||||
@@ -1,6 +1,7 @@
|
||||
import { calculateVector } from "@util/Vector";
|
||||
import { calculateVector } from "../util/Vector";
|
||||
|
||||
export const Missions = {
|
||||
export const Missions =
|
||||
{
|
||||
"dayzOffline.chernarusplus": "Chernarus",
|
||||
"dayzOffline.enoch": "Livonia",
|
||||
"dayzOffline.sakhal": "Sakhal",
|
||||
@@ -9,10 +10,11 @@ export const Missions = {
|
||||
export type MissionKey = keyof typeof Missions;
|
||||
export type MissionName = typeof Missions[MissionKey];
|
||||
|
||||
export type Position = [number, number];
|
||||
export type Position = [number, number] | [];
|
||||
|
||||
// Calculates the nearest location to a given coordinate
|
||||
export function nearest(pos: Position, mission: MissionName): string {
|
||||
export function nearest(pos: Position, mission: MissionName): string
|
||||
{
|
||||
let tempDest;
|
||||
let lastDist = 1000000;
|
||||
let destination_dir;
|
||||
@@ -28,13 +30,15 @@ export function nearest(pos: Position, mission: MissionName): string {
|
||||
return lastDist > 500 ? `${destination_dir} of ${tempDest}` : `Near ${tempDest}`;
|
||||
};
|
||||
|
||||
interface Destination {
|
||||
interface Destination
|
||||
{
|
||||
name: string;
|
||||
coord: Position;
|
||||
}
|
||||
|
||||
// A curated list of destinations across DayZ Chernarus and Livonia
|
||||
const destinations: Record<MissionName, Array<Destination>> = {
|
||||
const destinations: Record<MissionName, Array<Destination>> =
|
||||
{
|
||||
Chernarus: [
|
||||
{
|
||||
name: "Sinystok",
|
||||
|
||||
+36
-20
@@ -1,29 +1,43 @@
|
||||
import { Snowflake } from "discord.js";
|
||||
import DayZR from "../DayZRBot";
|
||||
import { NitradoCredentialStatus } from "@util/NitradoAPI";
|
||||
import { NitradoCredentialStatus } from "../util/NitradoAPI";
|
||||
import { ArmbandName } from "./armbands";
|
||||
import { MissionName } from "./destinations";
|
||||
|
||||
export const enum IntegerBoolean {
|
||||
export const enum IntegerBoolean
|
||||
{
|
||||
FALSE,
|
||||
TRUE
|
||||
};
|
||||
|
||||
export interface UAV {
|
||||
export interface UAV
|
||||
{
|
||||
// TODO: fill this out
|
||||
};
|
||||
|
||||
export interface Alarm {
|
||||
export interface Alarm
|
||||
{
|
||||
// TODO: fill this out
|
||||
};
|
||||
|
||||
export interface NitradoConfig {
|
||||
export interface NitradoConfig
|
||||
{
|
||||
ServerID: string;
|
||||
UserID: string;
|
||||
Auth: string;
|
||||
Status: NitradoCredentialStatus;
|
||||
Mission: MissionName,
|
||||
};
|
||||
|
||||
interface GuildConfigAttributes {
|
||||
serverID: Snowflake;
|
||||
export interface FactionArmband
|
||||
{
|
||||
faction: Snowflake; // faction role ID
|
||||
armband: ArmbandName;
|
||||
}
|
||||
|
||||
interface GuildConfigAttributes
|
||||
{
|
||||
serverID: Snowflake; // Guild ID
|
||||
lastLog: string | null;
|
||||
serverName: string;
|
||||
autoRestart: IntegerBoolean;
|
||||
@@ -31,40 +45,41 @@ interface GuildConfigAttributes {
|
||||
showKillfeedWeapon: IntegerBoolean;
|
||||
purchaseUAV: IntegerBoolean;
|
||||
purchaseEMP: IntegerBoolean;
|
||||
allowedChannels: Array<Snowflake>;
|
||||
allowedChannels: Array<Snowflake>; // Array of channel IDs
|
||||
customChannelStatus?: boolean; // optional - generated outside of DB
|
||||
hasBotAdmin?: boolean; // optional - generated outside of DB
|
||||
|
||||
killfeedChannel: Snowflake;
|
||||
connectionLogsChannel: Snowflake;
|
||||
activePlayersChannel: Snowflake;
|
||||
welcomeChannel: Snowflake;
|
||||
killfeedChannel: Snowflake; // Channel ID
|
||||
connectionLogsChannel: Snowflake; // Channel ID
|
||||
activePlayersChannel: Snowflake; // Channel ID
|
||||
welcomeChannel: Snowflake; // Channel ID
|
||||
|
||||
factionArmbands: any;
|
||||
factionArmbands: Record<Snowflake, FactionArmband>;
|
||||
usedArmbands: Array<string>;
|
||||
excludedRoles: Array<string>;
|
||||
hasExcludedRoles?: boolean; // optional - generated outside of DB
|
||||
botAdminRoles: Array<Snowflake>;
|
||||
botAdminRoles: Array<Snowflake>; // Array of role IDs
|
||||
|
||||
alarms: Array<Alarm>;
|
||||
events: Array<any>;
|
||||
uavs: Array<UAV>;
|
||||
|
||||
incomeRoles: Array<Snowflake>;
|
||||
incomeRoles: Array<Snowflake>; // Array of role IDs
|
||||
incomeLimiter: number;
|
||||
|
||||
startingBalance: number;
|
||||
uavPrice: number;
|
||||
empPrice: number;
|
||||
|
||||
linkedGamertagRole: Snowflake;
|
||||
memberRole: Snowflake;
|
||||
adminRole: Snowflake;
|
||||
linkedGamertagRole: Snowflake; // Role ID
|
||||
memberRole: Snowflake; // Role ID
|
||||
adminRole: Snowflake; // Role ID
|
||||
|
||||
combatLogTimer: number;
|
||||
}
|
||||
|
||||
interface GuildConfigDB {
|
||||
interface GuildConfigDB
|
||||
{
|
||||
server: GuildConfigAttributes;
|
||||
Nitrado: NitradoConfig | null;
|
||||
};
|
||||
@@ -75,7 +90,8 @@ interface GuildConfigDB {
|
||||
// Also this is so stupid, in the DB all server attributes are under server,
|
||||
// while in the code we almost always have the attributes directly in top
|
||||
// level of object, i.e. guild.startingBalance instead of guild.server.startingBalance
|
||||
export interface GuildConfig extends GuildConfigAttributes {
|
||||
export interface GuildConfig extends GuildConfigAttributes
|
||||
{
|
||||
Nitrado: NitradoConfig | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
const { weapons } = require("./weapons");
|
||||
|
||||
// Creates a copy of an object to prevent mutation of parent (i.e BodyParts, createWeaponsObject)
|
||||
const copy = (obj) => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
const BodyParts = {
|
||||
Head: 0,
|
||||
Torso: 0,
|
||||
RightArm: 0,
|
||||
LeftArm: 0,
|
||||
RightLeg: 0,
|
||||
LeftLeg: 0,
|
||||
};
|
||||
|
||||
const createWeaponsObject = (value) => {
|
||||
const defaultWeapons = {};
|
||||
for (const [_, weaponNames] of Object.entries(weapons)) {
|
||||
for (const [name, _] of Object.entries(weaponNames)) {
|
||||
defaultWeapons[name] = value;
|
||||
}
|
||||
}
|
||||
return copy(defaultWeapons);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
UpdatePlayer: async (client, player, interaction = null) => {
|
||||
/* Wrapping this function in a promise solves some bugs */
|
||||
return new Promise(resolve => {
|
||||
client.dbo.collection("players").updateOne(
|
||||
{ "playerID": player.playerID },
|
||||
{ $set: { ...player } },
|
||||
{ upsert: true }, // Create player stat document if it does not exist
|
||||
(err, _) => {
|
||||
if (err) {
|
||||
if (interaction == null) return client.error(`UpdatePlayer Error: ${err}`);
|
||||
else return client.sendInternalError(interaction, `UpdatePlayer Error: ${err}`);
|
||||
} else resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
getDefaultPlayer(gamertag, playerId, nitradoServerId) {
|
||||
return {
|
||||
// Identifiers
|
||||
gamertag: gamertag,
|
||||
playerID: playerId,
|
||||
discordID: "",
|
||||
nitradoServerID: nitradoServerId,
|
||||
|
||||
// General PVP Stats
|
||||
KDR: 0.00,
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
killStreak: 0,
|
||||
bestKillStreak: 0,
|
||||
longestKill: 0,
|
||||
deathStreak: 0,
|
||||
worstDeathStreak: 0,
|
||||
|
||||
// In depth PVP Stats
|
||||
shotsLanded: 0,
|
||||
timesShot: 0,
|
||||
shotsLandedPerBodyPart: copy(BodyParts),
|
||||
timesShotPerBodyPart: copy(BodyParts),
|
||||
weaponStats: createWeaponsObject({
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
shotsLanded: 0,
|
||||
timesShot: 0,
|
||||
shotsLandedPerBodyPart: copy(BodyParts),
|
||||
timesShotPerBodyPart: copy(BodyParts),
|
||||
}),
|
||||
combatRating: 800,
|
||||
highestCombatRating: 800,
|
||||
lowestCombatRating: 800,
|
||||
combatRatingHistory: [800],
|
||||
|
||||
// General Session Data
|
||||
lastConnectionDate: null,
|
||||
lastDisconnectionDate: null,
|
||||
lastDamageDate: null,
|
||||
lastDeathDate: null,
|
||||
lastHitBy: null,
|
||||
connected: false,
|
||||
pos: [],
|
||||
lastPos: [],
|
||||
time: null,
|
||||
lastTime: null,
|
||||
|
||||
// Session Stats
|
||||
totalSessionTime: 0,
|
||||
lastSessionTime: 0,
|
||||
longestSessionTime: 0,
|
||||
connections: 0,
|
||||
|
||||
// Other
|
||||
bounties: [],
|
||||
bountiesLength: 0,
|
||||
}
|
||||
},
|
||||
|
||||
insertPVPstats(player) {
|
||||
player.shotsLanded = 0;
|
||||
player.timesShot = 0;
|
||||
player.shotsLandedPerBodyPart = copy(BodyParts);
|
||||
player.timesShotPerBodyPart = copy(BodyParts);
|
||||
player.weaponStats = createWeaponsObject({
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
shotsLanded: 0,
|
||||
timesShot: 0,
|
||||
shotsLandedPerBodyPart: copy(BodyParts),
|
||||
timesShotPerBodyPart: copy(BodyParts),
|
||||
});
|
||||
return player;
|
||||
},
|
||||
|
||||
// If a new weapon is not in the existing weaponStats, this will add it.
|
||||
createWeaponStats(player, weapon) {
|
||||
player.weaponStats[weapon] = {
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
shotsLanded: 0,
|
||||
timesShot: 0,
|
||||
shotsLandedPerBodyPart: copy(BodyParts),
|
||||
timesShotPerBodyPart: copy(BodyParts),
|
||||
}
|
||||
return player;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import { ChatInputCommandInteraction } from "discord.js";
|
||||
import { weapons, AllWeapons } from "./weapons";
|
||||
import { Snowflake } from "discord.js";
|
||||
import { Position } from "./destinations"
|
||||
import DayZR from "../DayZRBot";
|
||||
|
||||
// Creates a copy of an object to prevent mutation of parent (i.e BodyParts, createWeaponsObject)
|
||||
const copy = <T>(obj: T): T => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
interface BodyPartStats
|
||||
{
|
||||
Head: number;
|
||||
Torso: number;
|
||||
RightArm: number;
|
||||
LeftArm: number;
|
||||
RightLeg: number;
|
||||
LeftLeg: number;
|
||||
}
|
||||
|
||||
interface IndividualWeaponStats
|
||||
{
|
||||
kills: number;
|
||||
deaths: number;
|
||||
shotsLanded: number;
|
||||
timesShot: number;
|
||||
shotsLandedPerBodyPart: BodyPartStats;
|
||||
timesShotPerBodyPart: BodyPartStats;
|
||||
}
|
||||
|
||||
const DefaultBodyPartStats: BodyPartStats =
|
||||
{
|
||||
Head: 0,
|
||||
Torso: 0,
|
||||
RightArm: 0,
|
||||
LeftArm: 0,
|
||||
RightLeg: 0,
|
||||
LeftLeg: 0,
|
||||
};
|
||||
|
||||
interface WeaponStats
|
||||
{
|
||||
[weaponName: string]: IndividualWeaponStats;
|
||||
}
|
||||
|
||||
const createWeaponsObject = (defaultWeaponStats: IndividualWeaponStats): WeaponStats =>
|
||||
{
|
||||
const defaultWeapons: WeaponStats = {};
|
||||
for (const [_, weaponNames] of Object.entries(weapons)) {
|
||||
for (const [name, _] of Object.entries(weaponNames)) {
|
||||
defaultWeapons[name] = defaultWeaponStats;
|
||||
}
|
||||
}
|
||||
return copy(defaultWeapons);
|
||||
};
|
||||
|
||||
export interface Player
|
||||
{
|
||||
// Identifiers
|
||||
gamertag: string,
|
||||
playerID: string,
|
||||
discordID: Snowflake,
|
||||
nitradoServerID: string,
|
||||
|
||||
// General PVP Stats
|
||||
KDR: number,
|
||||
kills: number,
|
||||
deaths: number,
|
||||
killStreak: number,
|
||||
bestKillStreak: number,
|
||||
longestKill: number,
|
||||
deathStreak: number,
|
||||
worstDeathStreak: number,
|
||||
|
||||
// In depth PVP Stats
|
||||
shotsLanded: number,
|
||||
timesShot: number,
|
||||
shotsLandedPerBodyPart: BodyPartStats,
|
||||
timesShotPerBodyPart: BodyPartStats,
|
||||
weaponStats: WeaponStats,
|
||||
combatRating: number,
|
||||
highestCombatRating: number,
|
||||
lowestCombatRating: number,
|
||||
combatRatingHistory: Array<number>,
|
||||
|
||||
// General Session Data
|
||||
lastConnectionDate: Date | null,
|
||||
lastDisconnectionDate: Date | null,
|
||||
lastDamageDate: Date | null,
|
||||
lastDeathDate: Date | null,
|
||||
lastHitBy: string | null,
|
||||
connected: boolean,
|
||||
pos: Position,
|
||||
lastPos: Position,
|
||||
time: string | null,
|
||||
lastTime: string | null,
|
||||
|
||||
// Session Stats
|
||||
totalSessionTime: number,
|
||||
lastSessionTime: number,
|
||||
longestSessionTime: number,
|
||||
connections: number,
|
||||
|
||||
// Other
|
||||
bounties: Array<any>, // TODO, define bounty object
|
||||
bountiesLength: number,
|
||||
}
|
||||
|
||||
export const UpdatePlayer = async(
|
||||
client: DayZR,
|
||||
player: Player,
|
||||
interaction: ChatInputCommandInteraction | null = null
|
||||
): Promise<void> =>
|
||||
{
|
||||
/* Wrapping this function in a promise solves some bugs */
|
||||
return new Promise(resolve => {
|
||||
client.dbo.collection("players").updateOne(
|
||||
{ "playerID": player.playerID },
|
||||
{ $set: { ...player } },
|
||||
{ upsert: true }, // Create player stat document if it does not exist
|
||||
(err: string) => {
|
||||
if (err)
|
||||
{
|
||||
if (interaction == null) return client.error(`UpdatePlayer Error: ${err}`);
|
||||
else return client.sendInternalError(interaction, `UpdatePlayer Error: ${err}`);
|
||||
} else resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: fix magic NUMBERS!!!
|
||||
export const getDefaultPlayer = (
|
||||
gamertag: string,
|
||||
playerId: string,
|
||||
nitradoServerId: string
|
||||
): Player =>
|
||||
{
|
||||
return {
|
||||
// Identifiers
|
||||
gamertag: gamertag,
|
||||
playerID: playerId,
|
||||
discordID: "",
|
||||
nitradoServerID: nitradoServerId,
|
||||
|
||||
// General PVP Stats
|
||||
KDR: 0.00,
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
killStreak: 0,
|
||||
bestKillStreak: 0,
|
||||
longestKill: 0,
|
||||
deathStreak: 0,
|
||||
worstDeathStreak: 0,
|
||||
|
||||
// In depth PVP Stats
|
||||
shotsLanded: 0,
|
||||
timesShot: 0,
|
||||
shotsLandedPerBodyPart: copy(DefaultBodyPartStats),
|
||||
timesShotPerBodyPart: copy(DefaultBodyPartStats),
|
||||
weaponStats: createWeaponsObject({
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
shotsLanded: 0,
|
||||
timesShot: 0,
|
||||
shotsLandedPerBodyPart: copy(DefaultBodyPartStats),
|
||||
timesShotPerBodyPart: copy(DefaultBodyPartStats),
|
||||
}),
|
||||
combatRating: 800,
|
||||
highestCombatRating: 800,
|
||||
lowestCombatRating: 800,
|
||||
combatRatingHistory: [ 800 ],
|
||||
|
||||
// General Session Data
|
||||
lastConnectionDate: null,
|
||||
lastDisconnectionDate: null,
|
||||
lastDamageDate: null,
|
||||
lastDeathDate: null,
|
||||
lastHitBy: null,
|
||||
connected: false,
|
||||
pos: [],
|
||||
lastPos: [],
|
||||
time: null,
|
||||
lastTime: null,
|
||||
|
||||
// Session Stats
|
||||
totalSessionTime: 0,
|
||||
lastSessionTime: 0,
|
||||
longestSessionTime: 0,
|
||||
connections: 0,
|
||||
|
||||
// Other
|
||||
bounties: [],
|
||||
bountiesLength: 0,
|
||||
}
|
||||
};
|
||||
|
||||
export const insertPVPstats = (player: Player): Player =>
|
||||
{
|
||||
player.shotsLanded = 0;
|
||||
player.timesShot = 0;
|
||||
player.shotsLandedPerBodyPart = copy(DefaultBodyPartStats);
|
||||
player.timesShotPerBodyPart = copy(DefaultBodyPartStats);
|
||||
player.weaponStats = createWeaponsObject(
|
||||
{
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
shotsLanded: 0,
|
||||
timesShot: 0,
|
||||
shotsLandedPerBodyPart: copy(DefaultBodyPartStats),
|
||||
timesShotPerBodyPart: copy(DefaultBodyPartStats),
|
||||
});
|
||||
|
||||
return player;
|
||||
};
|
||||
|
||||
// If a new weapon is not in the existing weaponStats, this will add it.
|
||||
export const createWeaponStats = (
|
||||
player: Player,
|
||||
weapon: AllWeapons
|
||||
): Player =>
|
||||
{
|
||||
player.weaponStats[weapon] = {
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
shotsLanded: 0,
|
||||
timesShot: 0,
|
||||
shotsLandedPerBodyPart: copy(DefaultBodyPartStats),
|
||||
timesShotPerBodyPart: copy(DefaultBodyPartStats),
|
||||
}
|
||||
|
||||
return player;
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
module.exports = {
|
||||
weapons: {
|
||||
handguns: {
|
||||
"CR-75": "https://static.wikia.nocookie.net/dayz_gamepedia/images/4/40/CZ75.png/revision/latest/scale-to-width-down/112?cb=20210505021307",
|
||||
"Deagle": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/62/Deagle.png/revision/latest/scale-to-width-down/127?cb=20210512003023",
|
||||
"Derringer": "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/9f/Derringer_Black.png/revision/latest/scale-to-width-down/105?cb=20220521175445",
|
||||
"FX-45": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/fd/FNX45.png/revision/latest/scale-to-width-down/104?cb=20210505025055",
|
||||
"IJ-70": "https://static.wikia.nocookie.net/dayz_gamepedia/images/2/26/MakarovIJ70.png/revision/latest/scale-to-width-down/92?cb=20210209000551",
|
||||
"Kolt 1911": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/f9/Colt1911.png/revision/latest/scale-to-width-down/112?cb=20210505030200",
|
||||
"Longhorn": "https://static.wikia.nocookie.net/dayz_gamepedia/images/7/79/Longhorn.png/revision/latest/scale-to-width-down/222?cb=20220324214533",
|
||||
"MK II": "https://static.wikia.nocookie.net/dayz_gamepedia/images/0/0d/MKII.png/revision/latest/scale-to-width-down/171?cb=20210210153348",
|
||||
"Mlock-91": "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/9b/Glock19.png/revision/latest/scale-to-width-down/121?cb=20210505024259",
|
||||
"P1": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/cc/P1.png/revision/latest/scale-to-width-down/120?cb=20220518204515",
|
||||
"Revolver": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/6d/Revolver.png/revision/latest/scale-to-width-down/148?cb=20210208232303",
|
||||
"Signal Pistol": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a7/Flaregun.png/revision/latest/scale-to-width-down/107?cb=20210501150913",
|
||||
},
|
||||
shotguns: {
|
||||
"BK-12": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/cb/Izh18Shotgun.png/revision/latest/scale-to-width-down/256?cb=20220922184507",
|
||||
"BK-133": "https://static.wikia.nocookie.net/dayz_gamepedia/images/5/5c/MP-133-Shotgun.png/revision/latest/scale-to-width-down/256?cb=20210210190104",
|
||||
"BK-43": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c7/Izh43Shotgun.png/revision/latest/scale-to-width-down/256?cb=20210210185835",
|
||||
"Vaiga": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c8/Vaiga.png/revision/latest/scale-to-width-down/256?cb=20220220185225",
|
||||
},
|
||||
subMachineGuns: {
|
||||
"Bizon": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/af/PP19.png/revision/latest/scale-to-width-down/251?cb=20220127132305",
|
||||
"CR-61 Skorpion": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/63/VZ61Scorpion.png/revision/latest/scale-to-width-down/222?cb=20220518204508",
|
||||
"SG5-K": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/fc/MP5-K.png/revision/latest/scale-to-width-down/158?cb=20220221011343",
|
||||
"USG-45": "https://static.wikia.nocookie.net/dayz_gamepedia/images/d/d7/UMP45.png/revision/latest/scale-to-width-down/153?cb=20220221002354",
|
||||
},
|
||||
assaultRifles: {
|
||||
"AUR A1": "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e6/AugShort.png/revision/latest/scale-to-width-down/173?cb=20211104175243",
|
||||
"AUR AX": "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/be/Aug.png/revision/latest/scale-to-width-down/233?cb=20211104182427",
|
||||
"KA-101": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/f2/AK101.png/revision/latest/scale-to-width-down/251?cb=20210207040122",
|
||||
"KA-74": "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/8b/AK74.png/revision/latest/scale-to-width-down/253?cb=20210505013141",
|
||||
"KAS-74U": "https://static.wikia.nocookie.net/dayz_gamepedia/images/0/0b/AKS74U.png/revision/latest/scale-to-width-down/191?cb=20210505014222",
|
||||
"KA-M": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/6c/AKM.png/revision/latest/scale-to-width-down/244?cb=20210505011614",
|
||||
"LE-MAS": "https://static.wikia.nocookie.net/dayz_gamepedia/images/2/21/FAMAS.png/revision/latest/scale-to-width-down/197?cb=20210902183114",
|
||||
"M16-A2": "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b3/M16-A2.png/revision/latest/scale-to-width-down/256?cb=20220221002601",
|
||||
"M4-A1": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a1/M4A1.png/revision/latest/scale-to-width-down/223?cb=20220330014851",
|
||||
"SVAL": "https://static.wikia.nocookie.net/dayz_gamepedia/images/3/39/ASVAL.png/revision/latest/scale-to-width-down/256?cb=20210208015731",
|
||||
"Vikhr": "https://static.wikia.nocookie.net/dayz_gamepedia/images/0/0d/Vikhr.png/revision/latest/scale-to-width-down/173?cb=20240116163108"
|
||||
},
|
||||
battleRifles: {
|
||||
"LAR": "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e9/FAL.png/revision/latest/scale-to-width-down/256?cb=20220221001123",
|
||||
},
|
||||
boltActionRifles: {
|
||||
"CR-527": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/f0/CR527Wood.png/revision/latest/scale-to-width-down/256?cb=20220518204503 ",
|
||||
"CR-550 Savanna": "https://static.wikia.nocookie.net/dayz_gamepedia/images/4/44/CR-550_Savanna.png/revision/latest/scale-to-width-down/256?cb=20220518204410",
|
||||
"M70 Tundra": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/62/Winchester70.png/revision/latest/scale-to-width-down/256?cb=20220517152918",
|
||||
"Mosin 91/30": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a8/Mosin9130.png/revision/latest/scale-to-width-down/256?cb=20230126021955",
|
||||
"Pioneer": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/69/Scout.png/revision/latest/scale-to-width-down/256?cb=20220518204357",
|
||||
"SSG 82": "https://static.wikia.nocookie.net/dayz_gamepedia/images/1/10/SSG82.png/revision/latest/scale-to-width-down/256?cb=20220922192455",
|
||||
"VS-89": "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ea/SV98.png/revision/latest/scale-to-width-down/256?cb=20240424164607",
|
||||
},
|
||||
breakActionRifles: {
|
||||
"BK-18": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/cb/IZH18_Rifle.png/revision/latest/scale-to-width-down/256?cb=20220517154121",
|
||||
"Blaze": "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/8a/Blaze_95_Double_Rifle_Wood.png/revision/latest/scale-to-width-down/256?cb=20220517154129",
|
||||
},
|
||||
leverActionRifles: {
|
||||
"Repeater Carbine": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/ce/Repeater.png/revision/latest/scale-to-width-down/256?cb=20220517154151",
|
||||
},
|
||||
marksmanRifles: {
|
||||
"VSD": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a2/SVD_w._PSO-1.png/revision/latest/scale-to-width-down/256?cb=20220220235826",
|
||||
"VSS": "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/83/VSSVintorez.png/revision/latest/scale-to-width-down/256?cb=20210208202042",
|
||||
},
|
||||
semiAutomaticRifles: {
|
||||
"DMR": "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b4/M14.png/revision/latest/scale-to-width-down/350?cb=20231005142636",
|
||||
"SK 59/66": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/fe/SKS.png/revision/latest/scale-to-width-down/256?cb=20220517154633",
|
||||
"Sporter 22": "https://static.wikia.nocookie.net/dayz_gamepedia/images/5/5b/Sporter_22_Wood.png/revision/latest/scale-to-width-down/256?cb=20220518204154",
|
||||
},
|
||||
other: {
|
||||
"Crossbow": "https://static.wikia.nocookie.net/dayz_gamepedia/images/7/79/Crossbow.png/revision/latest/scale-to-width-down/212?cb=20180121164101",
|
||||
"M79": "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b7/M79.png/revision/latest/scale-to-width-down/256?cb=20220521184052",
|
||||
},
|
||||
},
|
||||
|
||||
weaponClassOf: (weapon) => Object.keys(module.exports.weapons).filter(c => weapon in module.exports.weapons[c])[0],
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
export type WeaponCategory =
|
||||
| "handguns"
|
||||
| "shotguns"
|
||||
| "subMachineGuns"
|
||||
| "assaultRifles"
|
||||
| "battleRifles"
|
||||
| "boltActionRifles"
|
||||
| "breakActionRifles"
|
||||
| "leverActionRifles"
|
||||
| "marksmanRifles"
|
||||
| "semiAutomaticRifles"
|
||||
| "other";
|
||||
|
||||
export type HandgunWeapon =
|
||||
| "CR-75" | "Deagle" | "Derringer" | "FX-45" | "IJ-70" | "Kolt 1911"
|
||||
| "Longhorn" | "MK II" | "Mlock-91" | "P1" | "Revolver" | "Signal Pistol";
|
||||
|
||||
export type ShotgunWeapon =
|
||||
| "BK-12" | "BK-133" | "BK-43" | "Vaiga";
|
||||
|
||||
export type SubMachineGunWeapon =
|
||||
| "Bizon" | "CR-61 Skorpion" | "SG5-K" | "USG-45";
|
||||
|
||||
export type AssaultRifleWeapon =
|
||||
| "AUR A1" | "AUR AX" | "KA-101" | "KA-74" | "KAS-74U" | "KA-M"
|
||||
| "LE-MAS" | "M16-A2" | "M4-A1" | "SVAL" | "Vikhr";
|
||||
|
||||
export type BattleRifleWeapon = "LAR";
|
||||
|
||||
export type BoltActionRifleWeapon =
|
||||
| "CR-527" | "CR-550 Savanna" | "M70 Tundra" | "Mosin 91/30"
|
||||
| "Pioneer" | "SSG 82" | "VS-89";
|
||||
|
||||
export type BreakActionRifleWeapon = "BK-18" | "Blaze";
|
||||
|
||||
export type LeverActionRifleWeapon = "Repeater Carbine";
|
||||
|
||||
export type MarksmanRifleWeapon = "VSD" | "VSS";
|
||||
|
||||
export type SemiAutomaticRifleWeapon = "DMR" | "SK 59/66" | "Sporter 22";
|
||||
|
||||
export type OtherWeapon = "Crossbow" | "M79";
|
||||
|
||||
export interface Weapons
|
||||
{
|
||||
handguns: Record<HandgunWeapon, string>;
|
||||
shotguns: Record<ShotgunWeapon, string>;
|
||||
subMachineGuns: Record<SubMachineGunWeapon, string>;
|
||||
assaultRifles: Record<AssaultRifleWeapon, string>;
|
||||
battleRifles: Record<BattleRifleWeapon, string>;
|
||||
boltActionRifles: Record<BoltActionRifleWeapon, string>;
|
||||
breakActionRifles: Record<BreakActionRifleWeapon, string>;
|
||||
leverActionRifles: Record<LeverActionRifleWeapon, string>;
|
||||
marksmanRifles: Record<MarksmanRifleWeapon, string>;
|
||||
semiAutomaticRifles: Record<SemiAutomaticRifleWeapon, string>;
|
||||
other: Record<OtherWeapon, string>;
|
||||
}
|
||||
|
||||
export type AllWeapons =
|
||||
| HandgunWeapon
|
||||
| ShotgunWeapon
|
||||
| SubMachineGunWeapon
|
||||
| AssaultRifleWeapon
|
||||
| BattleRifleWeapon
|
||||
| BoltActionRifleWeapon
|
||||
| BreakActionRifleWeapon
|
||||
| LeverActionRifleWeapon
|
||||
| MarksmanRifleWeapon
|
||||
| SemiAutomaticRifleWeapon
|
||||
| OtherWeapon;
|
||||
|
||||
export const weapons: Weapons =
|
||||
{
|
||||
handguns: {
|
||||
"CR-75": "https://static.wikia.nocookie.net/dayz_gamepedia/images/4/40/CZ75.png/revision/latest/scale-to-width-down/112?cb=20210505021307",
|
||||
"Deagle": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/62/Deagle.png/revision/latest/scale-to-width-down/127?cb=20210512003023",
|
||||
"Derringer": "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/9f/Derringer_Black.png/revision/latest/scale-to-width-down/105?cb=20220521175445",
|
||||
"FX-45": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/fd/FNX45.png/revision/latest/scale-to-width-down/104?cb=20210505025055",
|
||||
"IJ-70": "https://static.wikia.nocookie.net/dayz_gamepedia/images/2/26/MakarovIJ70.png/revision/latest/scale-to-width-down/92?cb=20210209000551",
|
||||
"Kolt 1911": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/f9/Colt1911.png/revision/latest/scale-to-width-down/112?cb=20210505030200",
|
||||
"Longhorn": "https://static.wikia.nocookie.net/dayz_gamepedia/images/7/79/Longhorn.png/revision/latest/scale-to-width-down/222?cb=20220324214533",
|
||||
"MK II": "https://static.wikia.nocookie.net/dayz_gamepedia/images/0/0d/MKII.png/revision/latest/scale-to-width-down/171?cb=20210210153348",
|
||||
"Mlock-91": "https://static.wikia.nocookie.net/dayz_gamepedia/images/9/9b/Glock19.png/revision/latest/scale-to-width-down/121?cb=20210505024259",
|
||||
"P1": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/cc/P1.png/revision/latest/scale-to-width-down/120?cb=20220518204515",
|
||||
"Revolver": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/6d/Revolver.png/revision/latest/scale-to-width-down/148?cb=20210208232303",
|
||||
"Signal Pistol": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a7/Flaregun.png/revision/latest/scale-to-width-down/107?cb=20210501150913",
|
||||
},
|
||||
shotguns: {
|
||||
"BK-12": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/cb/Izh18Shotgun.png/revision/latest/scale-to-width-down/256?cb=20220922184507",
|
||||
"BK-133": "https://static.wikia.nocookie.net/dayz_gamepedia/images/5/5c/MP-133-Shotgun.png/revision/latest/scale-to-width-down/256?cb=20210210190104",
|
||||
"BK-43": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c7/Izh43Shotgun.png/revision/latest/scale-to-width-down/256?cb=20210210185835",
|
||||
"Vaiga": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/c8/Vaiga.png/revision/latest/scale-to-width-down/256?cb=20220220185225",
|
||||
},
|
||||
subMachineGuns: {
|
||||
"Bizon": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/af/PP19.png/revision/latest/scale-to-width-down/251?cb=20220127132305",
|
||||
"CR-61 Skorpion": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/63/VZ61Scorpion.png/revision/latest/scale-to-width-down/222?cb=20220518204508",
|
||||
"SG5-K": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/fc/MP5-K.png/revision/latest/scale-to-width-down/158?cb=20220221011343",
|
||||
"USG-45": "https://static.wikia.nocookie.net/dayz_gamepedia/images/d/d7/UMP45.png/revision/latest/scale-to-width-down/153?cb=20220221002354",
|
||||
},
|
||||
assaultRifles: {
|
||||
"AUR A1": "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e6/AugShort.png/revision/latest/scale-to-width-down/173?cb=20211104175243",
|
||||
"AUR AX": "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/be/Aug.png/revision/latest/scale-to-width-down/233?cb=20211104182427",
|
||||
"KA-101": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/f2/AK101.png/revision/latest/scale-to-width-down/251?cb=20210207040122",
|
||||
"KA-74": "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/8b/AK74.png/revision/latest/scale-to-width-down/253?cb=20210505013141",
|
||||
"KAS-74U": "https://static.wikia.nocookie.net/dayz_gamepedia/images/0/0b/AKS74U.png/revision/latest/scale-to-width-down/191?cb=20210505014222",
|
||||
"KA-M": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/6c/AKM.png/revision/latest/scale-to-width-down/244?cb=20210505011614",
|
||||
"LE-MAS": "https://static.wikia.nocookie.net/dayz_gamepedia/images/2/21/FAMAS.png/revision/latest/scale-to-width-down/197?cb=20210902183114",
|
||||
"M16-A2": "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b3/M16-A2.png/revision/latest/scale-to-width-down/256?cb=20220221002601",
|
||||
"M4-A1": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a1/M4A1.png/revision/latest/scale-to-width-down/223?cb=20220330014851",
|
||||
"SVAL": "https://static.wikia.nocookie.net/dayz_gamepedia/images/3/39/ASVAL.png/revision/latest/scale-to-width-down/256?cb=20210208015731",
|
||||
"Vikhr": "https://static.wikia.nocookie.net/dayz_gamepedia/images/0/0d/Vikhr.png/revision/latest/scale-to-width-down/173?cb=20240116163108",
|
||||
},
|
||||
battleRifles: {
|
||||
"LAR": "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/e9/FAL.png/revision/latest/scale-to-width-down/256?cb=20220221001123",
|
||||
},
|
||||
boltActionRifles: {
|
||||
"CR-527": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/f0/CR527Wood.png/revision/latest/scale-to-width-down/256?cb=20220518204503",
|
||||
"CR-550 Savanna": "https://static.wikia.nocookie.net/dayz_gamepedia/images/4/44/CR-550_Savanna.png/revision/latest/scale-to-width-down/256?cb=20220518204410",
|
||||
"M70 Tundra": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/62/Winchester70.png/revision/latest/scale-to-width-down/256?cb=20220517152918",
|
||||
"Mosin 91/30": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a8/Mosin9130.png/revision/latest/scale-to-width-down/256?cb=20230126021955",
|
||||
"Pioneer": "https://static.wikia.nocookie.net/dayz_gamepedia/images/6/69/Scout.png/revision/latest/scale-to-width-down/256?cb=20220518204357",
|
||||
"SSG 82": "https://static.wikia.nocookie.net/dayz_gamepedia/images/1/10/SSG82.png/revision/latest/scale-to-width-down/256?cb=20220922192455",
|
||||
"VS-89": "https://static.wikia.nocookie.net/dayz_gamepedia/images/e/ea/SV98.png/revision/latest/scale-to-width-down/256?cb=20240424164607",
|
||||
},
|
||||
breakActionRifles: {
|
||||
"BK-18": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/cb/IZH18_Rifle.png/revision/latest/scale-to-width-down/256?cb=20220517154121",
|
||||
"Blaze": "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/8a/Blaze_95_Double_Rifle_Wood.png/revision/latest/scale-to-width-down/256?cb=20220517154129",
|
||||
},
|
||||
leverActionRifles: {
|
||||
"Repeater Carbine": "https://static.wikia.nocookie.net/dayz_gamepedia/images/c/ce/Repeater.png/revision/latest/scale-to-width-down/256?cb=20220517154151",
|
||||
},
|
||||
marksmanRifles: {
|
||||
"VSD": "https://static.wikia.nocookie.net/dayz_gamepedia/images/a/a2/SVD_w._PSO-1.png/revision/latest/scale-to-width-down/256?cb=20220220235826",
|
||||
"VSS": "https://static.wikia.nocookie.net/dayz_gamepedia/images/8/83/VSSVintorez.png/revision/latest/scale-to-width-down/256?cb=20210208202042",
|
||||
},
|
||||
semiAutomaticRifles: {
|
||||
"DMR": "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b4/M14.png/revision/latest/scale-to-width-down/350?cb=20231005142636",
|
||||
"SK 59/66": "https://static.wikia.nocookie.net/dayz_gamepedia/images/f/fe/SKS.png/revision/latest/scale-to-width-down/256?cb=20220517154633",
|
||||
"Sporter 22": "https://static.wikia.nocookie.net/dayz_gamepedia/images/5/5b/Sporter_22_Wood.png/revision/latest/scale-to-width-down/256?cb=20220518204154",
|
||||
},
|
||||
other: {
|
||||
"Crossbow": "https://static.wikia.nocookie.net/dayz_gamepedia/images/7/79/Crossbow.png/revision/latest/scale-to-width-down/212?cb=20180121164101",
|
||||
"M79": "https://static.wikia.nocookie.net/dayz_gamepedia/images/b/b7/M79.png/revision/latest/scale-to-width-down/256?cb=20220521184052",
|
||||
},
|
||||
};
|
||||
|
||||
export const weaponClassOf = (weapon: AllWeapons): WeaponCategory | undefined => {
|
||||
return (Object.keys(weapons) as WeaponCategory[]).find((category) => weapon in weapons[category]);
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { ShardingManager } from "discord.js";
|
||||
import config from "./config/config";
|
||||
import { config } from "./config/config";
|
||||
|
||||
// ShardingManager spawns node instances and we need to explicitely point to dist/bot.js
|
||||
const manager: ShardingManager = new ShardingManager("./dist/bot.js", { token: config.Token });
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
import { bgYellow } from "colors";
|
||||
import "discord.js";
|
||||
|
||||
/**
|
||||
* Type augmentation allows us to add custom functions to the
|
||||
* ChatInputCommandInteraction without typescript complaining
|
||||
*/
|
||||
declare module "discord.js" {
|
||||
interface ChatInputCommandInteraction {
|
||||
send(
|
||||
content: string | { embeds?: (EmbedBuilder | APIEmbed)[]; [key: string]: any }
|
||||
): Promise<unknown>;
|
||||
deferReply(
|
||||
content?: string | { embeds?: (EmbedBuilder | APIEmbed)[]; [key: string]: any }
|
||||
): Promise<unknown>;
|
||||
showModal(
|
||||
content: string | { embeds?: (EmbedBuilder | APIEmbed)[]; [key: string]: any }
|
||||
): Promise<unknown>;
|
||||
editReply(
|
||||
content: string | { embeds?: (EmbedBuilder | APIEmbed)[]; [key: string]: any }
|
||||
): Promise<unknown>;
|
||||
}
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import { Readable } from "stream";
|
||||
|
||||
// cannot get a warning to go away, this is like the only solution
|
||||
declare module "node:readline" {
|
||||
import { Interface, ReadLineOptions } from "readline";
|
||||
|
||||
interface ReadLineOptionsFixed extends ReadLineOptions {
|
||||
input: Readable; // 👈 force Node stream
|
||||
}
|
||||
|
||||
export function createInterface(options: ReadLineOptionsFixed): Interface;
|
||||
}
|
||||
@@ -1,40 +1,44 @@
|
||||
const winston = require("winston");
|
||||
const colors = require("colors");
|
||||
import winston from "winston";
|
||||
import colors from "colors";
|
||||
|
||||
class Logger {
|
||||
constructor(LoggingFile) {
|
||||
export default class Logger {
|
||||
|
||||
public logger: winston.Logger;
|
||||
|
||||
constructor(loggingFile: string)
|
||||
{
|
||||
this.logger = winston.createLogger({
|
||||
transports: [new winston.transports.File({ filename: LoggingFile })],
|
||||
transports: [new winston.transports.File({ filename: loggingFile })],
|
||||
});
|
||||
}
|
||||
|
||||
log(Text) {
|
||||
log(text: string): void
|
||||
{
|
||||
let d = new Date();
|
||||
this.logger.log({
|
||||
level: "info",
|
||||
message:
|
||||
`${d.getHours()}:${d.getMinutes()} - ${d.getMonth() + 1}:${d.getDate()}:${d.getFullYear()} | Info: ` + Text
|
||||
`${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)
|
||||
) + colors.yellow(" | Info: " + text)
|
||||
);
|
||||
}
|
||||
|
||||
error(Text) {
|
||||
error(text: string): void
|
||||
{
|
||||
let d = new Date();
|
||||
this.logger.log({
|
||||
level: "error",
|
||||
message:
|
||||
`${d.getHours()}:${d.getMinutes()} - ${d.getMonth() + 1}:${d.getDate()}:${d.getFullYear()} | Error: ` + Text
|
||||
`${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)
|
||||
) + colors.yellow(" | Error: ") + colors.red(text)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Logger;
|
||||
+7
-4
@@ -10,12 +10,15 @@
|
||||
"resolveJsonModule": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowJs": true,
|
||||
"baseUrl": "./src",
|
||||
"lib": ["es2020"],
|
||||
"types": ["node"],
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@util/*": ["util/*"],
|
||||
"@db/*": ["database/*"]
|
||||
"@util/*": ["src/util/*"],
|
||||
"@db/*": ["src/database/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "src/types"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in new issue
Block a user