diff --git a/docs/commands.md b/docs/commands.md index 9dfd6ab22..2c8b51d46 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -347,6 +347,7 @@ In-Game Command | Description --------------- | ----------- [**afk**](commands.md#afk) | Get the currently afk players in your team. [**alive**](commands.md#alive) | Get the player with the longest time alive. +[**calc**](commands.md#calc) | Calculate a mathematical expression. [**cargo**](commands.md#cargo) | Get information about CargoShip (Location, time till enters egress stage, time since last on map). [**chinook**](commands.md#chinook) | Get information about Chinook 47 (Location, time since last on map). [**connection/connections**](commands.md#connectionconnections) | Get recent connection events. @@ -401,6 +402,12 @@ In-Game Command | Description ![In-Game Command alive Image](images/ingame_commands/alive_ingame.png) +## **calc** + +> **Calculate a mathematical expression.** +
Command: `!calc 1+1` +
Command: `!calc 17*33` + ## **cargo** diff --git a/package-lock.json b/package-lock.json index 645f969b9..bb74f05b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -700,6 +700,30 @@ "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", "license": "MIT" }, + "node_modules/readable-web-to-node-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/es-set-tostringtag": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", @@ -2109,6 +2133,12 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/prism-media": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz", @@ -4835,6 +4865,13 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, "node_modules/pngjs": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", diff --git a/src/handlers/discordCommandHandler.js b/src/handlers/discordCommandHandler.js index baf9f6bc4..0aed3e440 100644 --- a/src/handlers/discordCommandHandler.js +++ b/src/handlers/discordCommandHandler.js @@ -44,6 +44,12 @@ module.exports = { commandLowerCase.startsWith(`${prefix}${client.intlGet(guildId, 'commandSyntaxAlive')}`)) { response = rustplus.getCommandAlive(command); } + else if ((commandLowerCase.startsWith(`${prefix}${client.intlGet('en', 'commandSyntaxCalc')} `) || + commandLowerCase === `${prefix}${client.intlGet('en', 'commandSyntaxCalc')}`) || + (commandLowerCase.startsWith(`${prefix}${client.intlGet(guildId, 'commandSyntaxCalc')} `) || + commandLowerCase === `${prefix}${client.intlGet(guildId, 'commandSyntaxCalc')}`)) { + response = rustplus.getCommandCalc(client, command); + } else if (commandLowerCase === `${prefix}${client.intlGet('en', 'commandSyntaxCargo')}` || commandLowerCase === `${prefix}${client.intlGet(guildId, 'commandSyntaxCargo')}`) { response = rustplus.getCommandCargo(); diff --git a/src/handlers/inGameCommandHandler.js b/src/handlers/inGameCommandHandler.js index 8d606ef74..27b895967 100644 --- a/src/handlers/inGameCommandHandler.js +++ b/src/handlers/inGameCommandHandler.js @@ -51,6 +51,12 @@ module.exports = { commandLowerCase.startsWith(`${prefix}${client.intlGet(guildId, 'commandSyntaxAlive')}`)) { rustplus.sendInGameMessage(rustplus.getCommandAlive(command)); } + else if ((commandLowerCase.startsWith(`${prefix}${client.intlGet('en', 'commandSyntaxCalc')} `) || + commandLowerCase === `${prefix}${client.intlGet('en', 'commandSyntaxCalc')}`) || + (commandLowerCase.startsWith(`${prefix}${client.intlGet(guildId, 'commandSyntaxCalc')} `) || + commandLowerCase === `${prefix}${client.intlGet(guildId, 'commandSyntaxCalc')}`)) { + rustplus.sendInGameMessage(rustplus.getCommandCalc(client, command)); + } else if (commandLowerCase === `${prefix}${client.intlGet('en', 'commandSyntaxCargo')}` || commandLowerCase === `${prefix}${client.intlGet(guildId, 'commandSyntaxCargo')}`) { rustplus.sendInGameMessage(rustplus.getCommandCargo()); diff --git a/src/languages/cs.json b/src/languages/cs.json index e09ac66f7..49c46e5a4 100644 --- a/src/languages/cs.json +++ b/src/languages/cs.json @@ -786,5 +786,6 @@ "wipeDetected": "Wipe byl detekován!", "yield": "Úrodnost", "youAreAlreadyLeader": "Už jsi vůdce.", - "youAreNotPairedWithServer": "Příkaz na vůdce nefunguje, protože nejsi spárován se serverem." + "youAreNotPairedWithServer": "Příkaz na vůdce nefunguje, protože nejsi spárován se serverem.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/de.json b/src/languages/de.json index 3f6e42a9c..477ca6bc3 100644 --- a/src/languages/de.json +++ b/src/languages/de.json @@ -786,5 +786,6 @@ "wipeDetected": "Wipe erkannt!", "yield": "Ertrag", "youAreAlreadyLeader": "Du bist bereits Anführer.", - "youAreNotPairedWithServer": "Anführer-Befehl funktioniert nicht, weil du nicht mit dem Server gekoppelt bist." + "youAreNotPairedWithServer": "Anführer-Befehl funktioniert nicht, weil du nicht mit dem Server gekoppelt bist.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/en.json b/src/languages/en.json index bb5fb961d..b40b26453 100644 --- a/src/languages/en.json +++ b/src/languages/en.json @@ -735,7 +735,7 @@ "travelingVendorHaltedSetting": "When the Traveling Vendor stops moving, send a notification.", "travelingVendorLeftSetting": "When the Traveling Vendor left the map, send a notification.", "travelingVendorLocatedAt": "The Traveling Vendor is located at {location}.", - "travelingVendorLeftMap" : "The Traveling Vendor just left the map at {location}.", + "travelingVendorLeftMap": "The Traveling Vendor just left the map at {location}.", "travelingVendorNotCurrentlyOnMap": "The Traveling Vendor is not currently on the map.", "travelingVendorResumedAt": "The Traveling Vendor resumed moving at {location}.", "travelingVendorSpawnedAt": "The Traveling Vendor spawned at {location}.", @@ -786,5 +786,6 @@ "wipeDetected": "Wipe detected!", "yield": "Yield", "youAreAlreadyLeader": "You are already leader.", - "youAreNotPairedWithServer": "Leader command does not work because you're not paired with the server." + "youAreNotPairedWithServer": "Leader command does not work because you're not paired with the server.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/es.json b/src/languages/es.json index 662e86e30..36f22825f 100644 --- a/src/languages/es.json +++ b/src/languages/es.json @@ -786,5 +786,6 @@ "wipeDetected": "¡Wipe detectado!", "yield": "Yield", "youAreAlreadyLeader": "Ya eres líder.", - "youAreNotPairedWithServer": "El comando líder no funciona porque no está emparejado con el servidor." + "youAreNotPairedWithServer": "El comando líder no funciona porque no está emparejado con el servidor.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/fr.json b/src/languages/fr.json index ccc19587c..b1077c1d9 100644 --- a/src/languages/fr.json +++ b/src/languages/fr.json @@ -786,5 +786,6 @@ "wipeDetected": "Wipe detecté!", "yield": "Rendement", "youAreAlreadyLeader": "Vous êtes déjà le chef du groupe.", - "youAreNotPairedWithServer": "La commande Leader ne fonctionne pas car n'est pas associé au serveur." + "youAreNotPairedWithServer": "La commande Leader ne fonctionne pas car n'est pas associé au serveur.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/it.json b/src/languages/it.json index 8c7c6c331..276032da5 100644 --- a/src/languages/it.json +++ b/src/languages/it.json @@ -786,5 +786,6 @@ "wipeDetected": "Rilevato wipe!", "yield": "Yield", "youAreAlreadyLeader": "Sei già il leader.", - "youAreNotPairedWithServer": "Il comando del leader non funziona perché non sei associato al server." + "youAreNotPairedWithServer": "Il comando del leader non funziona perché non sei associato al server.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/ko.json b/src/languages/ko.json index 8fcb75144..adf293e15 100644 --- a/src/languages/ko.json +++ b/src/languages/ko.json @@ -786,5 +786,6 @@ "wipeDetected": "서버 초기화가 감지되었습니다!", "yield": "Yield", "youAreAlreadyLeader": "당신은 이미 팀 리더 입니다.", - "youAreNotPairedWithServer": "서버와 페어링되지 않았기 때문에 리더 명령어가 작동하지 않습니다." + "youAreNotPairedWithServer": "서버와 페어링되지 않았기 때문에 리더 명령어가 작동하지 않습니다.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/pl.json b/src/languages/pl.json index d23121c4c..fd7fc4ac6 100644 --- a/src/languages/pl.json +++ b/src/languages/pl.json @@ -786,5 +786,6 @@ "wipeDetected": "Wipe detected!", "yield": "Plon", "youAreAlreadyLeader": "You are already leader.", - "youAreNotPairedWithServer": "Leader command does not work because you're not paired with the server." + "youAreNotPairedWithServer": "Leader command does not work because you're not paired with the server.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/pt.json b/src/languages/pt.json index 3598ffffa..9d8c0bcde 100644 --- a/src/languages/pt.json +++ b/src/languages/pt.json @@ -786,5 +786,6 @@ "wipeDetected": "Wipe detetado!", "yield": "Rendimento", "youAreAlreadyLeader": "Você já é o líder.", - "youAreNotPairedWithServer": "O comando Líder não funciona porque você não está emparelhado com o servidor." + "youAreNotPairedWithServer": "O comando Líder não funciona porque você não está emparelhado com o servidor.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/ru.json b/src/languages/ru.json index f1d099369..7943dad24 100644 --- a/src/languages/ru.json +++ b/src/languages/ru.json @@ -786,5 +786,6 @@ "wipeDetected": "Обнаружен Wipe!", "yield": "Урожай", "youAreAlreadyLeader": "Вы уже лидер.", - "youAreNotPairedWithServer": "Команда лидера не работает, потому что вы не подключены к серверу." + "youAreNotPairedWithServer": "Команда лидера не работает, потому что вы не подключены к серверу.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/sv.json b/src/languages/sv.json index 8d71ed5bb..7edd4ede7 100644 --- a/src/languages/sv.json +++ b/src/languages/sv.json @@ -786,5 +786,6 @@ "wipeDetected": "Rensning upptäcktes!", "yield": "Yield", "youAreAlreadyLeader": "Du är redan lagledare.", - "youAreNotPairedWithServer": "Leader-kommandot fungerar inte eftersom du inte är parad med servern." + "youAreNotPairedWithServer": "Leader-kommandot fungerar inte eftersom du inte är parad med servern.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/languages/tr.json b/src/languages/tr.json index c8b7e925d..8a7c01739 100644 --- a/src/languages/tr.json +++ b/src/languages/tr.json @@ -786,5 +786,6 @@ "wipeDetected": "Wipe tespit edildi!", "yield": "Teslim Ol", "youAreAlreadyLeader": "Zaten lidersin.", - "youAreNotPairedWithServer": "Sunucuyla eşleşmediğiniz için lider komutu çalışmıyor." + "youAreNotPairedWithServer": "Sunucuyla eşleşmediğiniz için lider komutu çalışmıyor.", + "commandSyntaxCalc": "calc" } \ No newline at end of file diff --git a/src/structures/RustPlus.js b/src/structures/RustPlus.js index 39820fa6a..453a8e011 100644 --- a/src/structures/RustPlus.js +++ b/src/structures/RustPlus.js @@ -708,6 +708,34 @@ class RustPlus extends RustPlusLib { }); } + getCommandCalc(client, command) { + const prefix = this.generalSettings.prefix; + const commandCalc = `${prefix}${client.intlGet(this.guildId, 'commandSyntaxCalc')}`; + const commandCalcEn = `${prefix}${client.intlGet('en', 'commandSyntaxCalc')}`; + + let expr = ''; + if (command.toLowerCase().startsWith(`${commandCalc} `)) { + expr = command.substring(commandCalc.length).trim(); + } + else if (command.toLowerCase().startsWith(`${commandCalcEn} `)) { + expr = command.substring(commandCalcEn.length).trim(); + } + + if (expr === '') return client.intlGet(this.guildId, 'missingArguments'); + + try { + const safeExpr = expr.replace(/[^\d.\+\-\*\/\(\)\s]/g, ''); + if (safeExpr.length === 0) return client.intlGet(this.guildId, 'errorExecutingCommand'); + + const res = new Function(`return (${safeExpr});`)(); + if (res === undefined || Number.isNaN(res)) return client.intlGet(this.guildId, 'errorExecutingCommand'); + + return `${client.intlGet(this.guildId, 'calculated')}: ${expr} = ${res}`; + } catch(e) { + return client.intlGet(this.guildId, 'errorExecutingCommand'); + } + } + getCommandCargo(isInfoChannel = false) { const strings = []; let unhandled = this.mapMarkers.cargoShips.map(e => e.id); diff --git a/ubuntu_install.sh b/ubuntu_install.sh new file mode 100755 index 000000000..2e538813c --- /dev/null +++ b/ubuntu_install.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# ubuntu_install.sh +# Automatically clones repository and starts the bot using pm2 interactively + +set -e + +REPO_URL="https://github.com/abboodnoga176-max/rustplusplus.git" +INSTALL_DIR="$HOME/rustplusplus" + +echo "=== Rustplusplus Ubuntu Installer ===" + +echo "Checking for required dependencies..." +sudo apt-get update +sudo apt-get install -y git curl build-essential + +if ! command -v node >/dev/null 2>&1; then + echo "Node.js not found. Installing Node.js..." + curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - + sudo apt-get install -y nodejs +fi + +if ! command -v pm2 >/dev/null 2>&1; then + echo "PM2 not found. Installing PM2 globally..." + sudo npm install -g pm2 +fi + +if [ -d "$INSTALL_DIR" ]; then + echo "Directory $INSTALL_DIR already exists." +else + echo "Cloning rustplusplus..." + git clone "$REPO_URL" "$INSTALL_DIR" +fi + +cd "$INSTALL_DIR" + +echo "Installing NPM dependencies..." +npm install + +ENV_FILE=".env" +if [ ! -f "$ENV_FILE" ]; then + echo "" + echo "--- Discord Credentials Setup ---" + read -p "Enter your RPP_DISCORD_CLIENT_ID: " DISCORD_CLIENT_ID + read -p "Enter your RPP_DISCORD_TOKEN: " DISCORD_TOKEN + + cat < "$ENV_FILE" +RPP_DISCORD_CLIENT_ID=$DISCORD_CLIENT_ID +RPP_DISCORD_TOKEN=$DISCORD_TOKEN +ENV + echo "Credentials saved to $ENV_FILE" +else + echo "Credentials already exist in $ENV_FILE" +fi + +UPDATE_SCRIPT="$INSTALL_DIR/ubuntu_update.sh" +cat << 'UPSCRIPT' > "$UPDATE_SCRIPT" +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$DIR" + +# Fetch latest changes +git fetch origin + +LOCAL=$(git rev-parse HEAD) +REMOTE=$(git rev-parse @{u}) + +if [ "$LOCAL" != "$REMOTE" ]; then + echo "Updates found. Sending restart notification..." + + if [ -f "notify_restart.js" ]; then + node notify_restart.js + sleep 5 + fi + + git reset --hard origin/master + npm install + + pm2 restart rustplusplus + echo "Update complete." +fi +UPSCRIPT + +chmod +x "$UPDATE_SCRIPT" + +NOTIFY_SCRIPT="$INSTALL_DIR/notify_restart.js" +cat << 'NJSCRIPT' > "$NOTIFY_SCRIPT" +require('dotenv').config(); +const { Client, GatewayIntentBits, ChannelType } = require('discord.js'); +const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages] }); + +client.on('ready', async () => { + try { + const guilds = client.guilds.cache; + for (const [id, guild] of guilds) { + const channels = guild.channels.cache.filter(c => c.type === ChannelType.GuildText); + const infoChannel = channels.find(c => c.name === 'information' && c.parent && c.parent.name === 'rustplusplus'); + if (infoChannel) { + await infoChannel.send('⚠️ **Notice:** The bot is restarting to apply a new update. It will be back shortly!').catch(() => {}); + } + } + } catch (e) { + console.error("Error sending restart notifications:", e); + } + client.destroy(); +}); +client.login(process.env.RPP_DISCORD_TOKEN).catch(()=>console.log("Could not login to notify")); +NJSCRIPT + +CRON_JOB="*/5 * * * * $UPDATE_SCRIPT >> $INSTALL_DIR/logs/auto_update.log 2>&1" +(crontab -l 2>/dev/null | grep -Fv "ubuntu_update.sh"; echo "$CRON_JOB") | crontab - +echo "Cron job added to check for updates every 5 minutes." + +mkdir -p "$INSTALL_DIR/logs" + +echo "Starting bot with PM2..." +npm install dotenv --no-save +pm2 start npm --name "rustplusplus" -- run start +pm2 save +pm2 startup | grep "sudo" | bash || true + +echo "=== Installation Complete ===" +echo "Bot is now running in the background via PM2." +echo "Use 'pm2 logs rustplusplus' to view logs."