salien-script-js
Advanced tools
Comparing version 0.0.12 to 0.0.13
{ | ||
"name": "salien-script-js", | ||
"version": "0.0.12", | ||
"description": "An easy to install, run and update Node.js script for the Steam salien mini-game.", | ||
"version": "0.0.13", | ||
"description": "A easy to install, run and update Node.js script for the Steam salien mini-game.", | ||
"keywords": [ | ||
@@ -33,7 +33,7 @@ "salien", | ||
"files": [ | ||
"src", | ||
"cli.js" | ||
"bin", | ||
"src" | ||
], | ||
"bin": { | ||
"salien-script-js": "./cli.js" | ||
"salien-script-js": "./bin/cli.js" | ||
}, | ||
@@ -48,3 +48,5 @@ "main": "src/index.js", | ||
"prepublishOnly": "npm run lint", | ||
"prettier": "prettier --write \"src/**/*.js\"" | ||
"prettier": "prettier --write \"src/**/*.js\"", | ||
"test": "jest", | ||
"test/coverage": "jest --coverage" | ||
}, | ||
@@ -55,7 +57,8 @@ "dependencies": { | ||
"delay": "^3.0.0", | ||
"fetch-retry": "^1.2.1", | ||
"meow": "^5.0.0", | ||
"node-fetch": "^2.1.2", | ||
"update-check": "^1.5.2" | ||
}, | ||
"devDependencies": { | ||
"coveralls": "^3.0.1", | ||
"eslint": "^5.0.0", | ||
@@ -65,3 +68,5 @@ "eslint-config-airbnb-base": "^13.0.0", | ||
"eslint-plugin-import": "^2.12.0", | ||
"eslint-plugin-jest": "^21.17.0", | ||
"eslint-plugin-prettier": "^2.6.0", | ||
"jest": "^23.1.0", | ||
"prettier": "^1.13.5" | ||
@@ -68,0 +73,0 @@ }, |
139
README.md
# salien-script-js | ||
👽 An easy to install, run and update Node.js script for the Steam salien mini-game. | ||
👽 A easy to install, run and update Node.js script for the Steam salien mini-game. | ||
@@ -9,2 +9,3 @@ > A Node.js implementation of https://github.com/SteamDatabase/SalienCheat by [xPaw](https://github.com/xPaw) with additional features! | ||
[![CI Status](https://img.shields.io/travis/South-Paw/salien-script-js.svg)](https://travis-ci.org/South-Paw/salien-script-js) | ||
[![Coveralls Status](https://img.shields.io/coveralls/github/South-Paw/salien-script-js.svg)](https://coveralls.io/github/South-Paw/salien-script-js) | ||
[![Dependencies](https://david-dm.org/South-Paw/salien-script-js.svg)](https://david-dm.org/South-Paw/salien-script-js) | ||
@@ -17,11 +18,20 @@ [![Dev Dependencies](https://david-dm.org/South-Paw/salien-script-js/dev-status.svg)](https://david-dm.org/South-Paw/salien-script-js?type=dev) | ||
* 🎉 Easy to install, run and update | ||
* ✉️ Update checker and log notifications | ||
* 🎉 [Easy to install, run and update](#️-how-to-use-this) | ||
* ✉️ [Update checker and log notifications](#-how-to-update-the-script) | ||
* 👽 Same logic as the [PHP version](https://github.com/SteamDatabase/SalienCheat) (we almost have parity) | ||
* 👌 Pick your own steam group | ||
* 👥 Works well with multiple tokens/scripts | ||
* 👀 Name your running scripts | ||
* 🐳 Docker support | ||
* ☁️ Heroku support | ||
* 👌 [Pick your own steam group](#-represent-your-steam-group-optional) | ||
* 👥 [Works well with multiple tokens/scripts](#-multiple-tokensscripts) | ||
* 👀 [Name your running scripts](#-multiple-tokensscripts) | ||
* ☁️ [Heroku support](#advanced-️-deploying-to-heroku) | ||
* 🐳 [Docker support](#advanced--running-as-a-docker-container) | ||
* 📦 [npm package export](#advanced--usage-as-an-npm-package) | ||
> Note: We'll try our best to keep this version up to date with the PHP and other versions! Suggestions welcome. | ||
@@ -40,3 +50,3 @@ | ||
> ### If you appreciate the script, please leave a star ⭐ on the project! | ||
> ### Remeber to drop us a ⭐ star on the project if you appreciate this script! | ||
@@ -69,20 +79,23 @@ ## 😍 How to update the script | ||
* [/r/saliens](https://steamcommunity.com/groups/summersaliens) id: `103582791462557324` | ||
* [SteamDB](https://steamcommunity.com/groups/steamdb) id: `103582791434298690` | ||
* [100Pals](https://steamcommunity.com/groups/100pals) id: `103582791454524084` | ||
* [SteamDB](https://steamcommunity.com/groups/steamdb) id: `103582791434298690` | ||
### 🌌 Select a planet (Optional) | ||
### 👥 Multiple tokens/scripts | ||
If you would like to override planet selection in favor of a particular one, provide the `--planet` CLI option with the planet ID. | ||
Simply open another PowerShell window and run `salien-script-js --token yyyyyyyy --name "name of this script"` where `yyyyyyyy` is your other accounts token and `name of this script` if what you'd like to see in the log outputs. | ||
```sh-session | ||
salien-script-js --token xxxxxxxx --planet 15 | ||
``` | ||
--- | ||
### 👥 Multiple tokens/scripts | ||
### Advanced: CLI Arguments | ||
Simply open another PowerShell window and run `salien-script-js --token yyyyyyyy --name "name of this script"` where `yyyyyyyy` is your other accounts token and `name of this script` if what you'd like to see in the log outputs. | ||
``` | ||
Usage: | ||
salien-script-js [options] | ||
--- | ||
Options: | ||
--token, -t Your Saliens game token. | ||
--group, -g (Optional) The ID of a steam group you'd like to represent. | ||
--name, -n (Optional) The name to display on this instance of the script. | ||
--logRequests, -l (Optional) Set to true if you'd like to show Steam API requests in the logs. | ||
``` | ||
@@ -92,3 +105,3 @@ ## Advanced: 📦 Usage as an npm package | ||
```js | ||
const SalienScript = require('salien-script-js'); | ||
const { SalienScript } = require('salien-script-js'); | ||
@@ -127,6 +140,9 @@ const config = { | ||
1. Click the button above. | ||
2. Set SALIEN_CONFIG (`token1:group1:name1;token2:group2:name2...`). | ||
![Heroku-Config](https://i.imgur.com/07KcyVC.png) | ||
2. Set SALIEN_CONFIG_V2 ([see note below](#heroku-configuration)). | ||
3. That's all! | ||
To check if it works, visit logs at https://dashboard.heroku.com/apps/[YOUR_APP_NAME]/logs | ||
If you see "Application Error" when going to the webpage of your app, it's okay - the script will still run anyway. | ||
### Deploying with Heroku CLI | ||
@@ -138,3 +154,3 @@ | ||
$ heroku create [APP_NAME] | ||
$ heroku config:set "SALIEN_CONFIG=token1:group1:name1;token2:group2:name2..." | ||
$ heroku config:set "SALIEN_CONFIG_V2=[APP_CONFIG]" | ||
$ git push heroku master | ||
@@ -144,4 +160,81 @@ $ heroku ps:scale web=0 salien=1 | ||
And to check if it works: | ||
```bash | ||
$ heroku logs --tail | ||
``` | ||
### Heroku configuration | ||
`SALIEN_CONFIG_V2` is just an array of config that will be passed to `SalienScript` constructor. | ||
If you only have one account, then your config will look like this: | ||
```JSON | ||
[ | ||
{ | ||
"token": "12345" | ||
} | ||
] | ||
``` | ||
The only mandatory key for each account is `token` and you can add extra keys to this config such as `clan`, `name` or `selectedPlanetId`: | ||
```JSON | ||
[ | ||
{ | ||
"token": "12345", | ||
"clan": "67890", | ||
"name": "first_acc", | ||
"selectedPlanetId": "28" | ||
} | ||
] | ||
``` | ||
If you had two accounts for example; | ||
* one named `first_acc` with a token of `123` and a group of `98712` | ||
* one named `second_acc` with a token of `456` and a group of `67890` | ||
then you would make your config look like this: | ||
```JSON | ||
[ | ||
{ | ||
"token": "123", | ||
"clan": "98712", | ||
"name": "first_acc" | ||
}, | ||
{ | ||
"token": "456", | ||
"clan": "67890", | ||
"name": "second_acc" | ||
} | ||
] | ||
``` | ||
### Updating | ||
#### Easy | ||
The easiest way to update script on heroku is to just delete your old app and create new. | ||
You can also link your Heroku app to your Dropbox account. To do that, [download this repository](https://github.com/South-Paw/salien-script-js/archive/master.zip) as a zip archive, and unpack it to the folder created on your Dropbox. | ||
For more info on this, visit: https://devcenter.heroku.com/articles/dropbox-sync | ||
#### Medium | ||
1. Fork this repo on github. | ||
2. In your heroku app control panel, at Deploy tab, connect your app to a forked repository and enable automatic deploys. | ||
3. When update comes, merge changes into your repo on github: | ||
1. Create new pull request. | ||
2. Select your repo's master branch as base fork, and South-Paw/salien-script-js master branch as head fork. | ||
3. Click on a big green button "Merge pull request". | ||
For more info on connecting github account, visit: https://devcenter.heroku.com/articles/github-integration | ||
For more info on syncing fork using web interface, check this tutorial: https://www.sitepoint.com/quick-tip-sync-your-fork-with-the-original-without-the-cli/ | ||
#### Hard | ||
If you created your app using web-console, you need to clone heroku repo first | ||
@@ -148,0 +241,0 @@ |
799
src/index.js
@@ -28,298 +28,95 @@ /** | ||
const chalk = require('chalk'); | ||
const dateFormat = require('dateformat'); | ||
const delay = require('delay'); | ||
const fetch = require('fetch-retry'); | ||
const checkForUpdate = require('update-check'); | ||
const { | ||
getPlayerInfo, | ||
getPlanets, | ||
getPlanet, | ||
representClan, | ||
leaveGame, | ||
joinPlanet, | ||
joinZone, | ||
reportScore, | ||
} = require('./api/index'); | ||
const { getZoneDifficultyName, getScoreForZone, getAllPlanetStates, getBestPlanetAndZone } = require('./game/index'); | ||
const { SalienScriptRestart } = require('./exceptions'); | ||
const { getPercentage, updateCheck, utilLogger } = require('./util'); | ||
const pkg = require('../package.json'); | ||
const logger = (name, ...messages) => { | ||
let message = chalk.white(dateFormat(new Date(), '[HH:MM:ss]')); | ||
if (name) { | ||
message += ` (${name})`; | ||
} | ||
// eslint-disable-next-line no-console | ||
console.log(message, ...messages); | ||
}; | ||
// eslint-disable-next-line no-console | ||
const debug = message => console.log(`${JSON.stringify(message, 0, 2)}`); | ||
const getPercentage = number => Number(number * 100).toFixed(2); | ||
const getDifficultyName = zone => { | ||
const boss = zone.type === 4 ? 'BOSS - ' : ''; | ||
switch (zone.difficulty) { | ||
case 3: | ||
return `${boss}Hard`; | ||
case 2: | ||
return `${boss}Medium`; | ||
case 1: | ||
return `${boss}Low`; | ||
default: | ||
return `${boss}${zone.difficulty}`; | ||
} | ||
}; | ||
const getScoreForZone = zone => { | ||
let score; | ||
switch (zone.difficulty) { | ||
case 1: | ||
score = 5; | ||
break; | ||
case 2: | ||
score = 10; | ||
break; | ||
case 3: | ||
score = 20; | ||
break; | ||
default: | ||
score = 5; | ||
break; | ||
} | ||
return score * 120; | ||
}; | ||
const formatPlanetName = name => | ||
name | ||
.replace('#TerritoryControl_Planet', '') | ||
.split('_') | ||
.join(' '); | ||
const updateCheck = async name => { | ||
let hasUpdate = null; | ||
try { | ||
hasUpdate = await checkForUpdate(pkg, { interval: 120000 }); | ||
} catch (err) { | ||
logger(name, ` ${chalk.bgRed(' UpdateCheck ')}`, chalk.red(`Failed to check for updates: ${err}`)); | ||
} | ||
if (await hasUpdate) { | ||
logger( | ||
name, | ||
` ${chalk.bgMagenta(' UpdateCheck ')}`, | ||
`The latest version is ${chalk.bgCyan(hasUpdate.latest)}. Please update!`, | ||
); | ||
logger( | ||
name, | ||
` ${chalk.bgMagenta(' UpdateCheck ')}`, | ||
`To update, stop this script and run: ${chalk.bgCyan('npm i -g salien-script-js')}`, | ||
); | ||
// eslint-disable-next-line | ||
console.log(''); | ||
} | ||
}; | ||
class SalienScriptException { | ||
constructor(message) { | ||
this.name = 'SalienScriptException'; | ||
this.message = message; | ||
} | ||
} | ||
class SalienScriptRestart { | ||
constructor(message) { | ||
this.name = 'SalienScriptRestart'; | ||
this.message = message; | ||
} | ||
} | ||
class SalienScript { | ||
constructor({ token, clan, selectedPlanetId, name = null }) { | ||
constructor({ token, clan, name = null, logRequests = false }) { | ||
// user defined variables | ||
this.token = token; | ||
this.clan = clan; | ||
this.selectedPlanetId = selectedPlanetId; | ||
this.clanId = clan; | ||
this.name = name; | ||
this.isSilentRequest = !logRequests; | ||
this.maxRetries = 3; | ||
// script variables | ||
this.startTime = null; | ||
this.knownPlanets = new Map(); | ||
this.currentPlanetAndZone = null; | ||
this.steamThinksPlanet = null; | ||
this.skippedPlanets = []; | ||
// script variables that don't get reset | ||
this.clanCheckDone = false; | ||
// script defaults | ||
this.gameWaitTimeSec = 110; | ||
this.defaultDelayMs = 5000; | ||
this.defaultDelaySec = this.defaultDelayMs / 1000; | ||
this.cutoff = 0.99; | ||
} | ||
resetScript() { | ||
this.startTime = null; | ||
this.waitTime = 110; | ||
this.hasJoinedClan = false; | ||
this.isUpdateChecked = false; | ||
this.currentPlanetId = null; | ||
this.steamPlanetId = null; | ||
this.knownPlanets = new Map(); | ||
this.knownPlanetIds = []; | ||
this.currentPlanetAndZone = null; | ||
this.steamThinksPlanet = null; | ||
this.skippedPlanets = []; | ||
} | ||
async RequestAPI(method, params, maxRetries, additionalOptions = {}) { | ||
let url = `https://community.steam-api.com/${method}/v0001`; | ||
if (params) { | ||
url += '/?'; | ||
params.forEach(param => { | ||
url += `${param}&`; | ||
}); | ||
url = url.substring(0, url.length - 1); | ||
} | ||
const options = { | ||
retries: 3, | ||
retryDelay: 1000, | ||
headers: { | ||
Accept: '*/*', | ||
Origin: 'https://steamcommunity.com', | ||
Referer: 'https://steamcommunity.com/saliengame/play/', | ||
'User-Agent': | ||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36', | ||
}, | ||
...additionalOptions, | ||
}; | ||
let request; | ||
let response; | ||
let retries = 0; | ||
while (!response && retries < maxRetries) { | ||
try { | ||
logger(this.name, chalk.blue(` Sending ${method}...`)); | ||
request = await fetch(url, options); | ||
response = await request.json(); | ||
} catch (e) { | ||
// TODO there is some error handling/messaging we could implement here | ||
// see: https://github.com/SteamDatabase/SalienCheat/blob/ac3a28aeb0446ff80cf6a6e1370fd5ef42e75aa2/cheat.php#L533 | ||
logger(this.name, ` ${chalk.bgRed(`${e.name}:`)} ${chalk.red(`For ${method}`)}`); | ||
debug(e); | ||
retries += 1; | ||
if (retries < maxRetries) { | ||
logger(this.name, chalk.yellow(` Retrying ${method} in ${this.defaultDelaySec} seconds...`)); | ||
} else { | ||
throw new SalienScriptException(`Failed ${method} after ${retries} retries`); | ||
} | ||
await delay(this.defaultDelayMs); | ||
} | ||
} | ||
return response.response; | ||
logger(message, error) { | ||
utilLogger(this.name, { message, error }); | ||
} | ||
async ApiGetPlanets() { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/GetPlanets', | ||
['active_only=1'], | ||
this.maxRetries, | ||
); | ||
return response.planets; | ||
async apiGetPlayerInfo() { | ||
return getPlayerInfo(this.token, (m, e) => this.logger(m, e), this.isSilentRequest); | ||
} | ||
async ApiGetPlanet(planetId) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/GetPlanet', | ||
[`id=${planetId}`, 'language=english'], | ||
this.maxRetries, | ||
); | ||
return response.planets[0]; | ||
async apiGetPlanets() { | ||
return getPlanets((m, e) => this.logger(m, e), this.isSilentRequest); | ||
} | ||
async ApiGetPlayerInfo() { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/GetPlayerInfo', | ||
[`access_token=${this.token}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
async apiGetPlanet(planetId) { | ||
return getPlanet(planetId, (m, e) => this.logger(m, e), this.isSilentRequest); | ||
} | ||
async ApiRepresentClan(clanId) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/RepresentClan', | ||
[`access_token=${this.token}`, `clanid=${clanId}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
async apiRepresentClan(clanId) { | ||
return representClan(this.token, clanId, (m, e) => this.logger(m, e), this.isSilentRequest); | ||
} | ||
async ApiLeaveGame(gameId) { | ||
const response = await this.RequestAPI( | ||
'IMiniGameService/LeaveGame', | ||
[`access_token=${this.token}`, `gameid=${gameId}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
async apiLeaveGame(gameId) { | ||
return leaveGame(this.token, gameId, (m, e) => this.logger(m, e), this.isSilentRequest); | ||
} | ||
async ApiJoinPlanet(planetId) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/JoinPlanet', | ||
[`access_token=${this.token}`, `id=${planetId}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
async apiJoinPlanet(planetId) { | ||
return joinPlanet(this.token, planetId, (m, e) => this.logger(m, e), this.isSilentRequest); | ||
} | ||
async ApiJoinZone(zoneId) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/JoinZone', | ||
[`access_token=${this.token}`, `zone_position=${zoneId}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
async apiJoinZone(zoneId) { | ||
return joinZone(this.token, zoneId, (m, e) => this.logger(m, e), this.isSilentRequest); | ||
} | ||
async ApiReportScore(score) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/ReportScore', | ||
[`access_token=${this.token}`, `score=${score}`, `language=english`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
async apiReportScore(score) { | ||
return reportScore(this.token, score, (m, e) => this.logger(m, e), this.isSilentRequest); | ||
} | ||
async leaveCurrentGame(leaveCurrentPlanet = 0) { | ||
let playerInfo; | ||
async leaveCurrentGame(requestedPlanetId = 0) { | ||
const playerInfo = await this.apiGetPlayerInfo(); | ||
while (!playerInfo) { | ||
playerInfo = await this.ApiGetPlayerInfo(); | ||
} | ||
if (playerInfo.active_zone_game) { | ||
await this.ApiLeaveGame(playerInfo.active_zone_game); | ||
await this.apiLeaveGame(playerInfo.active_zone_game); | ||
} | ||
if (this.clan && !this.hasJoinedClan && playerInfo.clan_info && playerInfo.clan_info.accountid !== this.clan) { | ||
logger(this.name, ` Attempting to join groupId: ${chalk.yellow(this.clan)}`); | ||
await this.ApiRepresentClan(this.clan); | ||
let clanCheckInfo = null; | ||
while (!clanCheckInfo) { | ||
clanCheckInfo = await this.ApiGetPlayerInfo(); | ||
} | ||
if (clanCheckInfo.clan_info) { | ||
logger(this.name, ` ${chalk.bgCyan(` Joined group: ${clanCheckInfo.clan_info.name} `)}`); | ||
logger( | ||
this.name, | ||
` ${chalk.yellow("If the name above isn't expected, check if you're actually a member of that group")}`, | ||
); | ||
} | ||
this.hasJoinedClan = true; | ||
} | ||
if (!playerInfo.active_planet) { | ||
@@ -331,9 +128,9 @@ return 0; | ||
if (leaveCurrentPlanet > 0 && leaveCurrentPlanet !== activePlanet) { | ||
logger( | ||
this.name, | ||
`>> Leaving planet ${chalk.yellow(activePlanet)}, because we want to be on ${chalk.yellow(leaveCurrentPlanet)}`, | ||
); | ||
if (requestedPlanetId > 0 && requestedPlanetId !== activePlanet) { | ||
let message = `>> Leaving planet ${chalk.yellow(activePlanet)}, because`; | ||
message += ` we want to be on ${chalk.yellow(requestedPlanetId)}`; | ||
await this.ApiLeaveGame(activePlanet); | ||
this.logger(message); | ||
await this.apiLeaveGame(activePlanet); | ||
} | ||
@@ -344,353 +141,63 @@ | ||
async getFirstAvailableZone(planetId) { | ||
let planet; | ||
async doClanSetup() { | ||
let playerInfo = await this.apiGetPlayerInfo(); | ||
while (!planet) { | ||
planet = await this.ApiGetPlanet(planetId); | ||
} | ||
if (this.clanId && !this.clanCheckDone && playerInfo.clan_info) { | ||
this.logger(`Attempting to join group id: ${chalk.yellow(this.clanId)}`); | ||
if (!planet.zones) { | ||
return null; | ||
} | ||
await this.apiRepresentClan(this.clanId); | ||
const planetName = planet.state.name; | ||
const planetCaptured = planet.state.capture_progress; | ||
const planetPlayers = planet.state.current_players; | ||
const { zones } = planet; | ||
playerInfo = await this.apiGetPlayerInfo(); | ||
const cleanZones = []; | ||
if (playerInfo.clan_info) { | ||
this.logger(chalk.bgCyan(` Joined group: ${playerInfo.clan_info.name} `)); | ||
this.logger(chalk.yellow("If the name above isn't expected, check if you're actually a member of that group")); | ||
let hardZones = 0; | ||
let mediumZones = 0; | ||
let easyZones = 0; | ||
let unknownZones = 0; | ||
let toReturn = null; | ||
zones.forEach(zone => { | ||
if (zone.captured) { | ||
return; | ||
this.clanCheckDone = true; | ||
} | ||
if (zone.type !== 3) { | ||
logger(this.name, chalk.red(`!! Unknown zone type: ${zone.type}`)); | ||
} | ||
// If a zone is close to completion, skip it because Valve does not reward points and replies with 42 NoMatch | ||
if (zone.capture_progress && zone.capture_progress > 0.97) { | ||
return; | ||
} | ||
switch (zone.difficulty) { | ||
case 3: | ||
hardZones += 1; | ||
break; | ||
case 2: | ||
mediumZones += 1; | ||
break; | ||
case 1: | ||
easyZones += 1; | ||
break; | ||
default: | ||
unknownZones += 1; | ||
break; | ||
} | ||
// Always join boss zone | ||
if (zone.type === 4) { | ||
toReturn = { | ||
hardZones, | ||
mediumZones, | ||
easyZones, | ||
unknownZones, | ||
planetPlayers, | ||
planetCaptured, | ||
planetName, | ||
...zone, | ||
}; | ||
return; | ||
} | ||
cleanZones.push(zone); | ||
}); | ||
if (toReturn) { | ||
return toReturn; | ||
console.log(''); // eslint-disable-line no-console | ||
} | ||
if (cleanZones.length < 0) { | ||
return false; | ||
} | ||
cleanZones.sort((a, b) => { | ||
if (b.difficulty === a.difficulty) { | ||
return b.zone_position - a.zone_position; | ||
} | ||
return b.difficulty - a.difficulty; | ||
}); | ||
return { | ||
hardZones, | ||
mediumZones, | ||
easyZones, | ||
unknownZones, | ||
planetPlayers, | ||
planetCaptured, | ||
planetName, | ||
...cleanZones[0], | ||
}; | ||
} | ||
async isThereAnyNewPlanets(knownPlanetIds) { | ||
logger(this.name, ' Checking for any new planets...'); | ||
async doGameSetup() { | ||
const planets = await this.apiGetPlanets(); | ||
let planets; | ||
this.knownPlanets = await getAllPlanetStates( | ||
planets, | ||
this.cutoff, | ||
(m, e) => this.logger(m, e), | ||
this.isSilentRequest, | ||
); | ||
while (!planets) { | ||
planets = await this.ApiGetPlanets(); | ||
} | ||
this.currentPlanetAndZone = await getBestPlanetAndZone(this.knownPlanets, (m, e) => this.logger(m, e)); | ||
if (!planets) { | ||
return false; | ||
} | ||
const zoneCapturePercent = getPercentage(this.currentPlanetAndZone.bestZone.capture_progress); | ||
let hasNewPlanet = false; | ||
let zoneMsg = `>> Selected Next Zone ${chalk.green(this.currentPlanetAndZone.bestZone.zone_position)}`; | ||
zoneMsg += ` on Planet ${chalk.green(this.currentPlanetAndZone.id)}`; | ||
zoneMsg += ` (Captured: ${chalk.yellow(`${zoneCapturePercent}%`.padStart(6))}`; | ||
zoneMsg += ` - Difficulty: ${chalk.yellow(getZoneDifficultyName(this.currentPlanetAndZone.bestZone))})`; | ||
await planets.forEach(planet => { | ||
if (!knownPlanetIds.includes(planet.id)) { | ||
hasNewPlanet = true; | ||
} | ||
}); | ||
this.logger(zoneMsg); | ||
return hasNewPlanet; | ||
console.log(''); // eslint-disable-line no-console | ||
} | ||
async setupGame() { | ||
const planets = await this.ApiGetPlanets(); | ||
async doGameLoop() { | ||
while (this.currentPlanetAndZone.id !== this.steamThinksPlanet) { | ||
this.steamThinksPlanet = await this.leaveCurrentGame(this.currentPlanetAndZone.id); | ||
if (!planets) { | ||
throw new SalienScriptException("Didn't find any planets."); | ||
} | ||
if (this.currentPlanetAndZone.id !== this.steamThinksPlanet) { | ||
await this.apiJoinPlanet(this.currentPlanetAndZone.id); | ||
logger(this.name, ' Getting first available planet...'); | ||
try { | ||
// Patch the apiGetPlanets response with zones from apiGetPlanet | ||
const mappedPlanets = await Promise.all( | ||
planets.map(async planet => { | ||
const object = Object.assign({}, planet); | ||
const currentPlanet = await this.ApiGetPlanet(planet.id); | ||
object.zones = currentPlanet.zones; | ||
return object; | ||
}), | ||
); | ||
mappedPlanets.forEach(planet => { | ||
let hardZones = 0; | ||
let mediumZones = 0; | ||
let easyZones = 0; | ||
let unknownZones = 0; | ||
let hasBossZone = false; | ||
// Filter out captured zones + determine zone types | ||
planet.zones.forEach(zone => { | ||
if ((zone.capture_progress && zone.capture_progress > 0.97) || zone.captured) { | ||
return; | ||
} | ||
if (zone.type === 4) { | ||
hasBossZone = true; | ||
} else if (zone.type !== 3) { | ||
logger(this.name, chalk.red(`!! Unknown zone type: ${zone.type}`)); | ||
} | ||
switch (zone.difficulty) { | ||
case 3: | ||
hardZones += 1; | ||
break; | ||
case 2: | ||
mediumZones += 1; | ||
break; | ||
case 1: | ||
easyZones += 1; | ||
break; | ||
default: | ||
unknownZones += 1; | ||
break; | ||
} | ||
}); | ||
this.knownPlanetIds.push(planet.id); | ||
this.knownPlanets.set(planet.id, { | ||
hardZones, | ||
mediumZones, | ||
easyZones, | ||
unknownZones, | ||
hasBossZone, | ||
...planet, | ||
}); | ||
const capturedPercent = getPercentage(planet.state.capture_progress).toString(); | ||
const planetName = formatPlanetName(planet.state.name); | ||
let logMsg = `>> Planet: ${chalk.green(planet.id)}`; | ||
logMsg += ` - Hard: ${chalk.yellow(hardZones)} - Medium: ${chalk.yellow(mediumZones)}`; | ||
logMsg += ` - Easy: ${chalk.yellow(easyZones)} - Captured: ${chalk.yellow(capturedPercent)}%`; | ||
logMsg += ` - Players: ${chalk.yellow(planet.state.current_players.toLocaleString())}`; | ||
logMsg += ` (${chalk.green(planetName)})`; | ||
logger(this.name, logMsg); | ||
if (unknownZones) { | ||
logger(this.name, `>> Unknown zones found: ${chalk.yellow(unknownZones)}`); | ||
} | ||
}); | ||
this.knownPlanetIds.forEach(id => { | ||
const planet = this.knownPlanets.get(id); | ||
if (planet.hasBossZone) { | ||
this.currentPlanetId = planet.id; | ||
throw new SalienScriptException('Boss zone found!'); | ||
} | ||
}); | ||
} catch (e) { | ||
if (e.name === 'SalienScriptException' && e.message === 'Boss zone found!') { | ||
logger( | ||
this.name, | ||
chalk.green(`>> Planet ${chalk.yellow(this.currentPlanetId)} has a boss zone, selecting this planet`), | ||
); | ||
} else { | ||
debug(e); | ||
throw new SalienScriptException(e.message); | ||
this.steamThinksPlanet = await this.leaveCurrentGame(); | ||
} | ||
} | ||
// FIXME this logic might be able to be cleaned up | ||
const priority = ['hardZones', 'mediumZones', 'easyZones']; | ||
const zone = await this.apiJoinZone(this.currentPlanetAndZone.bestZone.zone_position); | ||
if (!this.currentPlanetId) { | ||
const sortedPlanetIds = this.knownPlanetIds.sort((a, b) => { | ||
const planetA = this.knownPlanets.get(a); | ||
const planetB = this.knownPlanets.get(b); | ||
for (let i = 0; i < priority.length; i += 1) { | ||
const key = priority[i]; | ||
if (planetA[key] !== planetB[key]) { | ||
return planetA[key] - planetB[key]; | ||
} | ||
} | ||
return Number(planetA.id) - Number(planetB.id); | ||
}); | ||
// Attempt to get selected planet from provided planet id | ||
const selectedPlanet = this.knownPlanets.get(this.selectedPlanetId); | ||
// If the selected planet is not valid, handle it | ||
if (!selectedPlanet || this.skippedPlanets.includes(this.selectedPlanetId)) { | ||
// Only log if a planet was selected | ||
if (this.selectedPlanetId) { | ||
logger( | ||
this.name, | ||
`>> Selected planet ${chalk.yellow( | ||
this.selectedPlanetId, | ||
)} not available. Selecting next available planet...`, | ||
); | ||
} | ||
for (let i = 0; i < priority.length; i += 1) { | ||
sortedPlanetIds.forEach(planetId => { | ||
const planet = this.knownPlanets.get(planetId); | ||
if (this.skippedPlanets.includes(planetId) || !planet[priority[i]]) { | ||
return; | ||
} | ||
if (!planet.state.captured && !this.currentPlanetId) { | ||
const planetName = formatPlanetName(planet.state.name); | ||
logger(this.name, `>> Selected planet ${chalk.green(planetId)} (${chalk.green(planetName)})`); | ||
this.currentPlanetId = planetId; | ||
} | ||
}); | ||
} | ||
} else if (!selectedPlanet.state.captured && !this.currentPlanetId) { | ||
const planetName = formatPlanetName(selectedPlanet.state.name); | ||
logger(this.name, `>> Selected planet ${chalk.green(this.selectedPlanetId)} (${chalk.green(planetName)})`); | ||
this.currentPlanetId = this.selectedPlanetId; | ||
} | ||
if (!this.currentPlanetId) { | ||
// If there are no planets with hard or medium zones, just return first one | ||
this.currentPlanetId = planets[0].id; | ||
} | ||
} | ||
while (this.currentPlanetId !== this.steamPlanetId) { | ||
// Leave current game before trying to switch planets (it will report InvalidState otherwise) | ||
this.steamPlanetId = await this.leaveCurrentGame(this.currentPlanetId); | ||
if (this.currentPlanetId !== this.steamPlanetId) { | ||
await this.ApiJoinPlanet(this.currentPlanetId); | ||
this.steamPlanetId = await this.leaveCurrentGame(); | ||
} | ||
} | ||
} | ||
async gameLoop() { | ||
console.log(''); // eslint-disable-line no-console | ||
await updateCheck(this.name); | ||
// Scan planets every 10 minutes | ||
if (new Date().getTime() - this.startTime > 600000) { | ||
throw new SalienScriptRestart('!! Re-scanning for new planets'); | ||
} | ||
let zone; | ||
while (!zone) { | ||
zone = await this.getFirstAvailableZone(this.currentPlanetId); | ||
} | ||
if (zone === false) { | ||
this.skippedPlanets.push(this.currentPlanetId); | ||
throw new SalienScriptRestart('!! There are no zones to join in this planet'); | ||
} | ||
const { hardZones, mediumZones, easyZones, planetCaptured, planetPlayers } = zone; | ||
if (!hardZones) { | ||
if (!mediumZones && new Date().getTime() - this.startTime > this.waitTime * 1000) { | ||
throw new SalienScriptRestart('!! No hard or medium zones on this planet'); | ||
} | ||
const hasNewPlanet = await this.isThereAnyNewPlanets(this.knownPlanetIds); | ||
if (hasNewPlanet) { | ||
throw new SalienScriptRestart('!! Detected a new planet'); | ||
} | ||
} | ||
const planetName = formatPlanetName(zone.planetName); | ||
const position = zone.zone_position; | ||
zone = null; | ||
while (!zone) { | ||
zone = await this.ApiJoinZone(position); | ||
} | ||
// rescan if we failed to join | ||
if (!zone.zone_info) { | ||
throw new SalienScriptRestart('!! Failed to join a zone'); | ||
throw new SalienScriptRestart('!! Failed to join a zone', zone); | ||
} | ||
@@ -700,63 +207,64 @@ | ||
const capturedPercent = getPercentage(planetCaptured).toString(); | ||
const zoneCapturePercent = getPercentage(zoneInfo.capture_progress); | ||
let planetLogMsg = `>> Planet ${chalk.green(this.currentPlanetId)} - Captured: ${chalk.yellow(capturedPercent)}%`; | ||
planetLogMsg += ` - Hard: ${chalk.yellow(hardZones)} - Medium: ${chalk.yellow(mediumZones)}`; | ||
planetLogMsg += ` - Easy: ${chalk.yellow(easyZones)}`; | ||
planetLogMsg += ` - Players: ${chalk.yellow(planetPlayers.toLocaleString())} (${chalk.green(planetName)})`; | ||
let joinMsg = `>> Joined Zone ${chalk.green(zoneInfo.zone_position)}`; | ||
joinMsg += ` on Planet ${chalk.green(this.currentPlanetAndZone.id)}`; | ||
joinMsg += ` (Captured: ${chalk.yellow(`${zoneCapturePercent}%`.padStart(6))}`; | ||
joinMsg += ` - Difficulty: ${chalk.yellow(getZoneDifficultyName(this.currentPlanetAndZone.bestZone))})`; | ||
logger(this.name, planetLogMsg); | ||
this.logger(joinMsg); | ||
const capturedProgress = !zoneInfo.capture_progress ? 0 : getPercentage(zoneInfo.capture_progress).toString(); | ||
if (zoneInfo.top_clans) { | ||
this.logger(`-- Top Clans:${zoneInfo.top_clans.map(({ name }) => ` ${name}`)}`); | ||
} | ||
let zoneLogMsg = `>> Zone ${chalk.green(zoneInfo.zone_position)} - Captured: ${chalk.yellow(capturedProgress)}%`; | ||
zoneLogMsg += ` - Difficulty: ${chalk.yellow(getDifficultyName(zoneInfo))}`; | ||
console.log(''); // eslint-disable-line no-console | ||
this.logger(`${chalk.bgMagenta(` Waiting ${this.gameWaitTimeSec} seconds for round to finish... `)}`); | ||
logger(this.name, zoneLogMsg); | ||
// 10 seconds before the score is reported, get the next planet and zone we should focus on. | ||
setTimeout(async () => { | ||
await this.doGameSetup(); | ||
}, (this.gameWaitTimeSec - 10) * 1000); | ||
if (zoneInfo.top_clans) { | ||
logger(this.name, `-- Top Clans:${zoneInfo.top_clans.map(({ name }) => ` ${name}`)}`); | ||
} | ||
await delay(this.gameWaitTimeSec * 1000); | ||
logger(this.name, ` ${chalk.bgMagenta(` Waiting ${this.waitTime} seconds for round to finish... `)}`); | ||
const score = await this.apiReportScore(getScoreForZone(zoneInfo)); | ||
await delay(this.waitTime * 1000); | ||
// cause the game's api returns some numbers as strings and others as numbers | ||
const oldScore = Number(score.old_score); | ||
const newScore = Number(score.new_score); | ||
const nextLevelScore = Number(score.next_level_score); | ||
const newLevel = Number(score.new_level); | ||
const report = await this.ApiReportScore(getScoreForZone(zoneInfo)); | ||
if (newScore) { | ||
const earnedXp = newScore - oldScore; | ||
const nextLevelPercent = getPercentage(newScore / nextLevelScore); | ||
if (report.new_score) { | ||
const earnedXp = report.new_score - report.old_score; | ||
const nextLevelPercent = getPercentage(report.new_score / report.next_level_score); | ||
console.log(''); // eslint-disable-line no-console | ||
let currentLevelMsg = `>> XP Earned: ${chalk.green(earnedXp.toLocaleString())}`; | ||
currentLevelMsg += ` (${chalk.yellow(report.old_score.toLocaleString())} XP`; | ||
currentLevelMsg += ` => ${chalk.green(report.new_score.toLocaleString())} XP)`; | ||
currentLevelMsg += ` - Current Level: ${chalk.green(report.new_level)} (${nextLevelPercent}% to next)`; | ||
let currentLevelMsg = `>> Score: ${chalk.cyan(Number(newScore).toLocaleString())}`; | ||
currentLevelMsg += ` (${chalk.green(`+${earnedXp.toLocaleString()}`)})`; | ||
currentLevelMsg += ` - Current Level: ${chalk.green(newLevel)} (${nextLevelPercent}%)`; | ||
logger(this.name, currentLevelMsg); | ||
this.logger(currentLevelMsg); | ||
const remainingXp = report.next_level_score - report.new_score; | ||
const remainingXp = nextLevelScore - newScore; | ||
const timeRemaining = | ||
((report.next_level_score - report.new_score) / getScoreForZone(zoneInfo)) * (this.waitTime / 60); | ||
const timeRemaining = ((nextLevelScore - newScore) / getScoreForZone(zoneInfo)) * (this.gameWaitTimeSec / 60); | ||
const hoursRemaining = Math.floor(timeRemaining / 60); | ||
const minutesRemaining = Math.round(timeRemaining % 60); | ||
const levelEta = `${hoursRemaining}h ${minutesRemaining}m`; | ||
const levelEta = `${hoursRemaining}h ${hoursRemaining === 0 && minutesRemaining === 0 ? 2 : minutesRemaining}m`; | ||
let nextLevelMsg = `>> Next Level: ${chalk.yellow(report.next_level_score.toLocaleString())} XP`; | ||
nextLevelMsg += ` - Remaining: ${chalk.yellow(remainingXp.toLocaleString())} XP - ETA: ${chalk.green(levelEta)}`; | ||
let nextLevelMsg = `>> Next Level: ${chalk.yellow(nextLevelScore.toLocaleString())} XP`; | ||
nextLevelMsg += ` - Remaining: ${chalk.yellow(remainingXp.toLocaleString())} XP`; | ||
nextLevelMsg += ` - ETA: ${chalk.green(levelEta)}\n`; | ||
logger(this.name, nextLevelMsg); | ||
this.logger(nextLevelMsg); | ||
} | ||
// Some users get stuck in games after calling ReportScore, so we manually leave to fix this | ||
let leftGame; | ||
const leavingGame = await this.leaveCurrentGame(this.currentPlanetAndZone.id); | ||
while (!leftGame) { | ||
leftGame = await this.leaveCurrentGame(this.currentPlanetId); | ||
if (leavingGame !== this.currentPlanetAndZone.id) { | ||
throw new SalienScriptRestart(`!! Wrong current Planet ${chalk.yellow(leavingGame)}`); | ||
} | ||
if (leftGame !== this.currentPlanetId) { | ||
throw new SalienScriptRestart('!! Wrong current planet'); | ||
} | ||
} | ||
@@ -767,30 +275,25 @@ | ||
// Reset all variables to default values every time init() is called | ||
this.currentPlanetId = null; | ||
this.knownPlanetIds = []; | ||
this.knownPlanets = new Map(); | ||
this.skippedPlanets = []; | ||
console.log(''); // eslint-disable-line no-console | ||
this.logger(chalk.bgGreen(` Started SalienScript | Version: ${pkg.version} `)); | ||
this.logger(chalk.bgCyan(' Thanks for choosing https://github.com/South-Paw/salien-script-js ')); | ||
this.logger(chalk.bgCyan(' Remeber to drop us a ⭐ star on the project if you appreciate this script! ')); | ||
console.log(''); // eslint-disable-line no-console | ||
this.resetScript(); | ||
try { | ||
logger(this.name, ` ${chalk.bgGreen(` Started SalienScript | Version: ${pkg.version} `)}`); | ||
logger( | ||
this.name, | ||
` ${chalk.bgCyan(` If you appreciate the script, please remember to leave a ⭐ star ⭐ on the project! `)}`, | ||
); | ||
await this.doClanSetup(); | ||
await this.setupGame(); | ||
await this.doGameSetup(); | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
await this.gameLoop(); | ||
await updateCheck(this.name); | ||
await this.doGameLoop(); | ||
} | ||
} catch (e) { | ||
logger(this.name, ` ${chalk.bgRed(`${e.name}:`)} ${chalk.red(e.message)}`); | ||
this.logger(`${chalk.bgRed(`${e.name}:`)} ${chalk.red(e.message)}`, e.name !== 'SalienScriptRestart' ? e : null); | ||
this.logger(`${chalk.bgMagenta(` Script will restart in ${this.defaultDelaySec} seconds... `)}\n\n`); | ||
if (e.name !== 'SalienScriptRestart') { | ||
debug(e); | ||
} | ||
logger(this.name, ` ${chalk.bgMagenta(` Script will restart in ${this.defaultDelaySec} seconds... `)}\n\n`); | ||
await delay(this.defaultDelayMs); | ||
@@ -797,0 +300,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
43209
10
766
289
9
18
+ Addednode-fetch@^2.1.2
+ Addednode-fetch@2.7.0(transitive)
+ Addedtr46@0.0.3(transitive)
+ Addedwebidl-conversions@3.0.1(transitive)
+ Addedwhatwg-url@5.0.0(transitive)
- Removedfetch-retry@^1.2.1
- Removedencoding@0.1.13(transitive)
- Removedes6-promise@4.2.8(transitive)
- Removedfetch-retry@1.2.1(transitive)
- Removediconv-lite@0.6.3(transitive)
- Removedis-stream@1.1.0(transitive)
- Removedisomorphic-fetch@2.2.1(transitive)
- Removednode-fetch@1.7.3(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedwhatwg-fetch@3.6.20(transitive)