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)); + }, }