salien-script-js
Advanced tools
Comparing version 0.0.2 to 0.0.3
{ | ||
"name": "salien-script-js", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "Scripting the Steam Salien Sale minigame, the proper way.", | ||
@@ -24,7 +24,16 @@ "keywords": [ | ||
"url": "http://southpaw.co.nz/" | ||
}, | ||
{ | ||
"name": "Resi Respati", | ||
"email": "resir014@gmail.com", | ||
"url": "https://resir014.xyz/" | ||
} | ||
], | ||
"files": [ | ||
"src" | ||
"src", | ||
"cli.js" | ||
], | ||
"bin": { | ||
"salien-script-js": "cli.js" | ||
}, | ||
"main": "src/index.js", | ||
@@ -35,7 +44,25 @@ "repository": { | ||
}, | ||
"scripts": { | ||
"lint": "eslint ./src", | ||
"prepublishOnly": "npm run lint", | ||
"prettier": "prettier --write \"src/**/*.js\"" | ||
}, | ||
"dependencies": { | ||
"chalk": "^2.4.1", | ||
"dateformat": "^3.0.3", | ||
"delay": "^3.0.0", | ||
"fetch-retry": "^1.2.1" | ||
"fetch-retry": "^1.2.1", | ||
"meow": "^5.0.0" | ||
}, | ||
"devDependencies": {} | ||
"devDependencies": { | ||
"eslint": "^5.0.0", | ||
"eslint-config-airbnb-base": "^13.0.0", | ||
"eslint-config-prettier": "^2.9.0", | ||
"eslint-plugin-import": "^2.12.0", | ||
"eslint-plugin-prettier": "^2.6.0", | ||
"prettier": "^1.13.5" | ||
}, | ||
"engines": { | ||
"node": ">=10.0.0" | ||
} | ||
} |
@@ -5,18 +5,67 @@ # salien-script-js | ||
> A Node.js implementation of https://github.com/SteamDatabase/SalienCheat by [xPaw](https://github.com/xPaw) | ||
> A Node.js implementation of https://github.com/SteamDatabase/SalienCheat by [xPaw](https://github.com/xPaw) with additional features! | ||
[![salien-script-js on npm](https://nodei.co/npm/salien-script-js.png)](https://nodei.co/npm/salien-script-js/) | ||
[![CI Status](https://img.shields.io/travis/South-Paw/salien-script-js/rework.svg)](https://travis-ci.org/South-Paw/salien-script-js) | ||
[![Dependencies](https://david-dm.org/South-Paw/salien-script-js/rework.svg)](https://david-dm.org/South-Paw/salien-script-js/rework) | ||
[![Dev Dependencies](https://david-dm.org/South-Paw/salien-script-js/rework/dev-status.svg)](https://david-dm.org/South-Paw/salien-script-js/rework?type=dev) | ||
--- | ||
## How to use this (download repo) | ||
## 🌈 Features | ||
1. Log into Steam in your browser | ||
2. Join https://steamcommunity.com/groups/SteamDB (needed to represent captures) | ||
3. Open https://steamcommunity.com/saliengame/gettoken and find the bit that looks like `"token":"xxxxxxxx"` | ||
4. Create a new file next to `run.js`, call it `token.txt` and paste only the `xxxxxxxx` part of your token in | ||
5. Install the latest version [Node.js](https://nodejs.org/en/) | ||
6. Open command line in the folder | ||
* Tip: ['Shift + Right Click' in explorer -> 'Open Command Line/Powershell here'](http://i.imgur.com/6FJcydX.png) | ||
7. Type `npm i` to get dependencies | ||
8. Run the script by typing `node run.js` | ||
* Easy to install, run and update 🎉 | ||
* 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 👀 | ||
> Note: We'll try our best to keep this version up to date with the PHP and other versions! Suggestions welcome. | ||
--- | ||
## 🕹️ How to use this | ||
1. Install [Node.js](https://nodejs.org/en/). (Version 10 and above) | ||
2. Log into [Steam](http://store.steampowered.com/) in your browser. | ||
3. Open the following URL: <https://steamcommunity.com/saliengame/gettoken>. You should be able to find the bit that looks like `"token":"xxxxxxxx"`. Copy whatever is inside the second quotes, (e.g. `xxxxxxxx`). | ||
4. Open PowerShell on Windows. (Tip: Start > Run > type `powershell.exe` > Enter) | ||
5. Run `npm install -g salien-script-js` to install this project. | ||
6. Run the script by typing `salien-script-js --token xxxxxxxx` where `xxxxxxxx` is your token from step 3. | ||
> ### If you appreciate the script, please leave a star ⭐ on the project! | ||
## 😍 How to update the script | ||
1. Close/cancel any running script windows | ||
2. Open PowerShell on Windows. | ||
3. Run `npm update -g salien-script-js` | ||
4. Re-run your scripts using the same command | ||
Easy right? | ||
--- | ||
### 👌 Represent your Steam Group (Optional) | ||
If you'd like to represent a specific steam group, simply pass the `--group` option with the ID of the group. | ||
```sh-session | ||
salien-script-js --token xxxxxxxx --group 123456789 | ||
``` | ||
You can get your group id by going to https://steamcommunity.com/groups/YOUR_GROUP_NAME_HERE/memberslistxml/?xml=1 and replacing `YOUR_GROUP_NAME_HERE` with the group name shown at the end of your groups url. | ||
**You must be a member of a group to represent that group!** | ||
If you'd like to team up with an established larger group please consider using either: | ||
* [100Pals](https://steamcommunity.com/groups/100pals) id: `103582791454524084` | ||
* [SteamDB](https://steamcommunity.com/groups/steamdb) id: `103582791434298690` | ||
### 👥 Multiple tokens/scripts | ||
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. | ||
## Advanced: Usage as an npm package | ||
@@ -29,2 +78,4 @@ | ||
token: '', // Your token from https://steamcommunity.com/saliengame/gettoken | ||
clan: '', // (optional) Clan id from https://steamcommunity.com/groups/YOUR_GROUP_NAME_HERE/memberslistxml/?xml=1 | ||
name: '', // (optional) Name of this instance for logging | ||
}; | ||
@@ -34,8 +85,11 @@ | ||
salien.run(); | ||
salien.init(); | ||
``` | ||
## Other scripts Salien and languages | ||
## Development | ||
* [PHP version by xPaw](https://github.com/SteamDatabase/SalienCheat) (original) | ||
* [Python version](https://github.com/SteamDatabase/SalienCheat) | ||
Want to help out? Awesome! 👍 | ||
Pull the repo and you can run the script with `node cli.js -t TOKEN`. | ||
PRs, suggestions, fixes and improvements all welcome. |
770
src/index.js
/** | ||
* MIT License | ||
* | ||
* Copyright (C) 2018 Alex Gabites | ||
* Copyright (c) 2018 Alex Gabites | ||
* | ||
@@ -25,263 +25,721 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
const chalk = require('chalk'); | ||
const dateFormat = require('dateformat'); | ||
const delay = require('delay'); | ||
const fetch = require('fetch-retry'); | ||
const baseUrl = 'https://community.steam-api.com/'; | ||
const { version: pkgVersion } = require('../package.json'); | ||
const getUrl = (method, params = '') => `${baseUrl}/${method}${params ? '/?' : ''}${params}`; | ||
const logger = (name, ...messages) => { | ||
let message = chalk.white(dateFormat(new Date(), '[HH:MM:ss]')); | ||
const getOptions = (options = {}) => { | ||
return { | ||
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', | ||
}, | ||
...options, | ||
}; | ||
if (name) { | ||
message += ` (${name})`; | ||
} | ||
// eslint-disable-next-line no-console | ||
console.log(message, ...messages); | ||
}; | ||
const getScoreForZone = (zone) => { | ||
let score = 0; | ||
// eslint-disable-next-line no-console | ||
const debug = message => console.log(`${JSON.stringify(message, 0, 2)}`); | ||
const asyncForEach = async (_this, array, callback) => { | ||
for (let index = 0; index < array.length; index += 1) { | ||
await callback(array[index], index, array, _this); | ||
} | ||
}; | ||
const getDifficultyName = zone => { | ||
const boss = zone.type === 4 ? 'BOSS - ' : ''; | ||
switch (zone.difficulty) { | ||
case 1: score = 5; break; | ||
case 2: score = 10; break; | ||
case 3: score = 20; break; | ||
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; | ||
}; | ||
async function getFirstAvailablePlanetId() { | ||
console.log('Attempting to get first open planet...'); | ||
class SalienScriptException { | ||
constructor(message) { | ||
this.name = 'SalienScriptException'; | ||
this.message = message; | ||
} | ||
} | ||
const request = await fetch(getUrl('ITerritoryControlMinigameService/GetPlanets/v0001', 'active_only=1'), getOptions()); | ||
const response = await request.json(); | ||
class SalienScriptRestart { | ||
constructor(message) { | ||
this.name = 'SalienScriptRestart'; | ||
this.message = message; | ||
} | ||
} | ||
if (!response || !response.response.planets) { | ||
console.log('Didn\'t find any planets.'); | ||
class SalienScript { | ||
constructor({ token, clan, name = null }) { | ||
this.token = token; | ||
this.clan = clan; | ||
this.name = name; | ||
return null; | ||
this.maxRetries = 2; | ||
this.defaultDelayMs = 5000; | ||
this.defaultDelaySec = this.defaultDelayMs / 1000; | ||
this.startTime = null; | ||
this.waitTime = 110; | ||
this.hasJoinedClan = false; | ||
this.currentPlanetId = null; | ||
this.steamPlanetId = null; | ||
this.knownPlanetIds = []; | ||
this.knownPlanets = {}; | ||
this.skippedPlanets = []; | ||
} | ||
const firstOpen = response.response.planets.filter(planet => !planet.state.captured)[0]; | ||
async RequestAPI(method, params, maxRetries, additionalOptions = {}) { | ||
let url = `https://community.steam-api.com/${method}/v0001`; | ||
console.log('First open planet id:', firstOpen.id); | ||
if (params) { | ||
url += '/?'; | ||
return firstOpen.id; | ||
}; | ||
params.forEach(param => { | ||
url += `${param}&`; | ||
}); | ||
async function getPlayerInfo(token) { | ||
console.log('Getting player info...'); | ||
url = url.substring(0, url.length - 1); | ||
} | ||
const request = await fetch(getUrl('ITerritoryControlMinigameService/GetPlayerInfo/v0001', `access_token=${token}`), getOptions({ method: 'POST' })); | ||
const response = await request.json(); | ||
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, | ||
}; | ||
if (!response || !response.response) { | ||
console.log('Didn\'t get any player info.'); | ||
let request; | ||
let response; | ||
let retries = 0; | ||
return null; | ||
} | ||
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 | ||
console.log('Got player info!'); | ||
logger(this.name, ` ${chalk.bgRed(`${e.name}:`)} ${chalk.red(`For ${method}`)}`); | ||
debug(e); | ||
return response.response; | ||
}; | ||
retries += 1; | ||
async function leaveCurrentGame(token, leaveCurrentPlanet) { | ||
let playerInfo = null; | ||
if (retries < maxRetries) { | ||
logger(this.name, chalk.yellow(` Retrying ${method} in ${this.defaultDelaySec} seconds...`)); | ||
} else { | ||
throw new SalienScriptException(`Failed ${method} after ${retries} retries`); | ||
} | ||
while (!playerInfo) { | ||
playerInfo = await getPlayerInfo(token); | ||
await delay(this.defaultDelayMs); | ||
} | ||
} | ||
return response.response; | ||
} | ||
// Please do not change our clanid if you are going to use this script | ||
// If you want to cheat for your own group, come up with up with your own approach, thank you | ||
if (!playerInfo['clan_info']['accountid'] || playerInfo['clan_info']['accountid'] != 4777282) { | ||
await fetch(getUrl('ITerritoryControlMinigameService/RepresentClan/v0001', `clanid=4777282&access_token=${token}`), getOptions({ method: 'POST' })); | ||
async ApiGetPlanets() { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/GetPlanets', | ||
['active_only=1'], | ||
this.maxRetries, | ||
); | ||
return response.planets; | ||
} | ||
if (playerInfo['active_zone_game']) { | ||
console.log('Leaving `active_zone_game`...'); | ||
async ApiGetPlanet(planetId) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/GetPlanet', | ||
[`id=${planetId}`, 'language=english'], | ||
this.maxRetries, | ||
); | ||
return response.planets[0]; | ||
} | ||
await fetch(getUrl('IMiniGameService/LeaveGame/v0001', `access_token=${token}&gameid=${playerInfo['active_zone_game']}`), getOptions({ method: 'POST' })); | ||
async ApiGetPlayerInfo() { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/GetPlayerInfo', | ||
[`access_token=${this.token}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
} | ||
console.log('Success!'); | ||
async ApiRepresentClan(clanId) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/RepresentClan', | ||
[`access_token=${this.token}`, `clanid=${clanId}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
} | ||
if (!playerInfo['active_planet']) { | ||
return 0; | ||
async ApiLeaveGame(gameId) { | ||
const response = await this.RequestAPI( | ||
'IMiniGameService/LeaveGame', | ||
[`access_token=${this.token}`, `gameid=${gameId}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
} | ||
if (leaveCurrentPlanet) { | ||
console.log('Leaving `active_planet`...'); | ||
async ApiJoinPlanet(planetId) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/JoinPlanet', | ||
[`access_token=${this.token}`, `id=${planetId}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
} | ||
await fetch(getUrl('IMiniGameService/LeaveGame/v0001', `access_token=${token}&gameid=${playerInfo['active_planet']}`), getOptions({ method: 'POST' })); | ||
async ApiJoinZone(zoneId) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/JoinZone', | ||
[`access_token=${this.token}`, `zone_position=${zoneId}`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
} | ||
console.log('Success!'); | ||
async ApiReportScore(score) { | ||
const response = await this.RequestAPI( | ||
'ITerritoryControlMinigameService/ReportScore', | ||
[`access_token=${this.token}`, `score=${score}`, `language=english`], | ||
this.maxRetries, | ||
{ method: 'POST' }, | ||
); | ||
return response; | ||
} | ||
return playerInfo['active_planet']; | ||
} | ||
async leaveCurrentGame(leaveCurrentPlanet = 0) { | ||
let playerInfo; | ||
async function joinPlanet(token, planetId) { | ||
console.log('Attempting to join planet id:', planetId); | ||
while (!playerInfo) { | ||
playerInfo = await this.ApiGetPlayerInfo(); | ||
} | ||
await fetch(getUrl('ITerritoryControlMinigameService/JoinPlanet/v0001', `id=${planetId}&access_token=${token}`), getOptions({ method: 'POST' })); | ||
if (playerInfo.active_zone_game) { | ||
await this.ApiLeaveGame(playerInfo.active_zone_game); | ||
} | ||
console.log('Joined!'); | ||
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)}`); | ||
return; | ||
} | ||
await this.ApiRepresentClan(this.clan); | ||
async function getFirstAvailableZone(planetId) { | ||
console.log(`Requesting zones for planet ${planetId}...`); | ||
let clanCheckInfo = null; | ||
const request = await fetch(getUrl('ITerritoryControlMinigameService/GetPlanet/v0001', `id=${planetId}`), getOptions()); | ||
const response = await request.json(); | ||
while (!clanCheckInfo) { | ||
clanCheckInfo = await this.ApiGetPlayerInfo(); | ||
} | ||
if (!response.response.planets[0].zones) { | ||
return null; | ||
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) { | ||
return 0; | ||
} | ||
const activePlanet = playerInfo.active_planet; | ||
if (leaveCurrentPlanet > 0 && leaveCurrentPlanet !== activePlanet) { | ||
logger( | ||
this.name, | ||
`Leaving planet ${chalk.yellow(activePlanet)}, because we want to be on ${chalk.yellow(leaveCurrentPlanet)}`, | ||
); | ||
await this.ApiLeaveGame(activePlanet); | ||
} | ||
return activePlanet; | ||
} | ||
let zones = response.response.planets[0].zones; | ||
let cleanZones = []; | ||
let bossZone = null; | ||
async getFirstAvailableZone(planetId) { | ||
let planet; | ||
zones.some(zone => { | ||
if (zone.captured) { | ||
return; | ||
while (!planet) { | ||
planet = await this.ApiGetPlanet(planetId); | ||
} | ||
if (zone.type === 4) { | ||
bossZone = zone; | ||
} else if (zone.type != 3) { | ||
console.log('Unknown zone type:', zone.type); | ||
if (!planet.zones) { | ||
return null; | ||
} | ||
if (zone['capture_progress'] < 0.95) { | ||
const planetName = planet.state.name; | ||
const planetCaptured = planet.state.capture_progress; | ||
const planetPlayers = planet.state.current_players; | ||
const { zones } = planet; | ||
const cleanZones = []; | ||
let hardZones = 0; | ||
let mediumZones = 0; | ||
let easyZones = 0; | ||
let unknownZones = 0; | ||
let toReturn = null; | ||
zones.forEach(zone => { | ||
if (zone.captured) { | ||
return; | ||
} | ||
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; | ||
} | ||
}) | ||
if (bossZone) { | ||
return bossZone; | ||
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], | ||
}; | ||
} | ||
if (cleanZones.length < 0) { | ||
return null; | ||
async isThereAnyNewPlanets(knownPlanetIds) { | ||
logger(this.name, ' Checking for any new planets...'); | ||
let planets; | ||
while (!planets) { | ||
planets = await this.ApiGetPlanets(); | ||
} | ||
if (!planets) { | ||
return false; | ||
} | ||
let hasNewPlanet = false; | ||
await planets.forEach(planet => { | ||
if (!knownPlanetIds.includes(planet.id)) { | ||
hasNewPlanet = true; | ||
} | ||
}); | ||
return hasNewPlanet; | ||
} | ||
cleanZones.sort((a, b) => { | ||
if (a.difficulty === b.difficulty) { | ||
return b['zone_position'] - a['zone_position']; | ||
async setupGame() { | ||
const planets = await this.ApiGetPlanets(); | ||
if (!planets) { | ||
throw new SalienScriptException("Didn't find any planets."); | ||
} | ||
return b.difficulty - a.difficulty; | ||
}); | ||
try { | ||
await asyncForEach(this, planets, async (planet, index, array, _this) => { | ||
let zones; | ||
return cleanZones[0]; | ||
} | ||
let hardZones = 0; | ||
let mediumZones = 0; | ||
let easyZones = 0; | ||
let unknownZones = 0; | ||
async function joinZone(token, position) { | ||
console.log('Attempting to join zone position:', position); | ||
let hasBossZone = false; | ||
const request = await fetch(getUrl('ITerritoryControlMinigameService/JoinZone/v0001', `zone_position=${position}&access_token=${token}`), getOptions({ method: 'POST' })); | ||
const response = await request.json(); | ||
while (!zones) { | ||
zones = await _this.ApiGetPlanet(planet.id); | ||
} | ||
if (!response || !response.response['zone_info']) { | ||
console.log('Failed to join a zone.'); | ||
zones.zones.forEach(zone => { | ||
if ((zone.capture_progress && zone.capture_progress > 0.97) || zone.captured) { | ||
return; | ||
} | ||
return null; | ||
} | ||
if (zone.type === 4) { | ||
hasBossZone = true; | ||
} else if (zone.type !== 3) { | ||
logger(this.name, chalk.red(`!! Unknown zone type: ${zone.type}`)); | ||
} | ||
console.log('Got player info!'); | ||
switch (zone.difficulty) { | ||
case 3: | ||
hardZones += 1; | ||
break; | ||
case 2: | ||
mediumZones += 1; | ||
break; | ||
case 1: | ||
easyZones += 1; | ||
break; | ||
default: | ||
unknownZones += 1; | ||
break; | ||
} | ||
}); | ||
return response.response; | ||
} | ||
_this.knownPlanetIds.push(planet.id); | ||
async function reportScore(token, score) { | ||
console.log('Attempting to send score...'); | ||
// eslint-disable-next-line no-param-reassign | ||
_this.knownPlanets[planet.id] = { | ||
hardZones, | ||
mediumZones, | ||
easyZones, | ||
unknownZones, | ||
hasBossZone, | ||
...planet, | ||
}; | ||
const request = await fetch(getUrl('ITerritoryControlMinigameService/ReportScore/v0001', `access_token=${token}&score=${score}&language=english`), getOptions({ method: 'POST' })); | ||
const response = await request.json(); | ||
const capturedPercent = Number(planet.state.capture_progress * 100) | ||
.toFixed(2) | ||
.toString(); | ||
if (response.response['new_score']) { | ||
const data = response.response; | ||
const planetName = planet.state.name | ||
.replace('#TerritoryControl_', '') | ||
.split('_') | ||
.join(' '); | ||
console.log(`Score: ${data['old_score']} => ${data['new_score']} (next level: ${data['next_level_score']}) - Current level: ${data['new_level']}`); | ||
} | ||
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)})`; | ||
return; | ||
} | ||
logger(this.name, logMsg); | ||
class SalienScript { | ||
constructor({ token }) { | ||
this.token = token; | ||
this.currentPlanetId = null; | ||
} | ||
if (unknownZones) { | ||
logger(this.name, `>> Unknown zones found: ${chalk.yellow(unknownZones)}`); | ||
} | ||
async run() { | ||
console.log('This script will not work until you have joined our group:'); | ||
console.log('https://steamcommunity.com/groups/SteamDB'); | ||
if (hasBossZone) { | ||
// eslint-disable-next-line no-param-reassign | ||
_this.currentPlanetId = planet.id; | ||
while (!this.currentPlanetId) { | ||
this.currentPlanetId = await getFirstAvailablePlanetId(); | ||
throw new SalienScriptException('Boss zone found!'); | ||
} | ||
}); | ||
} catch (e) { | ||
if (e.name === 'SalienScriptException' && e.message === 'Boss zone found!') { | ||
logger(this.name, chalk.green('>> This planet has a boss zone, selecting this planet')); | ||
} else { | ||
debug(e); | ||
throw new SalienScriptException(e.message); | ||
} | ||
} | ||
// FIXME this logic might be able to be cleaned up | ||
const priority = ['hardZones', 'mediumZones', 'easyZones']; | ||
if (!this.currentPlanetId) { | ||
this.knownPlanetIds.sort((a, b) => { | ||
const planetA = this.knownPlanets[a]; | ||
const planetB = this.knownPlanets[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); | ||
}); | ||
for (let i = 0; i < priority.length; i += 1) { | ||
this.knownPlanetIds.forEach(planetId => { | ||
const planet = this.knownPlanets[planetId]; | ||
if (this.skippedPlanets.includes(planetId) || !planet[priority[i]]) { | ||
return; | ||
} | ||
if (!planet.state.captured && !this.currentPlanetId) { | ||
const planetName = planet.state.name | ||
.replace('#TerritoryControl_', '') | ||
.split('_') | ||
.join(' '); | ||
logger(this.name, `>> Selected planet ${chalk.green(planetId)} (${chalk.green(planetName)})`); | ||
this.currentPlanetId = planetId; | ||
} | ||
}); | ||
} | ||
if (!this.currentPlanetId) { | ||
console.log('Trying to get another PlanetId in 5 seconds...'); | ||
// If there are no planets with hard or medium zones, just return first one | ||
this.currentPlanetId = planets[0].id; | ||
} | ||
} | ||
await delay(5000); | ||
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(); | ||
} | ||
} | ||
} | ||
await leaveCurrentGame(this.token, true); | ||
async gameLoop() { | ||
console.log(''); // eslint-disable-line no-console | ||
await joinPlanet(this.token, this.currentPlanetId); | ||
// Scan planets every 10 minutes | ||
if (new Date().getTime() - this.startTime > 600000) { | ||
throw new SalienScriptRestart('!! Re-scanning for new planets'); | ||
} | ||
this.currentPlanetId = await leaveCurrentGame(this.token, false); | ||
let zone; | ||
while (true) { | ||
let zone = null; | ||
let joinedZone = null; | ||
while (!zone) { | ||
zone = await this.getFirstAvailableZone(this.currentPlanetId); | ||
} | ||
while (!zone) { | ||
zone = await getFirstAvailableZone(this.currentPlanetId); | ||
if (zone === false) { | ||
this.skippedPlanets.push(this.currentPlanetId); | ||
if (!zone) { | ||
console.log('Trying to get another ZoneId in 5 seconds...'); | ||
throw new SalienScriptRestart('!! There are no zones to join in this planet'); | ||
} | ||
await delay(5000); | ||
} | ||
const { hardZones, mediumZones, 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'); | ||
} | ||
while (!joinedZone) { | ||
console.log('Attempting to join zone:', zone['zone_position']); | ||
const hasNewPlanet = await this.isThereAnyNewPlanets(this.knownPlanetIds); | ||
joinedZone = await joinZone(this.token, zone['zone_position']); | ||
if (hasNewPlanet) { | ||
throw new SalienScriptRestart('!! Detected a new planet'); | ||
} | ||
} | ||
if (!joinedZone) { | ||
console.log('Trying to get another Zone Position in 15 seconds...'); | ||
const planetName = zone.planetName | ||
.replace('#TerritoryControl_', '') | ||
.split('_') | ||
.join(' '); | ||
await delay(15000); | ||
} | ||
} | ||
const position = zone.zone_position; | ||
console.log(`Joined zone ${zone['zone_position']} - Captured: ${(zone['capture_progress'] * 100).toFixed(2)}% - Difficulty ${zone.difficulty}`); | ||
zone = null; | ||
console.log('Waiting 120 seconds for game to end...'); | ||
while (!zone) { | ||
zone = await this.ApiJoinZone(position); | ||
} | ||
await delay(120000); | ||
if (!zone.zone_info) { | ||
throw new SalienScriptRestart('!! Failed to join a zone'); | ||
} | ||
console.log('Game complete!'); | ||
const zoneInfo = zone.zone_info; | ||
await reportScore(this.token, getScoreForZone(zone)); | ||
const capturedPercent = Number(planetCaptured * 100) | ||
.toFixed(2) | ||
.toString(); | ||
let planetLogMsg = `>> Planet ${chalk.green(this.currentPlanetId)} - Captured: ${chalk.yellow(capturedPercent)}%`; | ||
planetLogMsg += ` - Hard: ${chalk.yellow(hardZones)} - Medium: ${chalk.yellow(mediumZones)}`; | ||
planetLogMsg += ` - Players: ${chalk.yellow(planetPlayers.toLocaleString())} (${chalk.green(planetName)})`; | ||
logger(this.name, planetLogMsg); | ||
const capturedProgress = !zoneInfo.capture_progress | ||
? 0 | ||
: Number(zoneInfo.capture_progress * 100) | ||
.toFixed(2) | ||
.toString(); | ||
let zoneLogMsg = `>> Zone ${chalk.green(zoneInfo.zone_position)} - Captured: ${chalk.yellow(capturedProgress)}%`; | ||
zoneLogMsg += ` - Difficulty: ${chalk.yellow(getDifficultyName(zoneInfo))}`; | ||
logger(this.name, zoneLogMsg); | ||
if (zoneInfo.top_clans) { | ||
logger(this.name, `-- Top Clans:${zoneInfo.top_clans.map(({ name }) => ` ${name}`)}`); | ||
} | ||
}; | ||
logger(this.name, ` ${chalk.bgMagenta(`Waiting ${this.waitTime} seconds for round to finish...`)}`); | ||
await delay(this.waitTime * 1000); | ||
const report = await this.ApiReportScore(getScoreForZone(zone)); | ||
if (report.new_score) { | ||
const earnedXp = report.new_score - report.old_score; | ||
const nextLevelPercent = ((report.new_score / report.next_level_score) * 100).toFixed(2); | ||
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)`; | ||
logger(this.name, currentLevelMsg); | ||
const remainingXp = report.next_level_score - report.new_score; | ||
const timeRemaining = | ||
((report.next_level_score - report.new_score) / getScoreForZone(zone)) * (this.waitTime / 60); | ||
const hoursRemaining = Math.floor(timeRemaining / 60); | ||
const minutesRemaining = timeRemaining % 60; | ||
const levelEta = `${hoursRemaining}h ${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)}`; | ||
logger(this.name, nextLevelMsg); | ||
} | ||
// Some users get stuck in games after calling ReportScore, so we manually leave to fix this | ||
let leftGame; | ||
while (!leftGame) { | ||
leftGame = await this.leaveCurrentGame(this.currentPlanetId); | ||
} | ||
if (leftGame !== this.currentPlanetId) { | ||
throw new SalienScriptRestart('!! Wrong current planet'); | ||
} | ||
} | ||
async init() { | ||
this.startTime = new Date().getTime(); | ||
// Reset all variables to default values every time init() is called | ||
this.currentPlanetId = null; | ||
this.knownPlanetIds = []; | ||
this.knownPlanets = {}; | ||
this.skippedPlanets = []; | ||
try { | ||
logger(this.name, ` ${chalk.bgGreen(` Started SalienScript | Version: ${pkgVersion} `)}`); | ||
await this.setupGame(); | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
await this.gameLoop(); | ||
} | ||
} catch (e) { | ||
logger(this.name, ` ${chalk.bgRed(`${e.name}:`)} ${chalk.red(e.message)}`); | ||
if (e.name !== 'SalienScriptRestart') { | ||
debug(e); | ||
} | ||
logger(this.name, ` ${chalk.bgMagenta(`Script will restart in ${this.defaultDelaySec} seconds...`)}\n\n`); | ||
await delay(this.defaultDelayMs); | ||
this.init(); | ||
} | ||
} | ||
} | ||
module.exports = SalienScript; |
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
27669
5
623
93
2
5
6
+ Addedchalk@^2.4.1
+ Addeddateformat@^3.0.3
+ Addedmeow@^5.0.0
+ Addedansi-styles@3.2.1(transitive)
+ Addedarray-find-index@1.0.2(transitive)
+ Addedarrify@1.0.1(transitive)
+ Addedcamelcase@4.1.0(transitive)
+ Addedcamelcase-keys@4.2.0(transitive)
+ Addedchalk@2.4.2(transitive)
+ Addedcolor-convert@1.9.3(transitive)
+ Addedcolor-name@1.1.3(transitive)
+ Addedcurrently-unhandled@0.4.1(transitive)
+ Addeddateformat@3.0.3(transitive)
+ Addeddecamelize@1.2.0(transitive)
+ Addeddecamelize-keys@1.1.1(transitive)
+ Addederror-ex@1.3.2(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedfind-up@2.1.0(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedhas-flag@3.0.0(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhosted-git-info@2.8.9(transitive)
+ Addedindent-string@3.2.0(transitive)
+ Addedis-arrayish@0.2.1(transitive)
+ Addedis-core-module@2.15.1(transitive)
+ Addedis-plain-obj@1.1.0(transitive)
+ Addedjson-parse-better-errors@1.0.2(transitive)
+ Addedload-json-file@4.0.0(transitive)
+ Addedlocate-path@2.0.0(transitive)
+ Addedloud-rejection@1.6.0(transitive)
+ Addedmap-obj@1.0.12.0.0(transitive)
+ Addedmeow@5.0.0(transitive)
+ Addedminimist-options@3.0.2(transitive)
+ Addednormalize-package-data@2.5.0(transitive)
+ Addedp-limit@1.3.0(transitive)
+ Addedp-locate@2.0.0(transitive)
+ Addedp-try@1.0.0(transitive)
+ Addedparse-json@4.0.0(transitive)
+ Addedpath-exists@3.0.0(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpath-type@3.0.0(transitive)
+ Addedpify@3.0.0(transitive)
+ Addedquick-lru@1.1.0(transitive)
+ Addedread-pkg@3.0.0(transitive)
+ Addedread-pkg-up@3.0.0(transitive)
+ Addedredent@2.0.0(transitive)
+ Addedresolve@1.22.8(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedsignal-exit@3.0.7(transitive)
+ Addedspdx-correct@3.2.0(transitive)
+ Addedspdx-exceptions@2.5.0(transitive)
+ Addedspdx-expression-parse@3.0.1(transitive)
+ Addedspdx-license-ids@3.0.20(transitive)
+ Addedstrip-bom@3.0.0(transitive)
+ Addedstrip-indent@2.0.0(transitive)
+ Addedsupports-color@5.5.0(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedtrim-newlines@2.0.0(transitive)
+ Addedvalidate-npm-package-license@3.0.4(transitive)
+ Addedyargs-parser@10.1.0(transitive)