diff --git a/.gitignore b/.gitignore
index 73b976c2..20b9b390 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ node_modules
.env
instances
credentials
+authtokens
maps
logs
temp
diff --git a/Dockerfile b/Dockerfile
index 11807d2b..43ffd97f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,6 +10,7 @@ RUN npm install
COPY . /app
VOLUME [ "/app/credentials" ]
+VOLUME [ "/app/authtokens" ]
VOLUME [ "/app/instances" ]
VOLUME [ "/app/logs" ]
VOLUME [ "/app/maps" ]
diff --git a/docker-compose.yml b/docker-compose.yml
index 5e234498..1c815f21 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,7 @@ services:
app:
volumes:
- ./credentials:/app/credentials
+ - ./authtokens:/app/authtokens
- ./instances:/app/instances
- ./logs:/app/logs
- ./maps:/app/maps
diff --git a/index.ts b/index.ts
index 01692754..7bd81ffc 100644
--- a/index.ts
+++ b/index.ts
@@ -53,6 +53,10 @@ function createMissingDirectories() {
Fs.mkdirSync(Path.join(__dirname, 'credentials'));
}
+ if (!Fs.existsSync(Path.join(__dirname, 'authtokens'))) {
+ Fs.mkdirSync(Path.join(__dirname, 'authtokens'));
+ }
+
if (!Fs.existsSync(Path.join(__dirname, 'maps'))) {
Fs.mkdirSync(Path.join(__dirname, 'maps'));
}
diff --git a/src/commands/authtoken.js b/src/commands/authtoken.js
new file mode 100644
index 00000000..124b23a0
--- /dev/null
+++ b/src/commands/authtoken.js
@@ -0,0 +1,286 @@
+/*
+ Copyright (C) 2024 Alexander Emanuelsson (alexemanuelol)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ https://github.com/alexemanuelol/rustplusplus
+
+*/
+
+const _ = require('lodash');
+const Builder = require('@discordjs/builders');
+
+const Config = require('../../config');
+const DiscordEmbeds = require('../discordTools/discordEmbeds.js');
+const DiscordMessages = require('../discordTools/discordMessages.js');
+const DiscordTools = require('../discordTools/discordTools.js');
+const InstanceUtils = require('../util/instanceUtils.js');
+const AuthTokenListenerTemp = require('../util/AuthTokenListener.js');
+
+module.exports = {
+ name: 'authtoken',
+
+ getData(client, guildId) {
+ return new Builder.SlashCommandBuilder()
+ .setName('authtoken')
+ .setDescription('Set/Remove Authentication Token.')
+ .addSubcommand(subcommand => subcommand
+ .setName('add')
+ .setDescription('Add Authentication Token.')
+ .addStringOption(option => option
+ .setName('token')
+ .setDescription('Authentication Token.')
+ .setRequired(true))
+ .addStringOption(option => option
+ .setName('steam_id')
+ .setDescription('Steam ID.')
+ .setRequired(true))
+ .addStringOption(option => option
+ .setName('issued_date')
+ .setDescription('Issued date of the Authentication Token.')
+ .setRequired(true))
+ .addStringOption(option => option
+ .setName('expire_date')
+ .setDescription('Expire date of the Authentication Token.')
+ .setRequired(true))
+ .addBooleanOption(option => option
+ .setName('hoster')
+ .setDescription('Host the bot')
+ .setRequired(false)))
+ .addSubcommand(subcommand => subcommand
+ .setName('remove')
+ .setDescription('Remove Authentication Token.')
+ .addStringOption(option => option
+ .setName('steam_id')
+ .setDescription('The SteamId of the Authentication Token to be removed.')
+ .setRequired(false)))
+ .addSubcommand(subcommand => subcommand
+ .setName('show')
+ .setDescription('Show the currently registered authentication token users.'))
+ .addSubcommand(subcommand => subcommand
+ .setName('set_hoster')
+ .setDescription('Set the main hoster.')
+ .addStringOption(option => option
+ .setName('steam_id')
+ .setDescription('The SteamId of the new hoster.')
+ .setRequired(false)));
+ },
+
+ async execute(client, interaction) {
+ const verifyId = Math.floor(100000 + Math.random() * 900000);
+ client.logInteraction(interaction, verifyId, 'slashCommand');
+
+ if (!await client.validatePermissions(interaction)) return;
+ await interaction.deferReply({ ephemeral: true });
+
+ switch (interaction.options.getSubcommand()) {
+ case 'add': {
+ addAuthToken(client, interaction, verifyId);
+ } break;
+
+ case 'remove': {
+ removeAuthToken(client, interaction, verifyId);
+ } break;
+
+ case 'show': {
+ showAuthTokenUsers(client, interaction, verifyId);
+ } break;
+
+ case 'set_hoster': {
+ setHoster(client, interaction, verifyId);
+ } break;
+
+ default: {
+ } break;
+ }
+ },
+};
+
+async function addAuthToken(client, interaction, verifyId) {
+ const guildId = interaction.guildId;
+ const authTokens = InstanceUtils.readAuthTokensFile(guildId);
+ const steamId = interaction.options.getString('steam_id');
+ const isHoster = interaction.options.getBoolean('host') || Object.keys(authTokens).length === 1;
+
+ if (Object.keys(authTokens) !== 1 && isHoster) {
+ if (Config.discord.needAdminPrivileges && !client.isAdministrator(interaction)) {
+ const str = client.intlGet(interaction.guildId, 'missingPermission');
+ client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(1, str));
+ client.log(client.intlGet(null, 'warningCap'), str);
+ return;
+ }
+ }
+
+ if (steamId in authTokens) {
+ const str = `Authentication Token for steamId: ${steamId} is already registered.`;
+ await client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(1, str));
+ client.log(client.intlGet(null, 'warningCap'), str);
+ return;
+ }
+
+ authTokens[steamId] = new Object();
+ authTokens[steamId].auth_token = interaction.options.getString('token');
+ authTokens[steamId].issued_date = interaction.options.getString('issued_date');
+ authTokens[steamId].expire_date = interaction.options.getString('expire_date');
+ authTokens[steamId].discordUserId = interaction.member.user.id;
+
+ if (isHoster) authTokens.hoster = steamId;
+
+ InstanceUtils.writeAuthTokensFile(guildId, authTokens);
+
+ await AuthTokenListenerTemp.startNewAuthTokenListener(client, interaction.guildId, steamId);
+ if (!isHoster) {
+ const rustplus = client.rustplusInstances[guildId];
+ if (rustplus && rustplus.team.leaderSteamId === steamId) {
+ rustplus.updateLeaderRustPlusLiteInstance();
+ }
+ }
+
+ client.log(client.intlGet(null, 'infoCap'), client.intlGet(null, 'slashCommandValueChange', {
+ id: `${verifyId}`,
+ value: `add, ${steamId}, ` +
+ `${authTokens[steamId].discordUserId}, ` +
+ `${isHoster}, ` +
+ `${authTokens[steamId].token}, ` +
+ `${authTokens[steamId].issued_date}, ` +
+ `${authTokens[steamId].expire_date}`
+ }));
+
+ const str = `Authentication Token were added successfully for steamId: ${steamId}.`
+ await client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(0, str));
+ client.log(client.intlGet(null, 'infoCap'), str);
+}
+
+async function removeAuthToken(client, interaction, verifyId) {
+ const guildId = interaction.guildId;
+ const authTokens = InstanceUtils.readAuthTokensFile(guildId);
+ let steamId = interaction.options.getString('steam_id');
+
+ if (steamId && (steamId in authTokens) && authTokens[steamId].discordUserId !== interaction.member.user.id) {
+ if (Config.discord.needAdminPrivileges && !client.isAdministrator(interaction)) {
+ const str = client.intlGet(interaction.guildId, 'missingPermission');
+ client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(1, str));
+ client.log(client.intlGet(null, 'warningCap'), str);
+ return;
+ }
+ }
+
+ if (!steamId) {
+ for (const authToken of Object.keys(authTokens)) {
+ if (authToken === 'hoster') continue;
+
+ if (authTokens[authToken].discordUserId === interaction.member.user.id) {
+ steamId = authToken;
+ break;
+ }
+ }
+ }
+
+ if (!(steamId in authTokens)) {
+ const str = `Authentication Token for steamId: ${steamId} does not exist.`;
+ await client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(1, str));
+ client.log(client.intlGet(null, 'warningCap'), str);
+ return;
+ }
+
+ if (!(client.authTokenListenerIntervalIds[guildId])) {
+ client.authTokenListenerIntervalIds[guildId] = new Object();
+ }
+
+ if (client.authTokenListenerIntervalIds[guildId] &&
+ client.authTokenListenerIntervalIds[guildId][steamId]) {
+ clearInterval(client.authTokenListenerIntervalIds[guildId][steamId]);
+ delete client.authTokenListenerIntervalIds[guildId][steamId];
+ }
+
+ if (client.authTokenReadNotifications[guildId] &&
+ client.authTokenReadNotifications[guildId][steamId]) {
+ delete client.authTokenReadNotifications[guildId][steamId];
+ }
+
+ if (steamId === authTokens.hoster) {
+ authTokens.hoster = null;
+ }
+
+ delete authTokens[steamId];
+ InstanceUtils.writeAuthTokensFile(guildId, authTokens);
+
+ client.log(client.intlGet(null, 'infoCap'), client.intlGet(null, 'slashCommandValueChange', {
+ id: `${verifyId}`,
+ value: `remove, ${steamId}`
+ }));
+
+ const str = `Authentication Token for steamId: ${steamId} was removed successfully.`;
+ await client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(0, str));
+ client.log(client.intlGet(null, 'infoCap'), str);
+}
+
+async function showAuthTokenUsers(client, interaction, verifyId) {
+ client.log(client.intlGet(null, 'infoCap'), client.intlGet(null, 'slashCommandValueChange', {
+ id: `${verifyId}`,
+ value: `show`
+ }));
+
+ await DiscordMessages.sendAuthTokensShowMessage(interaction);
+}
+
+async function setHoster(client, interaction, verifyId) {
+ const guildId = interaction.guildId;
+ const authTokens = InstanceUtils.readAuthTokensFile(guildId);
+ let steamId = interaction.options.getString('steam_id');
+
+ if (Config.discord.needAdminPrivileges && !client.isAdministrator(interaction)) {
+ const str = client.intlGet(interaction.guildId, 'missingPermission');
+ client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(1, str));
+ client.log(client.intlGet(null, 'warningCap'), str);
+ return;
+ }
+
+ if (!steamId) {
+ steamId = Object.keys(authTokens).find(e => authTokens[e] &&
+ authTokens[e].discordUserId === interaction.member.user.id);
+ }
+
+ if (!(steamId in authTokens)) {
+ const str = `Authentication Token for steamId: ${steamId} does not exist.`;
+ await client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(1, str));
+ client.log(client.intlGet(null, 'warningCap'), str);
+ return;
+ }
+
+ authTokens.hoster = steamId;
+ InstanceUtils.writeAuthTokensFile(guildId, authTokens);
+
+ const instance = client.getInstance(guildId);
+ const rustplus = client.rustplusInstances[guildId];
+ if (rustplus) {
+ instance.activeServer = null;
+ client.setInstance(guildId, instance);
+ client.resetRustplusVariables(guildId);
+ rustplus.disconnect();
+ delete client.rustplusInstances[guildId];
+ await DiscordMessages.sendServerMessage(guildId, rustplus.serverId);
+ }
+
+ await AuthTokenListenerTemp.startNewAuthTokenListener(client, interaction.guildId, steamId);
+
+ client.log(client.intlGet(null, 'infoCap'), client.intlGet(null, 'slashCommandValueChange', {
+ id: `${verifyId}`,
+ value: `setHoster, ${steamId}`
+ }));
+
+ const str = `Authentication Token hoster was successfully set to steamId: ${steamId}.`;
+ await client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(0, str));
+ client.log(client.intlGet(null, 'infoCap'), str);
+}
diff --git a/src/discordEvents/guildCreate.js b/src/discordEvents/guildCreate.js
index f3af936d..2f76bed4 100644
--- a/src/discordEvents/guildCreate.js
+++ b/src/discordEvents/guildCreate.js
@@ -23,6 +23,7 @@ module.exports = {
async execute(client, guild) {
require('../util/CreateInstanceFile')(client, guild);
require('../util/CreateCredentialsFile')(client, guild);
+ require('../util/CreateAuthTokensFile')(client, guild);
client.fcmListenersLite[guild.id] = new Object();
client.loadGuildIntl(guild.id);
diff --git a/src/discordEvents/guildMemberRemove.js b/src/discordEvents/guildMemberRemove.js
index 0eb7e49a..63df112d 100644
--- a/src/discordEvents/guildMemberRemove.js
+++ b/src/discordEvents/guildMemberRemove.js
@@ -26,27 +26,42 @@ module.exports = {
const guildId = member.guild.id;
const userId = member.user.id;
- const credentials = InstanceUtils.readCredentialsFile(guildId);
+ //const credentials = InstanceUtils.readCredentialsFile(guildId);
+ const authTokens = InstanceUtils.readAuthTokensFile(guildId);
- const steamId = Object.keys(credentials).find(e => credentials[e] && credentials[e].discordUserId === userId);
+ //const steamId = Object.keys(credentials).find(e => credentials[e] && credentials[e].discordUserId === userId);
+ const steamId = Object.keys(authTokens).find(e => authTokens[e] && authTokens[e].discordUserId === userId);
- if (!(steamId in credentials)) return;
+ //if (!(steamId in credentials)) return;
+ if (!(steamId in authTokens)) return;
- if (steamId === credentials.hoster) {
- if (client.fcmListeners[guildId]) {
- client.fcmListeners[guildId].destroy();
- }
- delete client.fcmListeners[guildId];
- credentials.hoster = null;
+ //if (steamId === credentials.hoster) {
+ // if (client.fcmListeners[guildId]) {
+ // client.fcmListeners[guildId].destroy();
+ // }
+ // delete client.fcmListeners[guildId];
+ // credentials.hoster = null;
+ //}
+ //else {
+ // if (client.fcmListenersLite[guildId][steamId]) {
+ // client.fcmListenersLite[guildId][steamId].destroy();
+ // }
+ // delete client.fcmListenersLite[guildId][steamId];
+ //}
+
+ if (client.authTokenListenerIntervalsIds[guildId] &&
+ client.authTokenListenerIntervalsIds[guildId][steamId]) {
+ clearInterval(client.authTokenListenerIntervalsIds[guildId][steamId]);
+ delete client.authTokenListenerIntervalsIds[guildId][steamId];
}
- else {
- if (client.fcmListenersLite[guildId][steamId]) {
- client.fcmListenersLite[guildId][steamId].destroy();
- }
- delete client.fcmListenersLite[guildId][steamId];
+
+ if (steamId === authTokens.hoster) {
+ authTokens.hoster = null;
}
- delete credentials[steamId];
- InstanceUtils.writeCredentialsFile(guildId, credentials);
+ //delete credentials[steamId];
+ delete authTokens[steamId];
+ //InstanceUtils.writeCredentialsFile(guildId, credentials);
+ InstanceUtils.writeAuthTokensFile(guildId, authTokens);
},
}
\ No newline at end of file
diff --git a/src/discordEvents/ready.js b/src/discordEvents/ready.js
index e46a3605..68b9ed58 100644
--- a/src/discordEvents/ready.js
+++ b/src/discordEvents/ready.js
@@ -31,6 +31,7 @@ module.exports = {
for (const guild of client.guilds.cache) {
require('../util/CreateInstanceFile')(client, guild[1]);
require('../util/CreateCredentialsFile')(client, guild[1]);
+ require('../util/CreateAuthTokensFile')(client, guild[1]);
client.fcmListenersLite[guild[0]] = new Object();
}
diff --git a/src/discordTools/discordEmbeds.js b/src/discordTools/discordEmbeds.js
index 3267ade3..cde0333a 100644
--- a/src/discordTools/discordEmbeds.js
+++ b/src/discordTools/discordEmbeds.js
@@ -66,11 +66,14 @@ module.exports = {
getServerEmbed: async function (guildId, serverId) {
const instance = Client.client.getInstance(guildId);
- const credentials = InstanceUtils.readCredentialsFile(guildId);
+ //const credentials = InstanceUtils.readCredentialsFile(guildId);
+ const authTokens = InstanceUtils.readAuthTokensFile(guildId);
const server = instance.serverList[serverId];
let hoster = Client.client.intlGet(guildId, 'unknown');
- if (credentials.hasOwnProperty(server.steamId)) {
- hoster = await DiscordTools.getUserById(guildId, credentials[server.steamId].discordUserId);
+ //if (credentials.hasOwnProperty(server.steamId)) {
+ // hoster = await DiscordTools.getUserById(guildId, credentials[server.steamId].discordUserId);
+ if (authTokens.hasOwnProperty(server.steamId)) {
+ hoster = await DiscordTools.getUserById(guildId, authTokens[server.steamId].discordUserId);
hoster = hoster.user.username;
}
@@ -1011,6 +1014,35 @@ module.exports = {
});
},
+ getAuthTokensShowEmbed: async function (guildId) {
+ const authTokens = InstanceUtils.readAuthTokensFile(guildId);
+ let names = '';
+ let steamIds = '';
+ let hoster = '';
+
+ for (const authToken in authTokens) {
+ if (authToken === 'hoster') continue;
+
+ const user = await DiscordTools.getUserById(guildId, authTokens[authToken].discordUserId);
+ names += `${user.user.username}\n`;
+ steamIds += `${authToken}\n`;
+ hoster += `${authToken === authTokens.hoster ? `${Constants.LEADER_EMOJI}\n` : '\u200B\n'}`;
+ }
+
+ if (names === '') names = Client.client.intlGet(guildId, 'empty');
+ if (steamIds === '') steamIds = Client.client.intlGet(guildId, 'empty');
+ if (hoster === '') hoster = Client.client.intlGet(guildId, 'empty');
+
+ return module.exports.getEmbed({
+ color: Constants.COLOR_DEFAULT,
+ title: Client.client.intlGet(guildId, 'fcmCredentials'),
+ fields: [
+ { name: Client.client.intlGet(guildId, 'name'), value: names, inline: true },
+ { name: 'SteamID', value: steamIds, inline: true },
+ { name: Client.client.intlGet(guildId, 'hoster'), value: hoster, inline: true }]
+ });
+ },
+
getItemAvailableVendingMachineEmbed: function (guildId, serverId, str) {
const instance = Client.client.getInstance(guildId);
const server = instance.serverList[serverId];
diff --git a/src/discordTools/discordMessages.js b/src/discordTools/discordMessages.js
index dcc2e0ee..bcc86bd3 100644
--- a/src/discordTools/discordMessages.js
+++ b/src/discordTools/discordMessages.js
@@ -502,6 +502,15 @@ module.exports = {
await Client.client.interactionEditReply(interaction, content);
},
+ sendAuthTokensShowMessage: async function (interaction) {
+ const content = {
+ embeds: [await DiscordEmbeds.getAuthTokensShowEmbed(interaction.guildId)],
+ ephemeral: true
+ }
+
+ await Client.client.interactionEditReply(interaction, content);
+ },
+
sendItemAvailableInVendingMachineMessage: async function (rustplus, str) {
const instance = Client.client.getInstance(rustplus.guildId);
diff --git a/src/structures/DiscordBot.js b/src/structures/DiscordBot.js
index 88f417e4..fd525fdb 100644
--- a/src/structures/DiscordBot.js
+++ b/src/structures/DiscordBot.js
@@ -34,6 +34,7 @@ const Logger = require('./Logger.js');
const PermissionHandler = require('../handlers/permissionHandler.js');
const RustLabs = require('../structures/RustLabs');
const RustPlus = require('../structures/RustPlus');
+const AuthTokenListenerTemp = require('../util/AuthTokenListener.js');
class DiscordBot extends Discord.Client {
constructor(props) {
@@ -44,6 +45,8 @@ class DiscordBot extends Discord.Client {
this.commands = new Discord.Collection();
this.fcmListeners = new Object();
this.fcmListenersLite = new Object();
+ this.authTokenListenerIntervalIds = new Object();
+ this.authTokenReadNotifications = new Object();
this.instances = {};
this.guildIntl = {};
this.botIntl = null;
@@ -226,12 +229,17 @@ class DiscordBot extends Discord.Client {
await PermissionHandler.resetPermissionsAllChannels(this, guild);
}
- require('../util/FcmListener')(this, guild);
- const credentials = InstanceUtils.readCredentialsFile(guild.id);
- for (const steamId of Object.keys(credentials)) {
- if (steamId !== credentials.hoster && steamId !== 'hoster') {
- require('../util/FcmListenerLite')(this, guild, steamId);
- }
+ //require('../util/FcmListener')(this, guild);
+ //const credentials = InstanceUtils.readCredentialsFile(guild.id);
+ //for (const steamId of Object.keys(credentials)) {
+ // if (steamId !== credentials.hoster && steamId !== 'hoster') {
+ // require('../util/FcmListenerLite')(this, guild, steamId);
+ // }
+ //}
+ const authTokens = InstanceUtils.readAuthTokensFile(guild.id);
+ for (const steamId of Object.keys(authTokens)) {
+ if (steamId === 'hoster') continue;
+ await AuthTokenListenerTemp.startNewAuthTokenListener(this, guild.id, steamId);
}
await require('../discordTools/SetupSettingsMenu')(this, guild);
diff --git a/src/structures/RustPlus.js b/src/structures/RustPlus.js
index b3e97c6e..375d544c 100644
--- a/src/structures/RustPlus.js
+++ b/src/structures/RustPlus.js
@@ -2254,7 +2254,8 @@ class RustPlus extends RustPlusLib {
}
async getCommandSend(command, callerName) {
- const credentials = InstanceUtils.readCredentialsFile(this.guildId);
+ //const credentials = InstanceUtils.readCredentialsFile(this.guildId);
+ const authTokens = InstanceUtils.readAuthTokensFile(this.guildId);
const prefix = this.generalSettings.prefix;
const commandSend = `${prefix}${Client.client.intlGet(this.guildId, 'commandSyntaxSend')}`;
const commandSendEn = `${prefix}${Client.client.intlGet('en', 'commandSyntaxSend')}`;
@@ -2274,13 +2275,15 @@ class RustPlus extends RustPlusLib {
for (const player of this.team.players) {
if (player.name.includes(name)) {
- if (!(player.steamId in credentials)) {
+ //if (!(player.steamId in credentials)) {
+ if (!(player.steamId in authTokens)) {
return Client.client.intlGet(this.guildId, 'userNotRegistered', {
user: player.name
});
}
- const discordUserId = credentials[player.steamId].discordUserId;
+ //const discordUserId = credentials[player.steamId].discordUserId;
+ const discordUserId = authTokens[player.steamId].discordUserId;
const user = await DiscordTools.getUserById(this.guildId, discordUserId);
const content = {
diff --git a/src/util/AuthTokenListener.js b/src/util/AuthTokenListener.js
new file mode 100644
index 00000000..77c928c3
--- /dev/null
+++ b/src/util/AuthTokenListener.js
@@ -0,0 +1,445 @@
+/*
+ Copyright (C) 2022 Alexander Emanuelsson (alexemanuelol)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ https://github.com/alexemanuelol/rustplusplus
+
+*/
+
+const Axios = require('axios');
+const Discord = require('discord.js');
+const Path = require('path');
+const PushReceiver = require('push-receiver');
+
+const Battlemetrics = require('../structures/Battlemetrics');
+const Constants = require('../util/constants.js');
+const DiscordButtons = require('../discordTools/discordButtons.js');
+const DiscordEmbeds = require('../discordTools/discordEmbeds.js');
+const DiscordMessages = require('../discordTools/discordMessages.js');
+const DiscordTools = require('../discordTools/discordTools.js');
+const InstanceUtils = require('../util/instanceUtils.js');
+const Map = require('../util/map.js');
+const Scrape = require('../util/scrape.js');
+
+const NotificationType = {
+ PAIRING: 1001
+}
+
+async function startNewAuthTokenListener(client, guildId, steamId) {
+ const authTokens = InstanceUtils.readAuthTokensFile(guildId);
+ const hoster = authTokens.hoster;
+
+ if (Object.keys(authTokens).length === 1) {
+ client.log(client.intlGet(null, 'warningCap'),
+ `Authentication Tokens are not registered for guild: ${guildId}, cannot start AuthTokenListener.`);
+ return false;
+ }
+
+ if (!hoster) {
+ client.log(client.intlGet(null, 'warningCap'),
+ `Authentication Token hoster is not set for guild ${guildId}, please set a hoster.`);
+ }
+
+ if (!(steamId in authTokens)) {
+ client.log(client.intlGet(null, 'warningCap'),
+ `Could not find a authentication token for steamId: ${steamId}.`);
+ return false;
+ }
+
+ client.log(client.intlGet(null, 'infoCap'), client.intlGet(null, 'fcmListenerStartHost', {
+ guildId: guildId,
+ steamId: hoster
+ }));
+ client.log(client.intlGet(null, 'infoCap'),
+ `Starting Auth Token Listener for guildId: ${guildId}, steamId: ${steamId}`);
+
+ if (!(client.authTokenListenerIntervalIds[guildId])) {
+ client.authTokenListenerIntervalIds[guildId] = new Object();
+ }
+
+ if (client.authTokenListenerIntervalIds[guildId][steamId]) {
+ clearInterval(client.authTokenListenerIntervalIds[guildId][steamId]);
+ delete client.authTokenListenerIntervalIds[guildId][steamId];
+ }
+
+ if (!(client.authTokenReadNotifications[guildId])) {
+ client.authTokenReadNotifications[guildId] = new Object();
+ }
+
+ if (client.authTokenReadNotifications[guildId][steamId]) {
+ client.authTokenReadNotifications[guildId][steamId].length = 0; /* Clear the array. */
+ }
+ else {
+ client.authTokenReadNotifications[guildId][steamId] = [];
+ }
+
+ await authTokenListener(client, guildId, steamId, true);
+ client.authTokenListenerIntervalIds[guildId][steamId] =
+ setInterval(authTokenListener, Constants.AUTH_TOKEN_LISTENER_REFRESH_MS, client, guildId, steamId);
+
+ return true;
+}
+
+async function authTokenListener(client, guildId, steamId, firstTime = false) {
+ const authTokens = InstanceUtils.readAuthTokensFile(guildId);
+ const hoster = authTokens.hoster;
+
+ if (!(steamId in authTokens)) {
+ client.log(client.intlGet(null, 'warningCap'),
+ `Could not find a authentication token for steamId: ${steamId}. Stopping interval.`);
+
+ if (client.authTokenListenerIntervalIds[guildId] && client.authTokenListenerIntervalIds[guildId][steamId]) {
+ clearInterval(client.authTokenListenerIntervalIds[guildId][steamId]);
+ delete client.authTokenListenerIntervalIds[guildId][steamId];
+ }
+ return;
+ }
+
+ let token = null;
+ for (let [key, value] of Object.entries(authTokens)) {
+ if (key === steamId) {
+ token = value.auth_token;
+ break;
+ }
+ }
+
+ if (!token) return;
+
+ const response = await Axios.post('https://companion-rust.facepunch.com/api/history/read', {
+ AuthToken: token
+ });
+
+ if (response.status !== 200) {
+ client.log(client.intlGet(null, 'warningCap'),
+ `Request to api/history/read was not successful, code: ${response.status}.`);
+ return;
+ }
+
+ const notifications = response.data;
+
+ if (firstTime) {
+ for (const notification of notifications) {
+ client.authTokenReadNotifications[guildId][steamId].push(notification.notificationId);
+ }
+ return;
+ }
+
+ /* Filter out the notifications that have already been read. */
+ const unreadNotifications = notifications.filter(
+ n => !client.authTokenReadNotifications[guildId][steamId].includes(n.notificationId));
+
+ for (const notification of unreadNotifications) {
+ const notificationId = notification.notificationId;
+ const title = notification.title;
+ const body = notification.body;
+ const data = JSON.parse(notification.data);
+ const channel = notification.channel;
+
+ switch (channel) {
+ case NotificationType.PAIRING: {
+ switch (data.type) {
+ case 'server': {
+ client.log('AuthToken', `GuildID: ${guildId}, SteamID: ${steamId}, pairing: server`);
+ pairingServer(client, guildId, data, hoster);
+ } break;
+
+ //case 'entity': {
+ // switch (data.entityName) {
+ // case 'Smart Switch': {
+ // client.log('AuthToken',
+ // `GuildID: ${guildId}, SteamID: ${steamId}, pairing: entity: Switch`);
+ // pairingEntitySwitch(client, guild, full, data, body);
+ // } break;
+
+ // case 'Smart Alarm': {
+ // client.log('FCM Host',
+ // `GuildID: ${guild.id}, SteamID: ${hoster}, pairing: entity: Smart Alarm`);
+ // pairingEntitySmartAlarm(client, guild, full, data, body);
+ // } break;
+
+ // case 'Storage Monitor': {
+ // client.log('FCM Host',
+ // `GuildID: ${guild.id}, SteamID: ${hoster}, pairing: entity: Storage Monitor`);
+ // pairingEntityStorageMonitor(client, guild, full, data, body);
+ // } break;
+
+ // default: {
+ // client.log('FCM Host',
+ // `GuildID: ${guild.id}, SteamID: ${hoster}, ` +
+ // `pairing: entity: other\n${JSON.stringify(full)}`);
+ // } break;
+ // }
+
+ //} break;
+
+ default: {
+ client.log('AuthToken',
+ `GuildID: ${guildId}, SteamID: ${steamId}, pairing: other\n${JSON.stringify(notification)}`);
+ } break;
+ }
+ } break;
+
+ default: {
+ client.log('AuthToken', `GuildID: ${guildId}, SteamID: ${steamId}, other\n${JSON.stringify(notification)}`);
+ } break;
+ }
+
+ // TODO! Support other notification, right now only pairing is supported.
+
+ client.authTokenReadNotifications[guildId][steamId].push(notificationId);
+ }
+}
+
+function isValidUrl(url) {
+ if (url.startsWith('https') || url.startsWith('http')) return true;
+ return false;
+}
+
+async function pairingServer(client, guildId, data, hoster) {
+ const instance = client.getInstance(guildId);
+ const serverId = `${data.ip}-${data.port}`;
+ const server = instance.serverList[serverId];
+
+ if (data.playerId === hoster) {
+ let message = undefined;
+ if (server) message = await DiscordTools.getMessageById(guildId, instance.channelId.servers, server.messageId);
+
+ let battlemetricsId = null;
+ const bmInstance = new Battlemetrics(null, data.name);
+ await bmInstance.setup();
+ if (bmInstance.lastUpdateSuccessful) {
+ battlemetricsId = bmInstance.id;
+ if (!client.battlemetricsInstances.hasOwnProperty(bmInstance.id)) {
+ client.battlemetricsInstances[bmInstance.id] = bmInstance;
+ }
+ }
+
+ instance.serverList[serverId] = {
+ title: data.name,
+ serverIp: data.ip,
+ appPort: data.port,
+ steamId: data.playerId,
+ playerToken: data.playerToken,
+ description: data.desc.replace(/\\n/g, '\n').replace(/\\t/g, '\t'),
+ img: isValidUrl(data.img) ? data.img.replace(/ /g, '%20') : Constants.DEFAULT_SERVER_IMG,
+ url: isValidUrl(data.url) ? data.url.replace(/ /g, '%20') : Constants.DEFAULT_SERVER_URL,
+ notes: server ? server.notes : {},
+ switches: server ? server.switches : {},
+ alarms: server ? server.alarms : {},
+ storageMonitors: server ? server.storageMonitors : {},
+ markers: server ? server.markers : {},
+ switchGroups: server ? server.switchGroups : {},
+ messageId: (message !== undefined) ? message.id : null,
+ battlemetricsId: battlemetricsId,
+ connect: (!bmInstance.lastUpdateSuccessful) ? null :
+ `connect ${bmInstance.server_ip}:${bmInstance.server_port}`,
+ cargoShipEgressTimeMs: server ? server.cargoShipEgressTimeMs : Constants.DEFAULT_CARGO_SHIP_EGRESS_TIME_MS,
+ oilRigLockedCrateUnlockTimeMs: server ? server.oilRigLockedCrateUnlockTimeMs :
+ Constants.DEFAULT_OIL_RIG_LOCKED_CRATE_UNLOCK_TIME_MS,
+ timeTillDay: server ? server.timeTillDay : null,
+ timeTillNight: server ? server.timeTillNight : null
+ };
+ }
+
+ if (!instance.serverListLite.hasOwnProperty(serverId)) instance.serverListLite[serverId] = new Object();
+
+ instance.serverListLite[serverId][data.playerId] = {
+ serverIp: data.ip,
+ appPort: data.port,
+ steamId: data.playerId,
+ playerToken: data.playerToken,
+ };
+ client.setInstance(guildId, instance);
+
+ if (data.playerId !== hoster) {
+ const rustplus = client.rustplusInstances[guildId];
+ if (rustplus && (rustplus.serverId === serverId) && rustplus.team.leaderSteamId === data.playerId) {
+ rustplus.updateLeaderRustPlusLiteInstance();
+ }
+ }
+
+ await DiscordMessages.sendServerMessage(guildId, serverId, null);
+}
+
+//async function pairingEntitySwitch(client, guild, full, data, body) {
+// const instance = client.getInstance(guild.id);
+// const serverId = `${body.ip}-${body.port}`;
+// if (!instance.serverList.hasOwnProperty(serverId)) return;
+// const switches = instance.serverList[serverId].switches;
+//
+// const entityExist = instance.serverList[serverId].switches.hasOwnProperty(body.entityId);
+// instance.serverList[serverId].switches[body.entityId] = {
+// active: entityExist ? switches[body.entityId].active : false,
+// reachable: entityExist ? switches[body.entityId].reachable : true,
+// name: entityExist ? switches[body.entityId].name : client.intlGet(guild.id, 'smartSwitch'),
+// command: entityExist ? switches[body.entityId].command : body.entityId,
+// image: entityExist ? switches[body.entityId].image : 'smart_switch.png',
+// autoDayNightOnOff: entityExist ? switches[body.entityId].autoDayNightOnOff : 0,
+// location: entityExist ? switches[body.entityId].location : null,
+// x: entityExist ? switches[body.entityId].x : null,
+// y: entityExist ? switches[body.entityId].y : null,
+// server: entityExist ? switches[body.entityId].server : body.name,
+// proximity: entityExist ? switches[body.entityId].proximity : Constants.PROXIMITY_SETTING_DEFAULT_METERS,
+// messageId: entityExist ? switches[body.entityId].messageId : null
+// };
+// client.setInstance(guild.id, instance);
+//
+// const rustplus = client.rustplusInstances[guild.id];
+// if (rustplus && serverId === rustplus.serverId) {
+// const info = await rustplus.getEntityInfoAsync(body.entityId);
+// if (!(await rustplus.isResponseValid(info))) {
+// instance.serverList[serverId].switches[body.entityId].reachable = false;
+// }
+//
+// const teamInfo = await rustplus.getTeamInfoAsync();
+// if (await rustplus.isResponseValid(teamInfo)) {
+// const player = teamInfo.teamInfo.members.find(e => e.steamId.toString() === rustplus.playerId);
+// if (player) {
+// const location = Map.getPos(player.x, player.y, rustplus.info.correctedMapSize, rustplus);
+// instance.serverList[serverId].switches[body.entityId].location = location.location;
+// instance.serverList[serverId].switches[body.entityId].x = location.x;
+// instance.serverList[serverId].switches[body.entityId].y = location.y;
+// }
+// }
+//
+// if (instance.serverList[serverId].switches[body.entityId].reachable) {
+// instance.serverList[serverId].switches[body.entityId].active = info.entityInfo.payload.value;
+// }
+// client.setInstance(guild.id, instance);
+//
+// await DiscordMessages.sendSmartSwitchMessage(guild.id, serverId, body.entityId);
+// }
+//}
+//
+//async function pairingEntitySmartAlarm(client, guild, full, data, body) {
+// const instance = client.getInstance(guild.id);
+// const serverId = `${body.ip}-${body.port}`;
+// if (!instance.serverList.hasOwnProperty(serverId)) return;
+// const alarms = instance.serverList[serverId].alarms;
+//
+// const entityExist = instance.serverList[serverId].alarms.hasOwnProperty(body.entityId);
+// instance.serverList[serverId].alarms[body.entityId] = {
+// active: entityExist ? alarms[body.entityId].active : false,
+// reachable: entityExist ? alarms[body.entityId].reachable : true,
+// everyone: entityExist ? alarms[body.entityId].everyone : false,
+// name: entityExist ? alarms[body.entityId].name : client.intlGet(guild.id, 'smartAlarm'),
+// message: entityExist ? alarms[body.entityId].message : client.intlGet(guild.id, 'baseIsUnderAttack'),
+// lastTrigger: entityExist ? alarms[body.entityId].lastTrigger : null,
+// command: entityExist ? alarms[body.entityId].command : body.entityId,
+// id: entityExist ? alarms[body.entityId].id : body.entityId,
+// image: entityExist ? alarms[body.entityId].image : 'smart_alarm.png',
+// location: entityExist ? alarms[body.entityId].location : null,
+// server: entityExist ? alarms[body.entityId].server : body.name,
+// messageId: entityExist ? alarms[body.entityId].messageId : null
+// };
+// client.setInstance(guild.id, instance);
+//
+// const rustplus = client.rustplusInstances[guild.id];
+// if (rustplus && serverId === rustplus.serverId) {
+// const info = await rustplus.getEntityInfoAsync(body.entityId);
+// if (!(await rustplus.isResponseValid(info))) {
+// instance.serverList[serverId].alarms[body.entityId].reachable = false;
+// }
+//
+// const teamInfo = await rustplus.getTeamInfoAsync();
+// if (await rustplus.isResponseValid(teamInfo)) {
+// const player = teamInfo.teamInfo.members.find(e => e.steamId.toString() === rustplus.playerId);
+// if (player) {
+// const location = Map.getPos(player.x, player.y, rustplus.info.correctedMapSize, rustplus);
+// instance.serverList[serverId].alarms[body.entityId].location = location.location;
+// }
+// }
+//
+// if (instance.serverList[serverId].alarms[body.entityId].reachable) {
+// instance.serverList[serverId].alarms[body.entityId].active = info.entityInfo.payload.value;
+// }
+// client.setInstance(guild.id, instance);
+// }
+//
+// await DiscordMessages.sendSmartAlarmMessage(guild.id, serverId, body.entityId);
+//}
+//
+//async function pairingEntityStorageMonitor(client, guild, full, data, body) {
+// const instance = client.getInstance(guild.id);
+// const serverId = `${body.ip}-${body.port}`;
+// if (!instance.serverList.hasOwnProperty(serverId)) return;
+// const storageMonitors = instance.serverList[serverId].storageMonitors;
+//
+// const entityExist = instance.serverList[serverId].storageMonitors.hasOwnProperty(body.entityId);
+// instance.serverList[serverId].storageMonitors[body.entityId] = {
+// name: entityExist ? storageMonitors[body.entityId].name : client.intlGet(guild.id, 'storageMonitor'),
+// reachable: entityExist ? storageMonitors[body.entityId].reachable : true,
+// id: entityExist ? storageMonitors[body.entityId].id : body.entityId,
+// type: entityExist ? storageMonitors[body.entityId].type : null,
+// decaying: entityExist ? storageMonitors[body.entityId].decaying : false,
+// upkeep: entityExist ? storageMonitors[body.entityId].upkeep : null,
+// everyone: entityExist ? storageMonitors[body.entityId].everyone : false,
+// inGame: entityExist ? storageMonitors[body.entityId].inGame : true,
+// image: entityExist ? storageMonitors[body.entityId].image : 'storage_monitor.png',
+// location: entityExist ? storageMonitors[body.entityId].location : null,
+// server: entityExist ? storageMonitors[body.entityId].server : body.name,
+// messageId: entityExist ? storageMonitors[body.entityId].messageId : null
+// };
+// client.setInstance(guild.id, instance);
+//
+// const rustplus = client.rustplusInstances[guild.id];
+// if (rustplus && serverId === rustplus.serverId) {
+// const info = await rustplus.getEntityInfoAsync(body.entityId);
+// if (!(await rustplus.isResponseValid(info))) {
+// instance.serverList[serverId].storageMonitors[body.entityId].reachable = false;
+// }
+//
+// const teamInfo = await rustplus.getTeamInfoAsync();
+// if (await rustplus.isResponseValid(teamInfo)) {
+// const player = teamInfo.teamInfo.members.find(e => e.steamId.toString() === rustplus.playerId);
+// if (player) {
+// const location = Map.getPos(player.x, player.y, rustplus.info.correctedMapSize, rustplus);
+// instance.serverList[serverId].storageMonitors[body.entityId].location = location.location;
+// }
+// }
+//
+// if (instance.serverList[serverId].storageMonitors[body.entityId].reachable) {
+// if (info.entityInfo.payload.capacity === Constants.STORAGE_MONITOR_TOOL_CUPBOARD_CAPACITY) {
+// instance.serverList[serverId].storageMonitors[body.entityId].type = 'toolCupboard';
+// instance.serverList[serverId].storageMonitors[body.entityId].image = 'tool_cupboard.png';
+// if (info.entityInfo.payload.protectionExpiry === 0) {
+// instance.serverList[serverId].storageMonitors[body.entityId].decaying = true;
+// }
+// }
+// else if (info.entityInfo.payload.capacity === Constants.STORAGE_MONITOR_VENDING_MACHINE_CAPACITY) {
+// instance.serverList[serverId].storageMonitors[body.entityId].type = 'vendingMachine';
+// instance.serverList[serverId].storageMonitors[body.entityId].image = 'vending_machine.png';
+// }
+// else if (info.entityInfo.payload.capacity === Constants.STORAGE_MONITOR_LARGE_WOOD_BOX_CAPACITY) {
+// instance.serverList[serverId].storageMonitors[body.entityId].type = 'largeWoodBox';
+// instance.serverList[serverId].storageMonitors[body.entityId].image = 'large_wood_box.png';
+// }
+//
+// rustplus.storageMonitors[body.entityId] = {
+// items: info.entityInfo.payload.items,
+// expiry: info.entityInfo.payload.protectionExpiry,
+// capacity: info.entityInfo.payload.capacity,
+// hasProtection: info.entityInfo.payload.hasProtection
+// }
+// }
+// client.setInstance(guild.id, instance);
+//
+// await DiscordMessages.sendStorageMonitorMessage(guild.id, serverId, body.entityId);
+// }
+//}
+
+module.exports = {
+ startNewAuthTokenListener
+}
\ No newline at end of file
diff --git a/src/util/CreateAuthTokensFile.js b/src/util/CreateAuthTokensFile.js
new file mode 100644
index 00000000..f8572eb1
--- /dev/null
+++ b/src/util/CreateAuthTokensFile.js
@@ -0,0 +1,29 @@
+/*
+ Copyright (C) 2024 Alexander Emanuelsson (alexemanuelol)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ https://github.com/alexemanuelol/rustplusplus
+
+*/
+
+const Fs = require('fs');
+const Path = require('path');
+
+module.exports = (client, guild) => {
+ if (!Fs.existsSync(Path.join(__dirname, '..', '..', 'authtokens', `${guild.id}.json`))) {
+ Fs.writeFileSync(Path.join(__dirname, '..', '..', 'authtokens', `${guild.id}.json`),
+ JSON.stringify({ hoster: null }, null, 2));
+ }
+};
diff --git a/src/util/constants.js b/src/util/constants.js
index 22d4b6dd..42e35a71 100644
--- a/src/util/constants.js
+++ b/src/util/constants.js
@@ -62,6 +62,7 @@ module.exports = {
OIL_RIG_CHINOOK_47_MAX_SPAWN_DISTANCE: 550,
PROXIMITY_SETTING_DEFAULT_METERS: 500,
HARBOR_DOCK_DISTANCE: 100,
+ AUTH_TOKEN_LISTENER_REFRESH_MS: 15000,
/* Emojis */
ONLINE_EMOJI: ':green_circle:',
diff --git a/src/util/instanceUtils.js b/src/util/instanceUtils.js
index 777c105d..d1f7d490 100644
--- a/src/util/instanceUtils.js
+++ b/src/util/instanceUtils.js
@@ -61,4 +61,14 @@ module.exports = {
const path = Path.join(__dirname, '..', '..', 'credentials', `${guildId}.json`);
Fs.writeFileSync(path, JSON.stringify(credentials, null, 2));
},
+
+ readAuthTokensFile: function (guildId) {
+ const path = Path.join(__dirname, '..', '..', 'authtokens', `${guildId}.json`);
+ return JSON.parse(Fs.readFileSync(path, 'utf8'));
+ },
+
+ writeAuthTokensFile: function (guildId, authTokens) {
+ const path = Path.join(__dirname, '..', '..', 'authtokens', `${guildId}.json`);
+ Fs.writeFileSync(path, JSON.stringify(authTokens, null, 2));
+ },
}