From 7bf2cc9e44ccf5ced466940f15856a295de4403c Mon Sep 17 00:00:00 2001 From: Natalie Rose Date: Wed, 2 Sep 2026 11:31:29 +1000 Subject: [PATCH] =?UTF-8?q?Remove=20old=20legacy=20client=20=F0=9F=90=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 66 +++-- package.json | 2 +- packages/cli/src/config.js | 2 +- packages/cli/src/task-runner.js | 16 +- packages/core/index.js | 51 +++- packages/core/package.json | 2 - packages/core/src/buildings.js | 83 ++++++ packages/core/src/call.js | 52 ++++ packages/core/src/captcha-prompt.js | 37 +++ packages/core/src/client.js | 89 +++++- packages/core/src/lacuna/building.js | 19 -- .../core/src/lacuna/buildings/archaeology.js | 36 --- .../core/src/lacuna/buildings/intelligence.js | 20 -- .../core/src/lacuna/buildings/observatory.js | 11 - packages/core/src/lacuna/buildings/park.js | 11 - .../src/lacuna/buildings/planetaryCommand.js | 15 - .../core/src/lacuna/buildings/shipyard.js | 18 -- .../core/src/lacuna/buildings/spaceport.js | 31 --- packages/core/src/lacuna/buildings/trade.js | 38 --- packages/core/src/lacuna/cache.js | 4 - packages/core/src/lacuna/emissary.js | 116 -------- packages/core/src/lacuna/index.js | 127 --------- packages/core/src/lacuna/module.js | 14 - packages/core/src/lacuna/modules/alliance.js | 11 - packages/core/src/lacuna/modules/body.js | 60 ---- packages/core/src/lacuna/modules/captcha.js | 42 --- packages/core/src/lacuna/modules/empire.js | 261 ------------------ packages/core/src/lacuna/modules/inbox.js | 21 -- packages/core/src/lacuna/modules/map.js | 27 -- packages/core/src/lacuna/modules/stats.js | 20 -- .../core/src/lacuna/ssbuildings/parliament.js | 38 --- .../src/lacuna/ssbuildings/policeStation.js | 19 -- .../src/lacuna/ssbuildings/stationCommand.js | 11 - .../src/{lacuna => }/ore-distributions.js | 0 packages/core/src/planets.js | 158 +++++++++++ packages/core/src/server-url.js | 25 ++ packages/core/src/util.js | 10 +- packages/tasks/src/archaeology-digs.js | 28 +- packages/tasks/src/build-ships.js | 64 +++-- packages/tasks/src/building-levels.js | 41 ++- packages/tasks/src/building-types.js | 59 ++-- packages/tasks/src/docked-ships.js | 34 ++- packages/tasks/src/docks.js | 19 +- packages/tasks/src/glyph-report.ts | 17 +- packages/tasks/src/index.js | 4 +- packages/tasks/src/make-halls.js | 30 +- packages/tasks/src/make-spies.js | 37 ++- packages/tasks/src/push-buildings-up.js | 19 +- packages/tasks/src/push-glyphs.js | 55 ++-- packages/tasks/src/scuttle-ships.js | 62 +++-- packages/tasks/src/send-excavators.js | 63 +++-- packages/tasks/src/send-probes.js | 68 +++-- packages/tasks/src/spy-skills.js | 20 +- packages/tasks/src/spy-status.js | 20 +- packages/tasks/src/spy-trainer.js | 77 +++--- packages/tasks/src/starmap.js | 18 +- packages/tasks/src/throw-parties.js | 11 +- packages/tasks/src/upgrade-buildings.js | 29 +- packages/tasks/src/view-laws.js | 12 +- packages/web/index.html | 4 +- packages/web/src/bootstrapper.js | 18 +- packages/web/src/stores/captcha.js | 21 +- test/scripts/lacuna-click-limit-test.js | 26 +- test/scripts/lacuna-test.js | 30 +- test/scripts/test-instance.js | 10 +- 65 files changed, 1056 insertions(+), 1403 deletions(-) create mode 100644 packages/core/src/buildings.js create mode 100644 packages/core/src/call.js create mode 100644 packages/core/src/captcha-prompt.js delete mode 100644 packages/core/src/lacuna/building.js delete mode 100644 packages/core/src/lacuna/buildings/archaeology.js delete mode 100644 packages/core/src/lacuna/buildings/intelligence.js delete mode 100644 packages/core/src/lacuna/buildings/observatory.js delete mode 100644 packages/core/src/lacuna/buildings/park.js delete mode 100644 packages/core/src/lacuna/buildings/planetaryCommand.js delete mode 100644 packages/core/src/lacuna/buildings/shipyard.js delete mode 100644 packages/core/src/lacuna/buildings/spaceport.js delete mode 100644 packages/core/src/lacuna/buildings/trade.js delete mode 100644 packages/core/src/lacuna/cache.js delete mode 100644 packages/core/src/lacuna/emissary.js delete mode 100644 packages/core/src/lacuna/index.js delete mode 100644 packages/core/src/lacuna/module.js delete mode 100644 packages/core/src/lacuna/modules/alliance.js delete mode 100644 packages/core/src/lacuna/modules/body.js delete mode 100644 packages/core/src/lacuna/modules/captcha.js delete mode 100644 packages/core/src/lacuna/modules/empire.js delete mode 100644 packages/core/src/lacuna/modules/inbox.js delete mode 100644 packages/core/src/lacuna/modules/map.js delete mode 100644 packages/core/src/lacuna/modules/stats.js delete mode 100644 packages/core/src/lacuna/ssbuildings/parliament.js delete mode 100644 packages/core/src/lacuna/ssbuildings/policeStation.js delete mode 100644 packages/core/src/lacuna/ssbuildings/stationCommand.js rename packages/core/src/{lacuna => }/ore-distributions.js (100%) create mode 100644 packages/core/src/planets.js create mode 100644 packages/core/src/server-url.js diff --git a/CLAUDE.md b/CLAUDE.md index d15961e..c0c2ba4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,28 +76,48 @@ Adding a task touches four places: `lib/tasks/.js`, the registry entry, `b in the `components` map of `lib/web/components/screens/task-configuration.vue` under the task's `name`. -### Two API clients coexist (migration in progress) - -- **Legacy, in-repo — used by every task.** `lib/lacuna/index.js` composes singleton module and - building objects. `lib/lacuna/module.js` turns a list of snake_case server methods into camelCase - methods bound to `emissary.serverCall`, so `apiMethods('empire', ['get_status'])` yields - `lacuna.empire.getStatus()`. `lib/lacuna/emissary.js` does the JSON-RPC POST and transparently - handles the game's click limit (`Slow down` → wait 61s → retry) and session expiry (re-login → - retry). `lib/lacuna/cache.js` (memory-cache) holds `config` and `session`. -- **New `@tlecommunity/client`** via `lib/client.js`, currently only used by `lib/web/bootstrapper.js` - for login, which then hands the session to the legacy client with - `legacyLacuna.init(config, session)` (marked `TODO: legacy, pls remove`). +### One API client: `@tlecommunity/client` + +Every backend call goes through `@tlecommunity/client`, re-exported from `@tlecommunity/super-core` +as `lacuna`. The old hand-rolled client (`packages/core/src/lacuna/`, `emissary.serverCall`, the +`apiMethods` snake_case→camelCase binding, `memory-cache`) is gone. + +- `packages/core/src/client.js` builds the singleton `Lacuna`, keeps the empire/password in a + module closure for re-login, and installs an `onResponse` handler that reproduces the old + click-limit behaviour (`Slow down` / code 1010 → wait 61s → `event.retry()`). It imports the + client from `../../../node_modules/@tlecommunity/client/dist/client.js` by relative path because + the package's `exports` map (v1.4.0) points at files it doesn't ship. Exports `login(config)`, + `relogin()`, `logout()`. +- `packages/core/src/call.js` — `call(() => lacuna.body.getStatus({ body_id }))` is the wrapper + every task uses. The new client never throws: it returns `{ result, error }`. `call()` unwraps to + the bare `result`, re-logs-in and retries once on `Session expired.`, and otherwise throws + `error.message` as a **string** (so `util.handlePromiseError` / `eachPlanet` keep swallowing + per-item failures). Pass a thunk, not a bare promise, or the session-expiry retry can't re-issue. +- Endpoint methods take a **single named-object param** (`{ body_id }`, `{ building_id, ore_type }`, + …) and modules are flat properties (`lacuna.spacePort`, `lacuna.archaeology`, …). + `lacuna.buildingFromUrl(url)` replaces the old `lacuna.buildings.generic(url)`. + `lacuna.server.callLegacy({ module, method, params: [...], addSession: true })` is the escape + hatch for RPCs with no working `/v2` method (`body/view_laws`, `archaeology/get_glyphs`, + `trade/get_glyph_summary`). ### Reuse these rather than reimplementing -- `lacuna.empire.findPlanets(planet, skip)` / `findPlanet(name)` — resolves the `--planet` argument - in all its forms: `'all'`, a name, or an array of names. -- `lacuna.empire.eachPlanet(planets, cb, {force})` — fetches each planet's buildings, skips unhappy - planets unless `force`, and isolates per-planet errors so one failure doesn't kill the task. -- `lacuna.body.findBuilding(buildings, 'Space Port')`. -- `lib/util.js`: `array`, `commify`, `int`, `objectToArray`, `handlePromiseError`, `regexMatch`, - `isCLI` / `isWeb`, `formatServerDate`. -- `lib/types.js` `translateShipType` and `lib/constants.js` `shipTypes` / `serverDateFormat`. +The old `lacuna.empire.*` / `lacuna.body.*` helpers are now plain functions exported from +`@tlecommunity/super-core`: + +- `findPlanets(planet, skip)` / `findPlanet(name)` (`packages/core/src/planets.js`) — resolves the + `--planet` argument in all its forms: `'all'`, a name, or an array of names. +- `eachPlanet(planets, cb, {force})` — fetches each planet's buildings, skips unhappy planets unless + `force`, and isolates per-planet errors so one failure doesn't kill the task. +- `colonies()` / `planets()` / `stations()` / `homePlanet()` / `getAllBuildings()`. +- `findBuilding(bodyIdOrBuildings, 'Space Port')` / `findBuildings(...)` / `bodyBuildings(...)` / + `archGetInventory(buildingId)` (`packages/core/src/buildings.js`). +- `captchaPrompt()` (`packages/core/src/captcha-prompt.js`) — no-op in the browser, stdin readline + prompt in the CLI. +- `packages/core/src/util.js`: `array`, `commify`, `int`, `objectToArray`, `handlePromiseError`, + `regexMatch`, `isCLI` / `isWeb`, `formatServerDate`. +- `packages/core/src/types.js` `translateShipType` and `packages/core/src/constants.js` `shipTypes` + / `serverDateFormat`. ### Logging is the task output channel @@ -108,16 +128,16 @@ the web UI. Tabular output uses `cli-table3` rendered into `log.info(table.toStr ### Web app -`index.html` sets `window.LE_SERF_WEB` and loads `lib/web/index.js` → `app.js`: Vue 3 with +`index.html` sets `window.SUPER_UI_WEB` and loads `lib/web/index.js` → `app.js`: Vue 3 with `vue-router` hash history, flowing login → `/task-selection` → `/task-configuration/:task` → `/task-runner/:task/:config`, where `:config` is a serialized query string produced by `$(form).serialize()`. jQuery and Bootstrap 3 come from unpkg in `index.html`, not from npm. Stores in `lib/web/stores/` are hand-rolled singleton classes wrapping Vue `reactive` and the `store` localStorage shim. -Captchas: `lib/lacuna/modules/captcha.js` `prompt()` is a no-op in the browser (the config screen's -`task-configs/helpers/captcha.vue` + `stores/captcha.js` handle it) and prompts on stdin via -readline in the CLI. +Captchas: `captchaPrompt()` (`packages/core/src/captcha-prompt.js`) is a no-op in the browser (the +config screen's `task-configs/helpers/captcha.vue` + `stores/captcha.js` handle it) and prompts on +stdin via readline in the CLI. ## Conventions and known rough edges diff --git a/package.json b/package.json index 2d64b19..c574e3d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "scripts": { "test": "npm run test:unit && npm run test:integration", - "test:unit": "node --experimental-strip-types --import=extensionless/register node_modules/.bin/_mocha", + "test:unit": "node --experimental-strip-types --import=extensionless/register node_modules/mocha/bin/_mocha", "test:integration": "node --import=extensionless/register test/scripts/lacuna-test.js", "start": "npm run --workspace=@tlecommunity/super-web start", "build": "npm run --workspace=@tlecommunity/super-web build", diff --git a/packages/cli/src/config.js b/packages/cli/src/config.js index d645dc6..a476fd0 100644 --- a/packages/cli/src/config.js +++ b/packages/cli/src/config.js @@ -2,7 +2,7 @@ import path from 'path' import fs from 'fs' import userHome from 'user-home' -let configLocation = path.join(userHome, 'le-serf-config.json') +let configLocation = path.join(userHome, 'super-cli-config.json') import { log, util } from '@tlecommunity/super-core' diff --git a/packages/cli/src/task-runner.js b/packages/cli/src/task-runner.js index 2793d2d..ba32991 100644 --- a/packages/cli/src/task-runner.js +++ b/packages/cli/src/task-runner.js @@ -1,15 +1,19 @@ import * as config from './config' import * as taskIndex from '@tlecommunity/super-tasks' -import { lacuna } from '@tlecommunity/super-core' +import { login } from '@tlecommunity/super-core' -let run = (name, options) => { - lacuna.init(config.load()) +let run = async (name, options) => { + const cfg = config.load() - lacuna.authenticate().then(() => { - // TODO: should we save this session for later? - taskIndex.getTaskByName(name).run(options) + await login({ + empire: cfg.empire, + password: cfg.password, + server: cfg.server, + apiKey: cfg.apiKey, }) + + await taskIndex.getTaskByName(name).run(options) } export { run } diff --git a/packages/core/index.js b/packages/core/index.js index 3e0101d..8b82310 100644 --- a/packages/core/index.js +++ b/packages/core/index.js @@ -1,15 +1,48 @@ +import lacuna, { login, logout } from './src/client' +import call from './src/call' +import { resolveServerUrl } from './src/server-url' +import { + findPlanets, + findPlanet, + colonies, + planets, + stations, + homePlanet, + getAllBuildings, + eachPlanet, +} from './src/planets' +import { findBuilding, findBuildings, bodyBuildings, archGetInventory } from './src/buildings' +import captchaPrompt from './src/captcha-prompt' import constants from './src/constants' -import lacuna from './src/lacuna' -import cache from './src/lacuna/cache' -import oreDistributions from './src/lacuna/ore-distributions' import log from './src/log' +import oreDistributions from './src/ore-distributions' import timelist from './src/timelist' import types from './src/types' import util from './src/util' -// NOTE: the new `@tlecommunity/client` wrapper lives at ./src/client and is -// intentionally NOT re-exported here. Its published `exports` map points at -// files the tarball doesn't ship, so bare `import '@tlecommunity/client'` -// throws under plain Node. Only the web app (bundled by Vite) uses it, and it -// imports it directly from '@tlecommunity/super-core/src/client'. -export { cache, constants, lacuna, log, oreDistributions, timelist, types, util } +export { + lacuna, + call, + login, + logout, + resolveServerUrl, + findPlanets, + findPlanet, + colonies, + planets, + stations, + homePlanet, + getAllBuildings, + eachPlanet, + findBuilding, + findBuildings, + bodyBuildings, + archGetInventory, + captchaPrompt, + constants, + log, + oreDistributions, + timelist, + types, + util, +} diff --git a/packages/core/package.json b/packages/core/package.json index a08c91c..62e100d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -5,9 +5,7 @@ "main": "index.js", "dependencies": { "@tlecommunity/client": "^1.4.0", - "camelize": "^1.0.0", "lodash": "^4.17.x", - "memory-cache": "^0.1.4", "moment": "^2.11.1", "pluralize": "^1.2.1" } diff --git a/packages/core/src/buildings.js b/packages/core/src/buildings.js new file mode 100644 index 0000000..42b61a5 --- /dev/null +++ b/packages/core/src/buildings.js @@ -0,0 +1,83 @@ +import _ from 'lodash' + +import lacuna from './client' +import call from './call' +import util from './util' + +/** + * Normalise a body's buildings into an array of `{ ..., id }` objects. Accepts + * either a body id (fetched from the server) or an already-fetched buildings + * collection (array or id-keyed object). Replaces the old `lacuna.body.buildings`. + * + * @param {string|number|object|array} idOrBuildings + * @return {Promise} + */ +export async function bodyBuildings(idOrBuildings) { + if (_.isString(idOrBuildings) || _.isNumber(idOrBuildings)) { + const result = await call(() => lacuna.body.getBuildings({ body_id: idOrBuildings })) + return util.objectToArray(result.buildings, 'id') + } + + return util.objectToArray(idOrBuildings, 'id') +} + +/** + * Every 100%-efficiency building of the given name on a body, level-coerced and + * sorted ascending by level. Replaces `lacuna.body.findBuildings`. + * + * @param {string|number|object|array} idOrBuildings - body id or pre-fetched buildings + * @param {string} name - the building name to match + * @return {Promise} + */ +export function findBuildings(idOrBuildings, name) { + return bodyBuildings(idOrBuildings).then((buildings) => + _.chain(buildings) + .filter({ name, efficiency: 100 }) + .map((b) => { + b.level = util.int(b.level) + return b + }) + .sortBy('level') + .value() + ) +} + +/** + * The highest-level 100%-efficiency building of the given name on a body, or + * `undefined`. Replaces `lacuna.body.findBuilding`. + * + * @param {string|number|object|array} idOrBuildings - body id or pre-fetched buildings + * @param {string} name - the building name to match + * @return {Promise} + */ +export function findBuilding(idOrBuildings, name) { + return findBuildings(idOrBuildings, name).then((matches) => _.first(matches.reverse())) +} + +/** + * A `{ [glyphName]: quantity }` map for an Archaeology Ministry. Replaces + * `lacuna.buildings.archaeology.getInventory`. Goes through `callLegacy` because + * `archaeology/get_glyphs` has no `/v2` method and the typed `get_glyph_summary` + * response drops the per-glyph `name` the callers key on. + * + * @param {string|number} buildingId + * @return {Promise>} + */ +export function archGetInventory(buildingId) { + return call(() => + lacuna.server.callLegacy({ + module: 'archaeology', + method: 'get_glyphs', + params: [buildingId], + addSession: true, + }) + ).then((result) => { + const summary = {} + + _.each(result.glyphs, (glyph) => { + summary[glyph.name] = util.int(glyph.quantity) + }) + + return summary + }) +} diff --git a/packages/core/src/call.js b/packages/core/src/call.js new file mode 100644 index 0000000..8af0c37 --- /dev/null +++ b/packages/core/src/call.js @@ -0,0 +1,52 @@ +import { relogin } from './client' +import log from './log' + +/** + * Run a server call through the same error handling the old `emissary.serverCall` + * did, on top of `@tlecommunity/client`'s non-throwing `{ result, error }` model: + * + * - success -> resolves to the bare `result` (envelope stripped) + * - `Session expired.` (code 1006) -> re-login, then run the call once more + * - anything else -> throws `error.message` as a **string** + * + * The click-limit ("Slow down" / 1010) case is handled transparently by the + * `onResponse` handler in `client.js` and never reaches here. + * + * Throwing a string (never an `Error`) is deliberate: `util.handlePromiseError` + * logs strings and rethrows everything else, and `eachPlanet` / `push-buildings-up` + * rely on per-item failures being swallowed. + * + * Because a settled promise can't be re-issued, pass a **thunk** to get the + * session-expiry retry: `call(() => lacuna.body.getStatus({ body_id }))`. A bare + * promise still works for the happy path. + * + * @param {Function|Promise} work - a `() => ServerResponse` thunk (preferred) or a promise + * @return {Promise<*>} the unwrapped result + */ +export default async function call(work) { + const run = () => (typeof work === 'function' ? work() : work) + + let { result, error } = await run() + if (!error) { + return result + } + + if (error.code === 1006 || error.message === 'Session expired.') { + log.newline() + log.warn('Session expired - logging in again') + await relogin() + log.info('Trying again') + log.newline() + + if (typeof work !== 'function') { + throw error.message || `Server error ${error.code}` + } + + ;({ result, error } = await run()) + if (!error) { + return result + } + } + + throw error.message || `Server error ${error.code}` +} diff --git a/packages/core/src/captcha-prompt.js b/packages/core/src/captcha-prompt.js new file mode 100644 index 0000000..4fade24 --- /dev/null +++ b/packages/core/src/captcha-prompt.js @@ -0,0 +1,37 @@ +import readline from 'readline' + +import lacuna from './client' +import call from './call' +import log from './log' + +/** + * Fetch a captcha and prompt for the answer on stdin, then solve it. Replaces + * `lacuna.captcha.prompt`. + * + * A no-op in the browser - the web config screen (`task-configs/helpers/captcha.vue` + * + `stores/captcha.js`) handles captchas there. + * + * @return {Promise} + */ +export default function captchaPrompt() { + if (typeof window === 'object') { + return Promise.resolve() + } + + return call(() => lacuna.captcha.fetch()).then((captcha) => { + log.info(`Please solve captcha from url: ${captcha.url}`) + + return new Promise((resolve, reject) => { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) + + rl.question('Captcha answer: ', (answer) => { + rl.close() + + // NOTE: getting the answer wrong kills the whole script. Whatever. + call(() => lacuna.captcha.solve({ guid: captcha.guid, solution: answer })) + .then(resolve) + .catch(reject) + }) + }) + }) +} diff --git a/packages/core/src/client.js b/packages/core/src/client.js index 5133833..a0412a6 100644 --- a/packages/core/src/client.js +++ b/packages/core/src/client.js @@ -1,6 +1,93 @@ -import { Lacuna } from '@tlecommunity/client' +// `@tlecommunity/client@1.4.0` ships a broken `exports` map (it points at +// `./dist/client.mjs` / `./dist/client.umd.js`, neither of which is in the +// tarball), so a bare `import '@tlecommunity/client'` throws under plain Node - +// only a bundler that follows the `module` field (Vite) resolves it. Import the +// shipped ESM bundle by relative path instead. This assumes npm keeps a single +// hoisted copy at the repo root; if a future install nests one under +// `packages/core/node_modules/` this path needs revisiting. +import { Lacuna } from '../../../node_modules/@tlecommunity/client/dist/client.js' + +import log from './log' +import { resolveServerUrl } from './server-url' const lacuna = new Lacuna() lacuna.log.setLogLevel('debug') +// The client discards the empire name/password after `authenticate`, so keep +// them here (in memory, like the old `memory-cache`) for `relogin()`. +let creds = null + +/** + * Point the client at `server`, remember the credentials, and log in. + * + * @param {{ empire: string, password: string, server: string, apiKey?: string }} config + * @return {Promise} the session id + */ +export async function login({ empire, password, server, apiKey }) { + lacuna.config.serverUrl = resolveServerUrl(server) + if (apiKey) { + lacuna.config.apiKey = apiKey + } + + creds = { empire, password } + + const { error } = await lacuna.authenticate(empire, password) + if (error) { + throw error.message + } + + return lacuna.session.get() +} + +/** + * Drop the current session and log in again with the remembered credentials. + * Used by `call()` to recover from a `Session expired.` error mid-task. + */ +export async function relogin() { + if (!creds) { + throw 'Not logged in' + } + + lacuna.session.set('') + + const { error } = await lacuna.authenticate(creds.empire, creds.password) + if (error) { + throw error.message + } +} + +export function logout() { + creds = null + lacuna.session.set('') +} + +// Click-limit ("Slow down" / code 1010) handling - parity with the old +// `emissary.serverCall`. 1010 is a BLOCKING_ERROR_CODE, so the originating call +// waits for this handler and adopts the response it returns: returning +// `event.retry()` transparently swaps the retried result in for the caller. +// Logs go through core `log` (the pub/sub the web task-runner subscribes to), +// not `lacuna.log`. +// +// NOTE: `lacuna.enableRpcLimitHandler()` is the built-in alternative, but it +// uses exponential backoff and its own wording - lower fidelity than the old +// "wait 61s, try again" behaviour. Don't also enable it or 1010 gets handled +// twice. +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) + +lacuna.onResponse(async (event) => { + const error = event.response.error + if (!error) { + return + } + + if (error.code === 1010 && /^Slow down/.test(error.message || '')) { + log.newline() + log.warn('Hit the click limit - waiting for a minute') + await sleep(61 * 1000) + log.info('Trying again') + log.newline() + return await event.retry() + } +}) + export default lacuna diff --git a/packages/core/src/lacuna/building.js b/packages/core/src/lacuna/building.js deleted file mode 100644 index 728ffcd..0000000 --- a/packages/core/src/lacuna/building.js +++ /dev/null @@ -1,19 +0,0 @@ -import Module from './module' - -class Building extends Module { - constructor(url) { - super() - - this.apiMethods(url, [ - 'build', - 'demolish', - 'downgrade', - 'get_stats_for_level', - 'repair', - 'upgrade', - 'view', - ]) - } -} - -export default Building diff --git a/packages/core/src/lacuna/buildings/archaeology.js b/packages/core/src/lacuna/buildings/archaeology.js deleted file mode 100644 index 20a1a5a..0000000 --- a/packages/core/src/lacuna/buildings/archaeology.js +++ /dev/null @@ -1,36 +0,0 @@ -import _ from 'lodash' - -import Building from '../building' - -class Archaeology extends Building { - constructor() { - super('archaeology') - - this.apiMethods('archaeology', [ - 'abandon_excavator', - 'assemble_glyphs', - 'get_glyphs', - 'get_glyph_summary', - 'get_ores_available_for_processing', - 'mass_abandon_excavator', - 'search_for_glyph', - 'subsidize_search', - 'view', - 'view_excavators', - ]) - } - - getInventory(id) { - return this.getGlyphs([id]).then((result) => { - var summary = {} - - _.each(result.glyphs, function (glyph) { - summary[glyph.name] = parseInt(glyph.quantity, 10) - }) - - return summary - }) - } -} - -export default Archaeology diff --git a/packages/core/src/lacuna/buildings/intelligence.js b/packages/core/src/lacuna/buildings/intelligence.js deleted file mode 100644 index c664b00..0000000 --- a/packages/core/src/lacuna/buildings/intelligence.js +++ /dev/null @@ -1,20 +0,0 @@ -import Building from '../building' - -class Intelligence extends Building { - constructor() { - super('intelligence') - - this.apiMethods('intelligence', [ - 'view', - 'train_spy', - 'view_spies', - 'view_all_spies', - 'subsidize_training', - 'burn_spy', - 'name_spy', - 'assign_spy', - ]) - } -} - -export default Intelligence diff --git a/packages/core/src/lacuna/buildings/observatory.js b/packages/core/src/lacuna/buildings/observatory.js deleted file mode 100644 index 42b590b..0000000 --- a/packages/core/src/lacuna/buildings/observatory.js +++ /dev/null @@ -1,11 +0,0 @@ -import Building from '../building' - -class Observatory extends Building { - constructor() { - super('observatory') - - this.apiMethods('observatory', ['get_probed_stars', 'abandon_probe', 'abandon_all_probes']) - } -} - -export default Observatory diff --git a/packages/core/src/lacuna/buildings/park.js b/packages/core/src/lacuna/buildings/park.js deleted file mode 100644 index 5feb911..0000000 --- a/packages/core/src/lacuna/buildings/park.js +++ /dev/null @@ -1,11 +0,0 @@ -import Building from '../building' - -class Park extends Building { - constructor() { - super('park') - - this.apiMethods('park', ['throw_a_party', 'subsidize_party']) - } -} - -export default Park diff --git a/packages/core/src/lacuna/buildings/planetaryCommand.js b/packages/core/src/lacuna/buildings/planetaryCommand.js deleted file mode 100644 index b8a9f88..0000000 --- a/packages/core/src/lacuna/buildings/planetaryCommand.js +++ /dev/null @@ -1,15 +0,0 @@ -import Building from '../building' - -class PlanetaryCommand extends Building { - constructor() { - super('planetarycommand') - - this.apiMethods('planetarycommand', [ - 'view_plans', - 'view_incoming_supply_chains', - 'subsidize_pod_cooldown', - ]) - } -} - -export default PlanetaryCommand diff --git a/packages/core/src/lacuna/buildings/shipyard.js b/packages/core/src/lacuna/buildings/shipyard.js deleted file mode 100644 index 9604776..0000000 --- a/packages/core/src/lacuna/buildings/shipyard.js +++ /dev/null @@ -1,18 +0,0 @@ -import Building from '../building' - -class Shipyard extends Building { - constructor() { - super('shipyard') - - this.apiMethods('shipyard', [ - 'build_ship', - 'build_ships', - 'get_buildable', - 'subsidize_build_queue', - 'subsidize_ship', - 'view_build_queue', - ]) - } -} - -export default Shipyard diff --git a/packages/core/src/lacuna/buildings/spaceport.js b/packages/core/src/lacuna/buildings/spaceport.js deleted file mode 100644 index db46dff..0000000 --- a/packages/core/src/lacuna/buildings/spaceport.js +++ /dev/null @@ -1,31 +0,0 @@ -import Building from '../building' - -class SpacePort extends Building { - constructor() { - super('spaceport') - - this.apiMethods('spaceport', [ - 'fetch_spies', - 'get_fleet_for', - 'get_ships_for', - 'mass_scuttle_ship', - 'name_ship', - 'prepare_fetch_spies', - 'prepare_send_spies', - 'recall_all', - 'recall_ship', - 'scuttle_ship', - 'send_fleet', - 'send_ship', - 'send_ship_types', - 'send_spies', - 'view_all_ships', - 'view_battle_logs', - 'view_foreign_ships', - 'view_ships_orbiting', - 'view_ships_travelling', - ]) - } -} - -export default SpacePort diff --git a/packages/core/src/lacuna/buildings/trade.js b/packages/core/src/lacuna/buildings/trade.js deleted file mode 100644 index cb4b1e3..0000000 --- a/packages/core/src/lacuna/buildings/trade.js +++ /dev/null @@ -1,38 +0,0 @@ -import Building from '../building' - -class Trade extends Building { - constructor() { - super('trade') - - this.apiMethods('trade', [ - 'accept_from_market', - 'add_supply_ship_to_fleet', - 'add_to_market', - 'add_waste_ship_to_fleet', - 'create_supply_chain', - 'delete_supply_chain', - 'get_glyph_summary', - 'get_plan_summary', - 'get_plans', - 'get_prisoners', - 'get_ships', - 'get_stored_resources', - 'get_supply_ships', - 'get_trade_ships', - 'get_waste_ships', - 'push_items', - 'remove_supply_ship_from_fleet', - 'remove_waste_ship_from_fleet', - 'report_abuse', - 'update_supply_chain', - 'update_waste_chain', - 'view_market', - 'view_my_market', - 'view_supply_chains', - 'view_waste_chains', - 'withdraw_from_market', - ]) - } -} - -export default Trade diff --git a/packages/core/src/lacuna/cache.js b/packages/core/src/lacuna/cache.js deleted file mode 100644 index 2b91575..0000000 --- a/packages/core/src/lacuna/cache.js +++ /dev/null @@ -1,4 +0,0 @@ -// Just a simple in-memory cache for storing things like: empire details, session id, etc. -import MemoryCache from 'memory-cache' - -export default MemoryCache diff --git a/packages/core/src/lacuna/emissary.js b/packages/core/src/lacuna/emissary.js deleted file mode 100644 index b1c39ce..0000000 --- a/packages/core/src/lacuna/emissary.js +++ /dev/null @@ -1,116 +0,0 @@ -import _ from 'lodash' - -import cache from './cache' -import log from '../log' -import util from '../util' -import lacuna from '../lacuna' - -let emissary = { - getUrl(moduleId) { - const server = cache.get('config').server - - if (server.startsWith('http')) { - return new URL(moduleId, server).href - } else { - let protocol = typeof window !== 'undefined' ? window.location.protocol : 'http:' - return `${protocol}//${server}.lacunaexpanse.com/${moduleId}` - } - }, - - addSession(moduleId, method, params) { - // Add session ID to the params. - let sessionId = cache.get('session') - if ( - `${moduleId}/${method}` !== 'empire/login' && - `${moduleId}/${method}` !== 'map/get_star_map' - ) { - if (_.isArray(params)) { - params = [sessionId].concat(params) - } else if (_.isObject(params)) { - params.session_id = sessionId - } - } - - return params - }, - - getJsonRequest(method, params) { - return { - jsonrpc: '2.0', - id: 1, - method, - params, - } - }, - - getReqOptions(moduleId, method, params) { - return { - url: emissary.getUrl(moduleId), - json: emissary.getJsonRequest(method, emissary.addSession(moduleId, method, params)), - method: 'POST', - } - }, - - async serverCall(moduleId, method, params = []) { - const url = emissary.getUrl(moduleId) - const data = emissary.getJsonRequest(method, emissary.addSession(moduleId, method, params)) - - log.debug(`Sending to ${url} with:`, data) - - const res = await fetch(url, { - method: 'POST', - mode: 'cors', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }) - - const json = await res.json() - - // NOTE: handle this error before `err` to avoid an unfriendly error message being emitted. - if (!json) { - reject('Connection to the server has been lost') - return - } - - log.silly('Received:', json) - - if (json.result) { - return json.result - } else if (json.error) { - if (util.regexMatch(/^Slow down/, json.error.message)) { - log.newline() - log.warn('Hit the click limit - waiting for a minute') - - await new Promise((resolve) => { - setTimeout(() => { - log.info('Trying again') - log.newline() - resolve() - }, 61 * 1000) - }) - - return emissary.serverCall(moduleId, method, params) - } else if (json.error.message === 'Session expired.') { - log.newline() - log.warn('Session expired - logging in again') - - return lacuna.newSession().then(() => { - log.info('Trying again') - log.newline() - - return emissary.serverCall(moduleId, method, params) - }) - } else { - throw json.error.message - } - } else if (body === 'Not Found') { - throw `${reqOptions.url} not found` - } else { - throw body - } - }, -} - -export default emissary diff --git a/packages/core/src/lacuna/index.js b/packages/core/src/lacuna/index.js deleted file mode 100644 index 9c1afa7..0000000 --- a/packages/core/src/lacuna/index.js +++ /dev/null @@ -1,127 +0,0 @@ -import _ from 'lodash' - -import cache from './cache' -import log from '../log' - -import Alliance from './modules/alliance' -import Body from './modules/body' -import Captcha from './modules/captcha' -import Empire from './modules/empire' -import Inbox from './modules/inbox' -import Map from './modules/map' -import Stats from './modules/stats' - -import Building from './building' - -import Archaeology from './buildings/archaeology' -import Intelligence from './buildings/intelligence' -import Observatory from './buildings/observatory' -import Park from './buildings/park' -import PlanetaryCommand from './buildings/planetaryCommand' -import Shipyard from './buildings/shipyard' -import SpacePort from './buildings/spaceport' -import Trade from './buildings/trade' - -import Parliament from './ssbuildings/parliament' -import PoliceStation from './ssbuildings/policeStation' -import StationCommand from './ssbuildings/stationCommand' - -const defaults = { - apiKey: 'anonymous', - empire: '', - password: '', - server: 'us1', -} - -const fingerprint = 'todo' - -/** - * This object is for interacting with the Lacuna game server. - * - * @namespace lacuna - */ -let lacuna = { - init(config, session) { - if (config) { - // Handle defaults and stuff - let obj = _.merge({}, defaults, config) - cache.put('config', obj) - } else { - log.error('No config passed to lacuna.init()') - } - - if (session) { - cache.put('session', session) - } - - return lacuna - }, - - // Base Modules - alliance: new Alliance(), - body: new Body(), - captcha: new Captcha(), - empire: new Empire(), - inbox: new Inbox(), - map: new Map(), - stats: new Stats(), - - buildings: { - archaeology: new Archaeology(), - generic: (url) => new Building(url.replace(/\//g, '')), - intelligence: new Intelligence(), - observatory: new Observatory(), - park: new Park(), - planetaryCommand: new PlanetaryCommand(), - shipyard: new Shipyard(), - spaceport: new SpacePort(), - trade: new Trade(), - }, - - modules: { - parliament: new Parliament(), - policeStation: new PoliceStation(), - stationCommand: new StationCommand(), - }, - - authenticate() { - let config = cache.get('config') - let session = cache.get('session') - - if (session) { - return new Promise((resolve, reject) => resolve(session)) - } else { - log.info(`Logging into empire ${config.empire}`) - - if (!config.empire || !config.password) { - return new Promise((resolve, reject) => { - reject('Empire name and password are required') - }) - } - - return lacuna.empire - .login([config.empire, config.password, config.apiKey, fingerprint]) - .then((result) => { - let session = result.session_id - log.info('Received session id: ' + session) - cache.put('session', session) - return session - }) - } - }, - - newSession() { - cache.put('session', '') - return lacuna.authenticate() - }, - - getConfig() { - return cache.get('config') - }, - - getSession() { - return cache.get('session') - }, -} - -export default lacuna diff --git a/packages/core/src/lacuna/module.js b/packages/core/src/lacuna/module.js deleted file mode 100644 index 0c92e74..0000000 --- a/packages/core/src/lacuna/module.js +++ /dev/null @@ -1,14 +0,0 @@ -import _ from 'lodash' -import camelize from 'camelize' - -import emissary from './emissary' - -class Module { - apiMethods(moduleId, methods) { - methods.forEach((method) => { - this[camelize(method)] = _.partial(emissary.serverCall, moduleId, method) - }) - } -} - -export default Module diff --git a/packages/core/src/lacuna/modules/alliance.js b/packages/core/src/lacuna/modules/alliance.js deleted file mode 100644 index ea40584..0000000 --- a/packages/core/src/lacuna/modules/alliance.js +++ /dev/null @@ -1,11 +0,0 @@ -import Module from '../module' - -class Alliance extends Module { - constructor() { - super() - - this.apiMethods('alliance', ['find', 'view_profile']) - } -} - -export default Alliance diff --git a/packages/core/src/lacuna/modules/body.js b/packages/core/src/lacuna/modules/body.js deleted file mode 100644 index 8b6de76..0000000 --- a/packages/core/src/lacuna/modules/body.js +++ /dev/null @@ -1,60 +0,0 @@ -import _ from 'lodash' - -import util from '../../util' - -import Module from '../module' - -class Body extends Module { - constructor() { - super() - - this.apiMethods('body', [ - 'abandon', - 'get_buildable', - 'get_buildings', - 'get_status', - 'rearrange_buildings', - 'rename', - 'repair_list', - 'view_laws', - ]) - } - - findBuilding(bodyId, name) { - return this.findBuildings(bodyId, name).then((matches) => { - // Reverse the matches so that we get the highest level not the lowest. - return _.first(matches.reverse()) - }) - } - - findBuildings(bodyId, name) { - return this.buildings(bodyId).then((buildings) => { - return _.chain(buildings) - .filter({ name, efficiency: 100 }) - .map((b) => { - b.level = util.int(b.level) - return b - }) - .sortBy('level') - .value() - }) - } - - buildings(id) { - return new Promise((resolve, reject) => { - if (_.isString(id) || _.isNumber(id)) { - // Get the buildings from the server... - this.getBuildings([id]) - .then((result) => { - resolve(util.objectToArray(result.buildings, 'id')) - }) - .catch(reject) - } else { - // .. otherwise the buildings have already been supplied. - resolve(util.objectToArray(id, 'id')) - } - }) - } -} - -export default Body diff --git a/packages/core/src/lacuna/modules/captcha.js b/packages/core/src/lacuna/modules/captcha.js deleted file mode 100644 index 3dd02da..0000000 --- a/packages/core/src/lacuna/modules/captcha.js +++ /dev/null @@ -1,42 +0,0 @@ -import Module from '../module' - -import log from '../../log' - -import readline from 'readline' - -class Captcha extends Module { - constructor() { - super() - - this.apiMethods('captcha', ['fetch', 'solve']) - } - - prompt() { - return new Promise((resolve, reject) => { - if (typeof window === 'object') { - // In the web version, the config screen handles captchas when required. - // Therefore, this shoud just be a no op if window exists. - resolve() - } else { - return this.fetch().then((captcha) => { - log.info(`Please solve captcha from url: ${captcha.url}`) - - let rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }) - - rl.question('Captcha answer: ', (answer) => { - // No more questions! - rl.close() - - // NOTE: getting the answer wrong kills the whole script. Whatever. - this.solve([captcha.guid, answer]).then(resolve).catch(reject) - }) - }) - } - }) - } -} - -export default Captcha diff --git a/packages/core/src/lacuna/modules/empire.js b/packages/core/src/lacuna/modules/empire.js deleted file mode 100644 index 4cec3ce..0000000 --- a/packages/core/src/lacuna/modules/empire.js +++ /dev/null @@ -1,261 +0,0 @@ -import _ from 'lodash' - -import Module from '../module' -import lacuna from '../../lacuna' - -import log from '../../log' -import util from '../../util' - -/** - * TODO - * - * @memberof lacuna - * @namespace lacuna.empire - * @alias lacuna.empire - */ -class Empire extends Module { - constructor() { - super() - - this.apiMethods('empire', [ - 'boost_building', - 'boost_energy', - 'boost_food', - 'boost_happiness', - 'boost_ore', - 'boost_storage', - 'boost_water', - 'change_password', - 'create', - 'disable_self_destruct', - 'edit_profile', - 'enable_self_destruct', - 'fetch_captcha', - 'find', - 'found', - 'get_invite_friend_url', - 'get_species_templates', - 'get_status', - 'invite_friend', - 'is_name_available', - 'login', - 'logout', - 'redeem_essentia_code', - 'redefine_species_limits', - 'redefine_species', - 'reset_password', - 'send_password_reset_message', - 'set_status_message', - 'spy_training_boost', - 'update_species', - 'view_boosts', - 'view_profile', - 'view_public_profile', - 'view_species_stats', - ]) - } - - /** - * This function is for handling the `--planet` argument. - * - * It can be one of three things: - * - the string 'all' - * - a planet name - * - an array of planet names - * - * @example - * lacuna.empire.findPlanets(['Earth', 'Mars']) - * // Returns: - * [ - * { - * name: 'Earth', - * id: 3 - * }, - * { - * name: 'Mars', - * id: 4 - * } - * ] - * - * @memberof lacuna.empire - * @alias lacuna.empire.findPlanets - * - * @param {string|array} planet - the planet names you want to find - * @param {string|array} [skip] - the planet names you want to skip - * @return {Promise} array of the planets that were found - */ - findPlanets(planet, skip) { - let arr = util.array(planet) - let toSkip = util.array(skip) - - log.debug(`Handling --planet`, planet) - log.debug(`Handling --skip`, skip) - - return new Promise((resolve, reject) => { - if (arr.length === 0) { - reject('please specify a planet') - return - } - - if (_.includes(arr, 'all')) { - lacuna.empire.colonies().then(resolve).catch(reject) - } else { - this.getStatus([]) - .then((status) => { - // Invert so we can key by name instead of ID - let planets = _.invert(status.empire.planets) - - return Promise.all( - arr.map((planetName) => { - return new Promise((resolve, reject) => { - let planetId = planets[planetName] - console.log('planetId', planetId) - - if (planetId) { - resolve({ - id: planetId, - name: planetName, - }) - } else { - reject(`Planet ${planetName} not found`) - } - }) - }) - ) - }) - .then((planets) => { - resolve(planets) - }) - } - }) - .then((bodies) => { - // Handle skipping of bodies. - return _.filter(bodies, (body) => { - return !_.includes(toSkip, body.name) - }) - }) - .then((result) => { - log.debug(`handlePlanetArg result:`, result) - return result - }) - } - - findPlanet(name) { - return this.findPlanets(util.array(name)).then(_.first) - } - - /** - * Loop through each given planet while handling getting buildings, unhappiness and errors. - * - * @example - * // Go thru each colony - * lacuna.empire.colonies.then((colonies) => { - * return lacuna.empire.eachPlanet(colonies, (colony, buildings) => { - * // Do stuff with colony or buildings. - * // Erorrs thrown in here are logged to the console and don't kill the current task. - * }) - * }) - * - * @example - * // Go thru each colony ignoring unhappiness checks (NOT RECOMMENDED) - * lacuna.empire.colonies.then((colonies) => { - * return lacuna.empire.eachPlanet(colonies, (colony, buildings) => { - * // Do stuff with colony or buildings. - * // Erorrs thrown in here are logged to the console and don't kill the current task. - * }, {force: true}) - * }) - * - * @memberof lacuna.empire - * @alias lacuna.empire.eachPlanet - * - * @param {array} planets - the list of planets you want to loop through - * @param {function} callback - a thenable to run on each planet - * @param {object} opts - options (set `force` to true to ignore unhappiness checks here) - */ - async eachPlanet(planets, callback, opts) { - opts = _.defaults(opts || {}, { force: false }) - - for (const planet of planets) { - log.newline() - log.info(`Looking at ${planet.name}`) - - await lacuna.body.getBuildings([planet.id]).then((result) => { - let buildings = util.objectToArray(result.buildings, 'id') - - // Don't do anything on planets that are unhappy. When a planet's happniess is negative - // making anything (ships, spies, buildings or SS plans) is slowed down significantly. - if (util.int(result.status.body.happiness) < 0 && !opts.force) { - log.error(`${planet.name} is unhappy`) - return - } - - return callback(planet, buildings) - .catch((err) => { - util.handlePromiseError(err) - }) - .finally(() => { - log.info('Moving on') - }) - }) - } - } - - planets() { - return this.findBodiesType('planets') - } - - stations() { - return this.findBodiesType('stations') - } - - colonies() { - return this.findBodiesType('colonies') - } - - findBodiesType(type) { - return this.getStatus([]).then((status) => { - let arr = [] - - _.each(status.empire[type], function (value, key) { - arr.push({ - id: key, - name: value, - }) - }) - - return _.sortBy(arr, 'name') - }) - } - - homePlanet() { - return this.getStatus([]).then((status) => { - return { - id: status.empire.home_planet_id, - name: status.empire.planets[status.empire.home_planet_id], - } - }) - } - - getAllBuildings() { - return new Promise((resolve, reject) => { - let result = [] - - this.colonies() - .then(async (colonies) => { - for (const colony of colonies) { - log.info(`Looking at ${colony.name}`) - - await lacuna.body.buildings(colony.id).then((buildings) => { - result = result.concat(buildings) - }) - } - }) - .then(() => { - resolve(result) - }) - .catch(reject) - }) - } -} - -export default Empire diff --git a/packages/core/src/lacuna/modules/inbox.js b/packages/core/src/lacuna/modules/inbox.js deleted file mode 100644 index f406735..0000000 --- a/packages/core/src/lacuna/modules/inbox.js +++ /dev/null @@ -1,21 +0,0 @@ -import Module from '../module' - -class Inbox extends Module { - constructor() { - super() - - this.apiMethods('inbox', [ - 'archive_messages', - 'read_message', - 'send_message', - 'trash_messages_where', - 'trash_messages', - 'view_archived', - 'view_inbox', - 'view_sent', - 'view_trashed', - ]) - } -} - -export default Inbox diff --git a/packages/core/src/lacuna/modules/map.js b/packages/core/src/lacuna/modules/map.js deleted file mode 100644 index 59346c7..0000000 --- a/packages/core/src/lacuna/modules/map.js +++ /dev/null @@ -1,27 +0,0 @@ -import Module from '../module' -import cache from '../cache' -import emissary from '../emissary' - -class Map extends Module { - constructor() { - super() - - this.apiMethods('map', [ - 'check_star_for_incoming_probe', - 'get_star_by_name', - 'get_star_by_xy', - 'get_star', - 'get_stars', - 'search_stars', - 'view_laws', - ]) - } - - getStarMap(bounds) { - return emissary.serverCall('map', 'get_star_map', [ - { ...bounds, session_id: cache.get('session') }, - ]) - } -} - -export default Map diff --git a/packages/core/src/lacuna/modules/stats.js b/packages/core/src/lacuna/modules/stats.js deleted file mode 100644 index 0ea54a0..0000000 --- a/packages/core/src/lacuna/modules/stats.js +++ /dev/null @@ -1,20 +0,0 @@ -import Module from '../module' - -class Stats extends Module { - constructor() { - super() - - this.apiMethods('stats', [ - 'alliance_rank', - 'colony_rank', - 'credits', - 'empire_rank', - 'find_alliance_rank', - 'find_empire_rank', - 'spy_rank', - 'weekly_medal_winners', - ]) - } -} - -export default Stats diff --git a/packages/core/src/lacuna/ssbuildings/parliament.js b/packages/core/src/lacuna/ssbuildings/parliament.js deleted file mode 100644 index 4e1dcfa..0000000 --- a/packages/core/src/lacuna/ssbuildings/parliament.js +++ /dev/null @@ -1,38 +0,0 @@ -import Building from '../building' - -class Parliament extends Building { - constructor() { - super('parliament') - - this.apiMethods('parliament', [ - 'cast_vote', - 'get_bodies_for_star_in_jurisdiction', - 'get_mining_platforms_for_asteroid_in_jurisdiction', - 'get_stars_in_jurisdiction', - 'propose_broadcast_on_network19', - 'propose_elect_new_leader', - 'propose_evict_mining_platform', - 'propose_expel_member', - 'propose_fire_bfg', - 'propose_foreign_aid', - 'propose_induct_member', - 'propose_members_only_colonization', - 'propose_members_only_excavation', - 'propose_members_only_mining_rights', - 'propose_neutralize_bhg', - 'propose_rename_asteroid', - 'propose_rename_star', - 'propose_rename_uninhabited', - 'propose_repeal_law', - 'propose_seize_star', - 'propose_taxation', - 'propose_transfer_station_ownership', - 'propose_writ', - 'view_laws', - 'view_taxes_collected', - 'view_propositions', - ]) - } -} - -export default Parliament diff --git a/packages/core/src/lacuna/ssbuildings/policeStation.js b/packages/core/src/lacuna/ssbuildings/policeStation.js deleted file mode 100644 index 6085456..0000000 --- a/packages/core/src/lacuna/ssbuildings/policeStation.js +++ /dev/null @@ -1,19 +0,0 @@ -import Building from '../building' - -class PoliceStation extends Building { - constructor() { - super('policestation') - - this.apiMethods('policestation', [ - 'execute_prisoner', - 'release_prisoner', - 'view_foreign_ships', - 'view_foreign_spies', - 'view_prisoners', - 'view_ships_orbiting', - 'view_ships_travelling', - ]) - } -} - -export default PoliceStation diff --git a/packages/core/src/lacuna/ssbuildings/stationCommand.js b/packages/core/src/lacuna/ssbuildings/stationCommand.js deleted file mode 100644 index a885381..0000000 --- a/packages/core/src/lacuna/ssbuildings/stationCommand.js +++ /dev/null @@ -1,11 +0,0 @@ -import Building from '../building' - -class StationCommand extends Building { - constructor() { - super('stationcommand') - - this.apiMethods('stationcommand', ['view', 'view_plans', 'view_incoming_supply_chains']) - } -} - -export default StationCommand diff --git a/packages/core/src/lacuna/ore-distributions.js b/packages/core/src/ore-distributions.js similarity index 100% rename from packages/core/src/lacuna/ore-distributions.js rename to packages/core/src/ore-distributions.js diff --git a/packages/core/src/planets.js b/packages/core/src/planets.js new file mode 100644 index 0000000..ef256a5 --- /dev/null +++ b/packages/core/src/planets.js @@ -0,0 +1,158 @@ +import _ from 'lodash' + +import lacuna from './client' +import call from './call' +import log from './log' +import util from './util' +import { bodyBuildings } from './buildings' + +/** + * `[{ id, name }]` for a body category (`colonies` / `planets` / `stations`), + * sorted by name. Replaces `lacuna.empire.findBodiesType`. + * + * @param {string} type - key on `status.empire` + * @return {Promise} + */ +export function findBodiesType(type) { + return call(() => lacuna.empire.getStatus()).then((status) => { + const arr = [] + + _.each(status.empire[type], (value, key) => { + arr.push({ id: key, name: value }) + }) + + return _.sortBy(arr, 'name') + }) +} + +export const colonies = () => findBodiesType('colonies') +export const planets = () => findBodiesType('planets') +export const stations = () => findBodiesType('stations') + +/** + * `{ id, name }` for the empire's home planet. + * + * @return {Promise} + */ +export function homePlanet() { + return call(() => lacuna.empire.getStatus()).then((status) => ({ + id: status.empire.home_planet_id, + name: status.empire.planets[status.empire.home_planet_id], + })) +} + +/** + * Resolve the `--planet` argument - the string `'all'`, a planet name, or an + * array of names - into `[{ id, name }]`, minus anything named in `skip`. + * Replaces `lacuna.empire.findPlanets`. + * + * @param {string|array} planet - the planet name(s), or `'all'` + * @param {string|array} [skip] - planet name(s) to leave out + * @return {Promise} + */ +export function findPlanets(planet, skip) { + const arr = util.array(planet) + const toSkip = util.array(skip) + + log.debug('Handling --planet', planet) + log.debug('Handling --skip', skip) + + if (arr.length === 0) { + return Promise.reject('please specify a planet') + } + + let bodies + if (_.includes(arr, 'all')) { + bodies = colonies() + } else { + bodies = call(() => lacuna.empire.getStatus()).then((status) => { + // Invert so we can key by name instead of id. + const byName = _.invert(status.empire.planets) + + return Promise.all( + arr.map((planetName) => { + const planetId = byName[planetName] + + if (planetId) { + return { id: planetId, name: planetName } + } + + return Promise.reject(`Planet ${planetName} not found`) + }) + ) + }) + } + + return bodies.then((found) => _.filter(found, (body) => !_.includes(toSkip, body.name))) +} + +/** + * The first match from `findPlanets`. Replaces `lacuna.empire.findPlanet`. + * + * @param {string|array} name + * @return {Promise} + */ +export function findPlanet(name) { + return findPlanets(util.array(name)).then(_.first) +} + +/** + * Loop through each given planet, fetching its buildings, skipping unhappy + * planets (unless `force`) and isolating per-planet errors so one failure + * doesn't kill the task. Replaces `lacuna.empire.eachPlanet`. + * + * @param {array} planetList - `[{ id, name }]` + * @param {function} callback - `(planet, buildings) => thenable` + * @param {{ force?: boolean }} [opts] + */ +export async function eachPlanet(planetList, callback, opts) { + opts = _.defaults(opts || {}, { force: false }) + + for (const planet of planetList) { + log.newline() + log.info(`Looking at ${planet.name}`) + + const result = await call(() => lacuna.body.getBuildings({ body_id: planet.id })) + const buildings = util.objectToArray(result.buildings, 'id') + + // Some responses carry a status block, some don't - fall back to a + // dedicated status call for the happiness check. + let happiness + if (result.status && result.status.body) { + happiness = result.status.body.happiness + } else { + const status = await call(() => lacuna.body.getStatus({ body_id: planet.id })) + happiness = status.body.happiness + } + + // Don't do anything on unhappy planets - making anything (ships, spies, + // buildings, SS plans) is heavily slowed down when happiness is negative. + if (util.int(happiness) < 0 && !opts.force) { + log.error(`${planet.name} is unhappy`) + continue + } + + await Promise.resolve(callback(planet, buildings)) + .catch((err) => util.handlePromiseError(err)) + .finally(() => log.info('Moving on')) + } +} + +/** + * Every building across all colonies, as a flat array. Replaces + * `lacuna.empire.getAllBuildings`. + * + * @return {Promise} + */ +export async function getAllBuildings() { + const cols = await colonies() + let all = [] + + for (const colony of cols) { + log.info(`Looking at ${colony.name}`) + const buildings = await bodyBuildings(colony.id) + all = all.concat(buildings) + } + + return all +} diff --git a/packages/core/src/server-url.js b/packages/core/src/server-url.js new file mode 100644 index 0000000..9109e36 --- /dev/null +++ b/packages/core/src/server-url.js @@ -0,0 +1,25 @@ +/** + * Resolve a `server` config value into a full base URL for `@tlecommunity/client`. + * + * Ported from the old `emissary.getUrl` host logic: a value that already looks + * like a URL is used as-is (normalised to exactly one trailing slash, which is + * what the client's own `Config` does); a bare shortname such as `us1` becomes + * `http://us1.lacunaexpanse.com/` (https in the browser, matching the page). + * + * @param {string} server - a full `http(s)://host` URL or a bare server shortname + * @return {string} the base URL, guaranteed to end in `/` + */ +export function resolveServerUrl(server) { + if (!server) { + throw 'No server configured' + } + + const value = String(server) + + if (value.startsWith('http')) { + return value.endsWith('/') ? value : `${value}/` + } + + const protocol = typeof window !== 'undefined' ? window.location.protocol : 'http:' + return `${protocol}//${value}.lacunaexpanse.com/` +} diff --git a/packages/core/src/util.js b/packages/core/src/util.js index d5927d3..6bac9d6 100644 --- a/packages/core/src/util.js +++ b/packages/core/src/util.js @@ -107,9 +107,9 @@ const util = { }, /** - * Determine if this is the CLI version of Le Serf + * Determine if this is the CLI version of Super UI * - * @return {Boolean} true if we're in the CLI version of Le Serf. + * @return {Boolean} true if we're in the CLI version of Super UI. */ isCLI() { return !util.isWeb() @@ -127,12 +127,12 @@ const util = { }, /** - * Determine if this is the Web version of Le Serf + * Determine if this is the Web version of Super UI * - * @return {Boolean} true if we're in the Web version of Le Serf. + * @return {Boolean} true if we're in the Web version of Super UI. */ isWeb() { - return typeof window === 'object' && window.LE_SERF_WEB === true + return typeof window === 'object' && window.SUPER_UI_WEB === true }, /** diff --git a/packages/tasks/src/archaeology-digs.js b/packages/tasks/src/archaeology-digs.js index 7aa9392..1f30435 100644 --- a/packages/tasks/src/archaeology-digs.js +++ b/packages/tasks/src/archaeology-digs.js @@ -1,6 +1,15 @@ import _ from 'lodash' -import { lacuna, log, oreDistributions, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + log, + oreDistributions, + util, +} from '@tlecommunity/super-core' const { ORE_TYPES, imageToken, oreVector } = oreDistributions @@ -29,13 +38,13 @@ class ArchaeologyDigs { } async handleColony(colony, buildings) { - let arch = await lacuna.body.findBuilding(buildings, 'Archaeology Ministry') + let arch = await findBuilding(buildings, 'Archaeology Ministry') if (!arch) { throw `No Archaeology Ministry on ${colony.name}` } - let view = await lacuna.buildings.archaeology.view([arch.id]) + let view = await call(() => lacuna.archaeology.view({ building_id: arch.id })) let work = (view.building || {}).work if (work && work.searching) { @@ -43,11 +52,13 @@ class ArchaeologyDigs { return } - let status = await lacuna.body.getStatus([colony.id]) + let status = await call(() => lacuna.body.getStatus({ body_id: colony.id })) let token = imageToken((status.body || {}).image) let ore = this.richestOre(token) - let available = await lacuna.buildings.archaeology.getOresAvailableForProcessing([arch.id]) + let available = await call(() => + lacuna.archaeology.getOresAvailableForProcessing({ building_id: arch.id }) + ) let stored = util.int((available.ore || {})[ore]) if (stored < 10000) { @@ -61,17 +72,16 @@ class ArchaeologyDigs { return } - await lacuna.buildings.archaeology.searchForGlyph([arch.id, ore]) + await call(() => lacuna.archaeology.searchForGlyph({ building_id: arch.id, ore_type: ore })) this.searchesStarted++ log.info(`Searching for a ${ore} glyph on ${colony.name}`) } run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this), { + return eachPlanet(colonies, _.bind(this.handleColony, this), { force: true, }) }) diff --git a/packages/tasks/src/build-ships.js b/packages/tasks/src/build-ships.js index 9d31f67..552d77f 100644 --- a/packages/tasks/src/build-ships.js +++ b/packages/tasks/src/build-ships.js @@ -1,6 +1,15 @@ import _ from 'lodash' -import { lacuna, log, types, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + log, + types, + util, +} from '@tlecommunity/super-core' class BuildShips { constructor(options) { @@ -12,34 +21,30 @@ class BuildShips { log.info(`Building ${quantity} ${plural}`) if (!this.options.dryRun) { - return lacuna.buildings.shipyard.buildShips([ - { - autoselect: 'all', - body_id: colony.id, - building_id: sy.id, - quantity, - type: this.options.serverType, - }, - ]) + return call(() => + lacuna.shipyard.buildShips({ + options: { + autoselect: 'all', + body_id: colony.id, + building_id: sy.id, + quantity, + type: this.options.serverType, + }, + }) + ) } } getExistingShipsCount(sp) { - return lacuna.buildings.spaceport - .viewAllShips([ - sp.id, - { - // paging - no_paging: true, - }, - { - // filter - type: this.options.serverType, - }, - ]) - .then((result) => { - return util.int(result.number_of_ships) + return call(() => + lacuna.spacePort.viewAllShips({ + building_id: sp.id, + paging: { no_paging: 1 }, + filter: { type: this.options.serverType }, }) + ).then((result) => { + return util.int(result.number_of_ships) + }) } fixQuantity(quantity, sy) { @@ -47,7 +52,7 @@ class BuildShips { return 0 } - return lacuna.buildings.shipyard.getBuildable([sy.id]).then((result) => { + return call(() => lacuna.shipyard.getBuildable({ building_id: sy.id })).then((result) => { let dockSpace = util.int(result.docks_available) let queueSpace = util.int(result.build_queue_max) - util.int(result.build_queue_used) @@ -74,8 +79,8 @@ class BuildShips { findShipBuildings(colony, buildings) { return new Promise(async (resolve, reject) => { - const sy = await lacuna.body.findBuilding(buildings, 'Shipyard') - const sp = await lacuna.body.findBuilding(buildings, 'Space Port') + const sy = await findBuilding(buildings, 'Shipyard') + const sp = await findBuilding(buildings, 'Space Port') if (!sy) { reject(`No Shipyard found on ${colony.name}`) @@ -142,10 +147,9 @@ class BuildShips { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this)) + return eachPlanet(colonies, _.bind(this.handleColony, this)) }) .then(() => { resolve() diff --git a/packages/tasks/src/building-levels.js b/packages/tasks/src/building-levels.js index 9aeed50..b018fa1 100644 --- a/packages/tasks/src/building-levels.js +++ b/packages/tasks/src/building-levels.js @@ -1,7 +1,7 @@ import _ from 'lodash' import Table from 'cli-table3' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { getAllBuildings, log, util } from '@tlecommunity/super-core' class BuildingLevels { constructor(options) { @@ -15,34 +15,29 @@ class BuildingLevels { } run() { - return lacuna - .authenticate() - .then(() => { - return lacuna.empire.getAllBuildings() + return getAllBuildings().then((buildings) => { + let table = new Table({ + head: ['Level', 'Number of Buildings'], }) - .then((buildings) => { - let table = new Table({ - head: ['Level', 'Number of Buildings'], - }) - _.each(_.range(0, 32), (level) => { - let num = _.filter(buildings, (b) => { - return util.int(b.level) === level - }).length + _.each(_.range(0, 32), (level) => { + let num = _.filter(buildings, (b) => { + return util.int(b.level) === level + }).length - table.push([level, num]) - }) + table.push([level, num]) + }) - log.newline() - log.info(table.toString()) - log.newline() + log.newline() + log.info(table.toString()) + log.newline() - let total = buildings.length - let mean = util.mean(_.map(buildings, 'level')) + let total = buildings.length + let mean = util.mean(_.map(buildings, 'level')) - log.info(`Average building level is ${mean}`) - log.info(`There are ${total} buildings`) - }) + log.info(`Average building level is ${mean}`) + log.info(`There are ${total} buildings`) + }) } } diff --git a/packages/tasks/src/building-types.js b/packages/tasks/src/building-types.js index d443e9f..29adac6 100644 --- a/packages/tasks/src/building-types.js +++ b/packages/tasks/src/building-types.js @@ -1,7 +1,7 @@ import _ from 'lodash' import Table from 'cli-table3' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { getAllBuildings, log, util } from '@tlecommunity/super-core' class BuildingTypes { constructor(options) { @@ -15,42 +15,37 @@ class BuildingTypes { } run() { - return lacuna - .authenticate() - .then(() => { - return lacuna.empire.getAllBuildings() + return getAllBuildings().then((buildings) => { + let table = new Table({ + head: ['Quantity', 'Type', 'Average Level'], }) - .then((buildings) => { - let table = new Table({ - head: ['Quantity', 'Type', 'Average Level'], - }) - - _.chain(buildings) - .thru((buildings) => { - let obj = {} - _.each(buildings, (b) => { - if (!obj[b.name]) { - obj[b.name] = [] - } + _.chain(buildings) + .thru((buildings) => { + let obj = {} - obj[b.name] = obj[b.name].concat([util.int(b.level)]) - }) + _.each(buildings, (b) => { + if (!obj[b.name]) { + obj[b.name] = [] + } - return obj + obj[b.name] = obj[b.name].concat([util.int(b.level)]) }) - .mapValues((levels, name) => { - return [levels.length, name, util.mean(levels)] - }) - .values() - .sortBy((arr) => arr[1]) - .sortBy((arr) => arr[0]) - .each((b) => table.push(b)) - .value() - - log.newline() - log.info(table.toString()) - }) + + return obj + }) + .mapValues((levels, name) => { + return [levels.length, name, util.mean(levels)] + }) + .values() + .sortBy((arr) => arr[1]) + .sortBy((arr) => arr[0]) + .each((b) => table.push(b)) + .value() + + log.newline() + log.info(table.toString()) + }) } } diff --git a/packages/tasks/src/docked-ships.js b/packages/tasks/src/docked-ships.js index 35c0ed1..6434d7d 100644 --- a/packages/tasks/src/docked-ships.js +++ b/packages/tasks/src/docked-ships.js @@ -1,7 +1,15 @@ import _ from 'lodash' import Table from 'cli-table3' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + log, + util, +} from '@tlecommunity/super-core' class DockedShips { constructor(options) { @@ -11,8 +19,7 @@ class DockedShips { } handleColony(colony, buildings) { - return lacuna.body - .findBuilding(buildings, 'Space Port') + return findBuilding(buildings, 'Space Port') .then((sp) => { if (!sp) { return new Promise((resolve, reject) => { @@ -20,15 +27,13 @@ class DockedShips { }) } - return lacuna.buildings.spaceport.viewAllShips([ - sp.id, - { - no_paging: 1, - }, - { - task: 'Docked', - }, - ]) + return call(() => + lacuna.spacePort.viewAllShips({ + building_id: sp.id, + paging: { no_paging: 1 }, + filter: { task: 'Docked' }, + }) + ) }) .then((result) => { let table = new Table({ @@ -84,10 +89,9 @@ class DockedShips { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this), { + return eachPlanet(colonies, _.bind(this.handleColony, this), { force: true, }) }) diff --git a/packages/tasks/src/docks.js b/packages/tasks/src/docks.js index ea06bcd..6882b44 100644 --- a/packages/tasks/src/docks.js +++ b/packages/tasks/src/docks.js @@ -1,7 +1,15 @@ import _ from 'lodash' import Table from 'cli-table3' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + colonies, + eachPlanet, + findBuilding, + log, + util, +} from '@tlecommunity/super-core' class Docks { constructor(options) { @@ -11,14 +19,14 @@ class Docks { } handleColony(colony, buildings) { - return lacuna.body.findBuilding(buildings, 'Space Port').then((sp) => { + return findBuilding(buildings, 'Space Port').then((sp) => { if (!sp) { return new Promise((resolve, reject) => { reject(`No Space Port found on ${colony.name}`) }) } - return lacuna.buildings.spaceport.view([sp.id]).then((result) => { + return call(() => lacuna.spacePort.view({ building_id: sp.id })).then((result) => { let docks = util.int(result.max_ships) let ships = docks - util.int(result.docks_available) @@ -39,10 +47,9 @@ class Docks { run() { return new Promise((resolve, reject) => { - lacuna.empire - .colonies() + colonies() .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this)) + return eachPlanet(colonies, _.bind(this.handleColony, this)) }) .then(() => { let table = new Table({ diff --git a/packages/tasks/src/glyph-report.ts b/packages/tasks/src/glyph-report.ts index d3eeaa1..5818387 100644 --- a/packages/tasks/src/glyph-report.ts +++ b/packages/tasks/src/glyph-report.ts @@ -1,7 +1,13 @@ import _ from 'lodash' import Table from 'cli-table3' -import { lacuna, log } from '@tlecommunity/super-core' +import { + findPlanets, + eachPlanet, + findBuilding, + archGetInventory, + log, +} from '@tlecommunity/super-core' type RecipeList = Array @@ -86,14 +92,14 @@ class GlyphReport { } handleColony(colony, buildings) { - return lacuna.body.findBuilding(buildings, 'Archaeology Ministry').then((archaeology) => { + return findBuilding(buildings, 'Archaeology Ministry').then((archaeology) => { if (!archaeology) { return new Promise((resolve, reject) => { reject(`No Archaeology Ministry on ${colony.name}`) }) } - return lacuna.buildings.archaeology.getInventory(archaeology.id).then((inventory) => { + return archGetInventory(archaeology.id).then((inventory) => { return this.reportGlyphs(colony, archaeology, inventory) }) }) @@ -101,10 +107,9 @@ class GlyphReport { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this), { + return eachPlanet(colonies, _.bind(this.handleColony, this), { force: true, }) }) diff --git a/packages/tasks/src/index.js b/packages/tasks/src/index.js index 3fbfa34..334a944 100644 --- a/packages/tasks/src/index.js +++ b/packages/tasks/src/index.js @@ -22,10 +22,10 @@ import spyStatus from './spy-status' import spyTrainer from './spy-trainer' import throwParties from './throw-parties' import viewLaws from './view-laws' -import glyphReport from './glyph-report' +import glyphReport from './glyph-report.ts' /* - Hello and welcome to Le Serf! + Hello and welcome to TLE Super UI! This is the part of the code where every task that can be run is assembled into a big list that is then used by the Web and CLI packages. This diff --git a/packages/tasks/src/make-halls.js b/packages/tasks/src/make-halls.js index 9329fb1..c56058f 100644 --- a/packages/tasks/src/make-halls.js +++ b/packages/tasks/src/make-halls.js @@ -1,6 +1,15 @@ import _ from 'lodash' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + archGetInventory, + log, + util, +} from '@tlecommunity/super-core' const RECIPES = [ 'goethite halite gypsum trona'.split(' '), @@ -42,11 +51,11 @@ class MakeHalls { log.info(`Making ${quantity} ${plural} with ${prettyList}`) - return lacuna.buildings.archaeology - .assembleGlyphs([archaeology.id, recipe, quantity]) - .then((result) => { - this.numMade += quantity - }) + return call(() => + lacuna.archaeology.assembleGlyphs({ building_id: archaeology.id, glyphs: recipe, quantity }) + ).then((result) => { + this.numMade += quantity + }) } async makeHalls(colony, archaeology, inventory) { @@ -56,14 +65,14 @@ class MakeHalls { } handleColony(colony, buildings) { - return lacuna.body.findBuilding(buildings, 'Archaeology Ministry').then((archaeology) => { + return findBuilding(buildings, 'Archaeology Ministry').then((archaeology) => { if (!archaeology) { return new Promise((resolve, reject) => { reject(`No Archaeology Ministry on ${colony.name}`) }) } - return lacuna.buildings.archaeology.getInventory(archaeology.id).then((inventory) => { + return archGetInventory(archaeology.id).then((inventory) => { return this.makeHalls(colony, archaeology, inventory) }) }) @@ -71,10 +80,9 @@ class MakeHalls { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this), { + return eachPlanet(colonies, _.bind(this.handleColony, this), { force: true, }) }) diff --git a/packages/tasks/src/make-spies.js b/packages/tasks/src/make-spies.js index e3c364b..ab85db2 100644 --- a/packages/tasks/src/make-spies.js +++ b/packages/tasks/src/make-spies.js @@ -1,6 +1,14 @@ import _ from 'lodash' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + colonies, + eachPlanet, + findBuilding, + log, + util, +} from '@tlecommunity/super-core' class MakeSpies { constructor(options) { @@ -31,26 +39,28 @@ class MakeSpies { log.info(`Making ${num} ${plural}`) - await lacuna.buildings.intelligence.trainSpy([intelId, num]).then((result) => { - if (util.int(result.not_trained) > 0) { - return new Promise((resolve, reject) => { - reject(result.reason_not_trained.message) - }) - } else { - this.spiesTrained += util.int(result.trained) + await call(() => lacuna.intelligence.trainSpy({ building_id: intelId, quantity: num })).then( + (result) => { + if (util.int(result.not_trained) > 0) { + return new Promise((resolve, reject) => { + reject(result.reason_not_trained.message) + }) + } else { + this.spiesTrained += util.int(result.trained) + } } - }) + ) } } handleColony(colony, buildings) { - return lacuna.body.findBuilding(colony.id, 'Intelligence Ministry').then((intelMin) => { + return findBuilding(colony.id, 'Intelligence Ministry').then((intelMin) => { if (intelMin === undefined) { log.warn(`No Intelligence Ministry on ${colony.name}`) return } - return lacuna.buildings.intelligence.view([intelMin.id]).then((result) => { + return call(() => lacuna.intelligence.view({ building_id: intelMin.id })).then((result) => { let spies = result.spies let max = util.int(spies.maximum) let current = util.int(spies.in_training) + util.int(spies.current) @@ -76,10 +86,9 @@ class MakeSpies { run() { return new Promise((resolve, reject) => { - lacuna.empire - .colonies() + colonies() .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this)) + return eachPlanet(colonies, _.bind(this.handleColony, this)) }) .then((results) => { let plural = util.handlePlurality(this.spiesTrained, 'spy') diff --git a/packages/tasks/src/push-buildings-up.js b/packages/tasks/src/push-buildings-up.js index 36590e6..77f82a0 100644 --- a/packages/tasks/src/push-buildings-up.js +++ b/packages/tasks/src/push-buildings-up.js @@ -1,6 +1,14 @@ import _ from 'lodash' -import { lacuna, log, timelist as Timelist, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + log, + timelist as Timelist, + util, +} from '@tlecommunity/super-core' const DONT_UPGRADE = [ 'Pyramid Junk Sculpture', @@ -74,9 +82,7 @@ class PushBuildingsUp { return } - await lacuna.buildings - .generic(b.url) - .upgrade([b.id]) + await call(() => lacuna.buildingFromUrl(b.url).upgrade({ building_id: b.id })) .then((result) => { this.upgraded++ this.timelist.add(result.building.pending_build.end) @@ -151,10 +157,9 @@ class PushBuildingsUp { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this)) + return eachPlanet(colonies, _.bind(this.handleColony, this)) }) .then(() => { let plural = util.handlePlurality(this.upgraded, 'building') diff --git a/packages/tasks/src/push-glyphs.js b/packages/tasks/src/push-glyphs.js index 75a0766..2f5602a 100644 --- a/packages/tasks/src/push-glyphs.js +++ b/packages/tasks/src/push-glyphs.js @@ -1,6 +1,15 @@ import _ from 'lodash' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + findPlanet, + eachPlanet, + findBuilding, + log, + util, +} from '@tlecommunity/super-core' class PushGlyphs { constructor(options) { @@ -48,15 +57,24 @@ class PushGlyphs { } async handleSending(from, to, tradeId) { - let trade = lacuna.buildings.trade - - const glyphSummary = await trade.getGlyphSummary([tradeId]) + // `get_glyph_summary` has no working /v2 method and the typed one drops the + // per-glyph `name` + `cargo_space_used_each` this relies on - go legacy. + const glyphSummary = await call(() => + lacuna.server.callLegacy({ + module: 'trade', + method: 'get_glyph_summary', + params: [tradeId], + addSession: true, + }) + ) if (glyphSummary.glyphs.length === 0) { log.info('No glyphs to push') return } - const { ships } = await trade.getTradeShips([tradeId, to.id]) + const { ships } = await call(() => + lacuna.trade.getTradeShips({ building_id: tradeId, target_body_id: to.id }) + ) if (ships.length === 0) { log.error('No ships for pushing glyphs') @@ -90,15 +108,14 @@ class PushGlyphs { `Cargo: ${_.map(cargo, (glyph) => `${_.capitalize(glyph.name)}[${glyph.quantity}]`).join(', ')}` ) - const result = await trade.pushItems([ - tradeId, - to.id, - cargo, - { - id: ship.id, - stay: 0, - }, - ]) + const result = await call(() => + lacuna.trade.pushItems({ + building_id: tradeId, + target_id: to.id, + items: cargo, + options: { ship_id: ship.id, stay: 0 }, + }) + ) const arrival = util.formatServerDate(result.ship.date_arrives) @@ -110,7 +127,7 @@ class PushGlyphs { } async pushGlyphs(from, to, buildings) { - const tradeMin = await lacuna.body.findBuilding(buildings, 'Trade Ministry') + const tradeMin = await findBuilding(buildings, 'Trade Ministry') if (!tradeMin) { log.error(`No Trade Ministry found on ${from.name}`) @@ -130,20 +147,20 @@ class PushGlyphs { } async run() { - const status = await lacuna.empire.getStatus([]) + const status = await call(() => lacuna.empire.getStatus()) if (status.empire.is_isolationist == 1) { log.info('Your empire is an isolationist and only has one colony.') } - const fromColonies = await lacuna.empire.findPlanets(this.options.from, this.options.to) - const to = await lacuna.empire.findPlanet(this.options.to) + const fromColonies = await findPlanets(this.options.from, this.options.to) + const to = await findPlanet(this.options.to) if (!fromColonies.length) { log.info('No colonies to push glyphs from.') } - await lacuna.empire.eachPlanet( + await eachPlanet( fromColonies, (colony, buildings) => { return this.pushGlyphs(colony, to, buildings) diff --git a/packages/tasks/src/scuttle-ships.js b/packages/tasks/src/scuttle-ships.js index daf8068..cfc107f 100644 --- a/packages/tasks/src/scuttle-ships.js +++ b/packages/tasks/src/scuttle-ships.js @@ -1,6 +1,15 @@ import _ from 'lodash' -import { lacuna, log, types, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + log, + types, + util, +} from '@tlecommunity/super-core' class ScuttleShips { constructor(options) { @@ -20,37 +29,37 @@ class ScuttleShips { } handleColony(colony, buildings) { - return lacuna.body.findBuilding(buildings, 'Space Port').then((sp) => { + return findBuilding(buildings, 'Space Port').then((sp) => { if (!sp) { return new Promise((resole, reject) => { reject(`No Space Port found on ${colony.name}`) }) } - return lacuna.buildings.spaceport - .viewAllShips([ - sp.id, - { - no_paging: 1, - }, - this.getFilter(), - ]) - .then((result) => { - let hitlist = _.map(result.ships, 'id') - let num = hitlist.length - let noun = this.options.type === 'all' ? 'ship' : this.options.type - let shipPlural = util.handlePlurality(num, noun) + return call(() => + lacuna.spacePort.viewAllShips({ + building_id: sp.id, + paging: { no_paging: 1 }, + filter: this.getFilter(), + }) + ).then((result) => { + let hitlist = _.map(result.ships, 'id') + let num = hitlist.length + let noun = this.options.type === 'all' ? 'ship' : this.options.type + let shipPlural = util.handlePlurality(num, noun) - if (num > 0) { - log.info(`Scuttling ${num} ${shipPlural}`) - } else { - return new Promise((resolve, reject) => { - reject(`No ${shipPlural} to scuttle on ${colony.name}`) - }) - } + if (num > 0) { + log.info(`Scuttling ${num} ${shipPlural}`) + } else { + return new Promise((resolve, reject) => { + reject(`No ${shipPlural} to scuttle on ${colony.name}`) + }) + } - return lacuna.buildings.spaceport.massScuttleShip([sp.id, hitlist]) - }) + return call(() => + lacuna.spacePort.massScuttleShip({ building_id: sp.id, ship_ids: hitlist }) + ) + }) }) } @@ -76,10 +85,9 @@ class ScuttleShips { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this)) + return eachPlanet(colonies, _.bind(this.handleColony, this)) }) .then(() => { resolve() diff --git a/packages/tasks/src/send-excavators.js b/packages/tasks/src/send-excavators.js index f6e2450..096346b 100644 --- a/packages/tasks/src/send-excavators.js +++ b/packages/tasks/src/send-excavators.js @@ -1,6 +1,15 @@ import _ from 'lodash' -import { lacuna, log, oreDistributions, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + log, + oreDistributions, + util, +} from '@tlecommunity/super-core' import { buildRoom, distance, scanOutward } from './starmap' @@ -42,15 +51,17 @@ class SendExcavators { log.info(`Building ${quantity} ${plural} on ${colony.name}`) if (!this.options.dryRun) { - await lacuna.buildings.shipyard.buildShips([ - { - autoselect: 'all', - body_id: colony.id, - building_id: sy.id, - quantity, - type: 'excavator', - }, - ]) + await call(() => + lacuna.shipyard.buildShips({ + options: { + autoselect: 'all', + body_id: colony.id, + building_id: sy.id, + quantity, + type: 'excavator', + }, + }) + ) } } @@ -110,9 +121,9 @@ class SendExcavators { } async handleColony(colony, buildings) { - let arch = await lacuna.body.findBuilding(buildings, 'Archaeology Ministry') - let sy = await lacuna.body.findBuilding(buildings, 'Shipyard') - let sp = await lacuna.body.findBuilding(buildings, 'Space Port') + let arch = await findBuilding(buildings, 'Archaeology Ministry') + let sy = await findBuilding(buildings, 'Shipyard') + let sp = await findBuilding(buildings, 'Space Port') if (!arch || util.int(arch.level) < 11) { log.info(`${colony.name} needs a level 11+ Archaeology Ministry - skipping`) @@ -124,19 +135,22 @@ class SendExcavators { return } - let ex = await lacuna.buildings.archaeology.viewExcavators([arch.id]) + let ex = await call(() => lacuna.archaeology.viewExcavators({ building_id: arch.id })) let sites = ex.excavators || [] let deployed = sites.filter((s) => util.int(s.id) !== 0).length let inTransit = util.int(ex.travelling) let max = util.int(ex.max_excavators) let usedIds = new Set(sites.map((s) => s.body && s.body.id).filter(Boolean)) - let ships = await lacuna.buildings.spaceport.viewAllShips([ - sp.id, - { no_paging: 1 }, - { type: 'excavator', task: 'Docked' }, - ]) + let ships = await call(() => + lacuna.spacePort.viewAllShips({ + building_id: sp.id, + paging: { no_paging: 1 }, + filter: { type: 'excavator', task: 'Docked' }, + }) + ) let dockedExcavators = ships.ships || [] + console.log('dockedExcavators', dockedExcavators) let docked = dockedExcavators.length let freeCap = max - deployed - inTransit @@ -154,7 +168,7 @@ class SendExcavators { return } - let status = await lacuna.body.getStatus([colony.id]) + let status = await call(() => lacuna.body.getStatus({ body_id: colony.id })) let origin = { x: util.int(status.body.x), y: util.int(status.body.y) } let candidates = [] @@ -221,7 +235,9 @@ class SendExcavators { } try { - await lacuna.buildings.spaceport.sendShip([ship.id, { body_id: target.id }]) + await call(() => + lacuna.spacePort.sendShip({ ship_id: ship.id, target: { body_id: target.id } }) + ) sent++ this.excavatorsSent++ log.info( @@ -243,10 +259,9 @@ class SendExcavators { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this), { + return eachPlanet(colonies, _.bind(this.handleColony, this), { force: true, }) }) diff --git a/packages/tasks/src/send-probes.js b/packages/tasks/src/send-probes.js index e463f6f..f3a1760 100644 --- a/packages/tasks/src/send-probes.js +++ b/packages/tasks/src/send-probes.js @@ -1,6 +1,14 @@ import _ from 'lodash' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + log, + util, +} from '@tlecommunity/super-core' import { buildRoom, distance, scanOutward } from './starmap' @@ -37,38 +45,44 @@ class SendProbes { log.info(`Building ${quantity} ${plural} on ${colony.name}`) if (!this.options.dryRun) { - await lacuna.buildings.shipyard.buildShips([ - { - autoselect: 'all', - body_id: colony.id, - building_id: sy.id, - quantity, - type: 'probe', - }, - ]) + await call(() => + lacuna.shipyard.buildShips({ + options: { + autoselect: 'all', + body_id: colony.id, + building_id: sy.id, + quantity, + type: 'probe', + }, + }) + ) } } async handleColony(colony, buildings) { - let obs = await lacuna.body.findBuilding(buildings, 'Observatory') - let sy = await lacuna.body.findBuilding(buildings, 'Shipyard') - let sp = await lacuna.body.findBuilding(buildings, 'Space Port') + let obs = await findBuilding(buildings, 'Observatory') + let sy = await findBuilding(buildings, 'Shipyard') + let sp = await findBuilding(buildings, 'Space Port') if (!obs || !sy || !sp) { log.info(`${colony.name} needs an Observatory, Shipyard and Space Port - skipping`) return } - let probed = await lacuna.buildings.observatory.getProbedStars([obs.id, 1]) + let probed = await call(() => + lacuna.observatory.getProbedStars({ building_id: obs.id, page_number: 1 }) + ) let controlled = util.int(probed.star_count) let inTransit = util.int(probed.travelling) let max = util.int(probed.max_probes) - let ships = await lacuna.buildings.spaceport.viewAllShips([ - sp.id, - { no_paging: 1 }, - { type: 'probe', task: 'Docked' }, - ]) + let ships = await call(() => + lacuna.spacePort.viewAllShips({ + building_id: sp.id, + paging: { no_paging: 1 }, + filter: { type: 'probe', task: 'Docked' }, + }) + ) let dockedProbes = ships.ships || [] let docked = dockedProbes.length @@ -92,7 +106,7 @@ class SendProbes { return } - let status = await lacuna.body.getStatus([colony.id]) + let status = await call(() => lacuna.body.getStatus({ body_id: colony.id })) let origin = { x: util.int(status.body.x), y: util.int(status.body.y) } let candidates = [] @@ -117,8 +131,9 @@ class SendProbes { break } - let incoming = await lacuna.map.checkStarForIncomingProbe([star.id]) - if (incoming.incoming_probe && incoming.incoming_probe !== '0') { + let incoming = await call(() => lacuna.map.checkStarForIncomingProbe({ star_id: star.id })) + // The client coerces numeric strings to numbers, so compare numerically. + if (incoming.incoming_probe && util.int(incoming.incoming_probe) !== 0) { continue } @@ -145,7 +160,9 @@ class SendProbes { } try { - await lacuna.buildings.spaceport.sendShip([ship.id, { star_id: star.id }]) + await call(() => + lacuna.spacePort.sendShip({ ship_id: ship.id, target: { star_id: star.id } }) + ) this.probesSent++ log.info(`Sent a probe from ${colony.name} to ${star.name} (${dist} away)`) } catch (err) { @@ -163,10 +180,9 @@ class SendProbes { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this), { + return eachPlanet(colonies, _.bind(this.handleColony, this), { force: true, }) }) diff --git a/packages/tasks/src/spy-skills.js b/packages/tasks/src/spy-skills.js index 2c1794a..8f36644 100644 --- a/packages/tasks/src/spy-skills.js +++ b/packages/tasks/src/spy-skills.js @@ -1,7 +1,15 @@ import _ from 'lodash' import Table from 'cli-table3' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + log, + util, +} from '@tlecommunity/super-core' class SpyStatus { constructor(options) { @@ -9,8 +17,7 @@ class SpyStatus { } handleColony(colony, buildings) { - return lacuna.body - .findBuilding(buildings, 'Intelligence Ministry') + return findBuilding(buildings, 'Intelligence Ministry') .then((intelMin) => { if (!intelMin) { return new Promise((resolve, reject) => { @@ -18,7 +25,7 @@ class SpyStatus { }) } - return lacuna.buildings.intelligence.viewAllSpies([intelMin.id]) + return call(() => lacuna.intelligence.viewAllSpies({ building_id: intelMin.id })) }) .then((result) => { let table = new Table({ @@ -61,10 +68,9 @@ class SpyStatus { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this)) + return eachPlanet(colonies, _.bind(this.handleColony, this)) }) .then(() => { resolve() diff --git a/packages/tasks/src/spy-status.js b/packages/tasks/src/spy-status.js index fea80b3..770df41 100644 --- a/packages/tasks/src/spy-status.js +++ b/packages/tasks/src/spy-status.js @@ -1,7 +1,15 @@ import _ from 'lodash' import Table from 'cli-table3' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + log, + util, +} from '@tlecommunity/super-core' class SpyStatus { constructor(options) { @@ -9,8 +17,7 @@ class SpyStatus { } handleColony(colony, buildings) { - return lacuna.body - .findBuilding(buildings, 'Intelligence Ministry') + return findBuilding(buildings, 'Intelligence Ministry') .then((intelMin) => { if (!intelMin) { return new Promise((resolve, reject) => { @@ -18,7 +25,7 @@ class SpyStatus { }) } - return lacuna.buildings.intelligence.viewAllSpies([intelMin.id]) + return call(() => lacuna.intelligence.viewAllSpies({ building_id: intelMin.id })) }) .then((result) => { let table = new Table({ @@ -64,10 +71,9 @@ class SpyStatus { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this)) + return eachPlanet(colonies, _.bind(this.handleColony, this)) }) .then(() => { resolve() diff --git a/packages/tasks/src/spy-trainer.js b/packages/tasks/src/spy-trainer.js index 4c89e5c..c2029d7 100644 --- a/packages/tasks/src/spy-trainer.js +++ b/packages/tasks/src/spy-trainer.js @@ -1,6 +1,16 @@ import _ from 'lodash' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + findPlanets, + eachPlanet, + findBuilding, + bodyBuildings, + captchaPrompt, + log, + util, +} from '@tlecommunity/super-core' const MISSION_NAMES = { intel: 'Intel Training', @@ -27,11 +37,15 @@ class SpyTrainer { log.info(`Setting ${spy.name} to ${newAssignment.toLowerCase()}`) if (!this.options.dryRun) { - return lacuna.buildings.intelligence - .assignSpy([intelMin.id, spy.id, newAssignment]) - .then((result) => { - this.trained++ + return call(() => + lacuna.intelligence.assignSpy({ + building_id: intelMin.id, + spy_id: spy.id, + assignment: newAssignment, }) + ).then((result) => { + this.trained++ + }) } } @@ -96,22 +110,24 @@ class SpyTrainer { } getSpies(intelMin, maxSkillLevels, colony) { - return lacuna.buildings.intelligence.viewAllSpies([intelMin.id]).then((result) => { - return _.chain(result.spies) - .filter((spy) => { - if ( - this.isAvailable(spy) && - !this.isMaxed(spy, maxSkillLevels) && - this.isAt(spy, colony) - ) { - return true - } else { - return false - } - }) - .sortBy((spy) => util.int(spy.id)) - .value() - }) + return call(() => lacuna.intelligence.viewAllSpies({ building_id: intelMin.id })).then( + (result) => { + return _.chain(result.spies) + .filter((spy) => { + if ( + this.isAvailable(spy) && + !this.isMaxed(spy, maxSkillLevels) && + this.isAt(spy, colony) + ) { + return true + } else { + return false + } + }) + .sortBy((spy) => util.int(spy.id)) + .value() + } + ) } getMaxSkillLevels(skillBuildings) { @@ -129,13 +145,13 @@ class SpyTrainer { } findSpyBuildings(colony, buildings) { - return lacuna.body.buildings(colony.id).then(async (buildings) => { + return bodyBuildings(colony.id).then(async (buildings) => { let intelBuildings = { - intelMin: await lacuna.body.findBuilding(buildings, 'Intelligence Ministry'), - intel: await lacuna.body.findBuilding(buildings, 'Intel Training'), - mayhem: await lacuna.body.findBuilding(buildings, 'Mayhem Training'), - politics: await lacuna.body.findBuilding(buildings, 'Politics Training'), - theft: await lacuna.body.findBuilding(buildings, 'Theft Training'), + intelMin: await findBuilding(buildings, 'Intelligence Ministry'), + intel: await findBuilding(buildings, 'Intel Training'), + mayhem: await findBuilding(buildings, 'Mayhem Training'), + politics: await findBuilding(buildings, 'Politics Training'), + theft: await findBuilding(buildings, 'Theft Training'), } let intelMin = intelBuildings.intelMin @@ -180,17 +196,16 @@ class SpyTrainer { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { if (!this.options.dryRun) { - return lacuna.captcha.prompt().then(() => colonies) + return captchaPrompt().then(() => colonies) } else { return colonies } }) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this)) + return eachPlanet(colonies, _.bind(this.handleColony, this)) }) .then(() => { let plural = util.handlePlurality(this.trained, 'spy') diff --git a/packages/tasks/src/starmap.js b/packages/tasks/src/starmap.js index a60215a..bb20736 100644 --- a/packages/tasks/src/starmap.js +++ b/packages/tasks/src/starmap.js @@ -1,6 +1,6 @@ import _ from 'lodash' -import { lacuna } from '@tlecommunity/super-core' +import { lacuna, call } from '@tlecommunity/super-core' // `get_star_map` rejects a rectangle whose area exceeds 3001, so each window is a // 54x54 square (2916). Rings step outward by a full window width. @@ -51,12 +51,14 @@ export async function scanOutward(origin, { collect, enough, maxRing = 2 }) { for (let k = 0; k <= maxRing; k++) { for (const centre of ringCentres(origin, k)) { - let res = await lacuna.map.getStarMap({ - left: Math.round(centre.x - HALF), - right: Math.round(centre.x + HALF), - bottom: Math.round(centre.y - HALF), - top: Math.round(centre.y + HALF), - }) + let res = await call(() => + lacuna.map.getStarMap({ + left: Math.round(centre.x - HALF), + right: Math.round(centre.x + HALF), + bottom: Math.round(centre.y - HALF), + top: Math.round(centre.y + HALF), + }) + ) for (const star of res.stars || []) { if (seen.has(star.id)) { @@ -90,7 +92,7 @@ export async function buildRoom(shipyard, desired) { return 0 } - let buildable = await lacuna.buildings.shipyard.getBuildable([shipyard.id]) + let buildable = await call(() => lacuna.shipyard.getBuildable({ building_id: shipyard.id })) let dockSpace = parseInt(buildable.docks_available, 10) || 0 let queueSpace = (parseInt(buildable.build_queue_max, 10) || 0) - (parseInt(buildable.build_queue_used, 10) || 0) diff --git a/packages/tasks/src/throw-parties.js b/packages/tasks/src/throw-parties.js index 2ced3a2..c76ff1f 100644 --- a/packages/tasks/src/throw-parties.js +++ b/packages/tasks/src/throw-parties.js @@ -1,6 +1,6 @@ import _ from 'lodash' -import { lacuna, log, util } from '@tlecommunity/super-core' +import { lacuna, call, findPlanets, eachPlanet, log, util } from '@tlecommunity/super-core' class ThrowParties { constructor(options) { @@ -21,7 +21,7 @@ class ThrowParties { } async handleParty(colony, park) { - let view = await lacuna.buildings.park.view([park.id]) + let view = await call(() => lacuna.park.view({ building_id: park.id })) let party = view.party || {} if (party.seconds_remaining) { @@ -40,7 +40,7 @@ class ThrowParties { return } - let result = await lacuna.buildings.park.throwAParty([park.id]) + let result = await call(() => lacuna.park.throwAParty({ building_id: park.id })) let gained = util.int((result.party || {}).happiness) this.partiesThrown++ @@ -66,10 +66,9 @@ class ThrowParties { run() { return new Promise((resolve, reject) => { - lacuna.empire - .findPlanets(this.options.planet) + findPlanets(this.options.planet) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, _.bind(this.handleColony, this), { + return eachPlanet(colonies, _.bind(this.handleColony, this), { force: true, }) }) diff --git a/packages/tasks/src/upgrade-buildings.js b/packages/tasks/src/upgrade-buildings.js index ae50955..59fa263 100644 --- a/packages/tasks/src/upgrade-buildings.js +++ b/packages/tasks/src/upgrade-buildings.js @@ -1,6 +1,16 @@ import _ from 'lodash' -import { lacuna, log, timelist as Timelist, util } from '@tlecommunity/super-core' +import { + lacuna, + call, + colonies, + findPlanets, + eachPlanet, + findBuildings, + log, + timelist as Timelist, + util, +} from '@tlecommunity/super-core' // This is the template that the upgrader follows. // The template is comprised of sections. @@ -392,9 +402,9 @@ class UpgradeBuildings { // - rejecting if there's any error checkPossibleUpgrade(bd) { return new Promise((resolve, reject) => { - let b = lacuna.buildings.generic(bd.url) + let b = lacuna.buildingFromUrl(bd.url) - b.view([bd.id]) + call(() => b.view({ building_id: bd.id })) .then((result) => { if (result.building.upgrade.can) { resolve() @@ -416,9 +426,9 @@ class UpgradeBuildings { log.info(`Upgrading ${bd.name} (${bd.x}, ${bd.y}) from ${bd.level} to ` + `${bd.level * 1 + 1}`) return this.checkPossibleUpgrade(bd).then(() => { - let b = lacuna.buildings.generic(bd.url) + let b = lacuna.buildingFromUrl(bd.url) - return b.upgrade([bd.id]).then((result) => { + return call(() => b.upgrade({ building_id: bd.id })).then((result) => { this.timelist.add(result.building.pending_build.end) this.upgraded++ }) @@ -444,7 +454,7 @@ class UpgradeBuildings { // A build is an upgrade that should be done according to the template. // Here, we go through each one and see if it can be done. - await lacuna.body.findBuildings(buildings, build.name).then(async (matches) => { + await findBuildings(buildings, build.name).then(async (matches) => { // A 'match' is a building that matches the current 'build' within // the current 'section'. @@ -493,13 +503,12 @@ class UpgradeBuildings { run() { return new Promise((resolve, reject) => { - lacuna.empire - .colonies() + colonies() .then((colonies) => { - return lacuna.empire.findPlanets(this.options.planet, this.options.skip) + return findPlanets(this.options.planet, this.options.skip) }) .then((colonies) => { - return lacuna.empire.eachPlanet(colonies, (colony, buildings) => { + return eachPlanet(colonies, (colony, buildings) => { // Add existing build queues to the list. _.each(buildings, (b) => { if (b.pending_build) { diff --git a/packages/tasks/src/view-laws.js b/packages/tasks/src/view-laws.js index 920e431..3c3fade 100644 --- a/packages/tasks/src/view-laws.js +++ b/packages/tasks/src/view-laws.js @@ -1,7 +1,7 @@ import _ from 'lodash' import Table from 'cli-table3' -import { lacuna, log } from '@tlecommunity/super-core' +import { lacuna, call, log } from '@tlecommunity/super-core' class ViewLaws { constructor(options) { @@ -20,8 +20,14 @@ class ViewLaws { run() { return new Promise((resolve, reject) => { - lacuna.body - .viewLaws([this.options.id]) + call(() => + lacuna.server.callLegacy({ + module: 'body', + method: 'view_laws', + params: [this.options.id], + addSession: true, + }) + ) .then((result) => { if (result.laws.length > 0) { let table = new Table({ diff --git a/packages/web/index.html b/packages/web/index.html index bb8c686..63c09db 100644 --- a/packages/web/index.html +++ b/packages/web/index.html @@ -24,8 +24,8 @@ diff --git a/packages/web/src/bootstrapper.js b/packages/web/src/bootstrapper.js index 13c9c7e..4c7ef22 100644 --- a/packages/web/src/bootstrapper.js +++ b/packages/web/src/bootstrapper.js @@ -1,5 +1,5 @@ import lacuna from '@tlecommunity/super-core/src/client' -import { lacuna as legacyLacuna } from '@tlecommunity/super-core' +import { login } from '@tlecommunity/super-core' import * as App from './app' import EmpireStore from './stores/empire' @@ -19,18 +19,16 @@ let freshLogin = async (config) => { return } - // TODO: error handling - // App.error(message); - // App.navigate('/login'); - lacuna.config.serverUrl = config.server + try { + await login({ empire: config.empire, password: config.password, server: config.server }) + } catch (message) { + App.error(message) + return + } - await lacuna.authenticate(config.empire, config.password) await EmpireStore.getStatus() - const session = lacuna.session.get() - - legacyLacuna.init(config, session) // TODO: legacy, pls remove - SessionStore.set(session) + SessionStore.set(lacuna.session.get()) ConfigStore.set(config) App.navigate('/task-selection') } diff --git a/packages/web/src/stores/captcha.js b/packages/web/src/stores/captcha.js index 0e072a5..c632a3e 100644 --- a/packages/web/src/stores/captcha.js +++ b/packages/web/src/stores/captcha.js @@ -1,5 +1,5 @@ import { reactive } from 'vue' -import { lacuna } from '@tlecommunity/super-core' +import { lacuna, call } from '@tlecommunity/super-core' class CaptchaStore { data = reactive({ @@ -9,31 +9,30 @@ class CaptchaStore { }) async fetch() { - const captcha = await lacuna.captcha.fetch() + const captcha = await call(() => lacuna.captcha.fetch()) this.data.guid = captcha.guid this.data.url = captcha.url this.data.solved = false } async refresh() { - const newCaptcha = await lacuna.captcha.fetch() + const newCaptcha = await call(() => lacuna.captcha.fetch()) this.data.guid = newCaptcha.guid this.data.url = newCaptcha.url this.data.solved = false } async solve(answer) { - try { - const result = await lacuna.captcha.solve([this.data.guid, answer]) - if (result) { - this.data.solved = true - return true - } - } catch (err) { - console.error(err) + const { error } = await lacuna.captcha.solve({ guid: this.data.guid, solution: answer }) + + if (error) { + console.error(error.message) this.refresh() return false } + + this.data.solved = true + return true } clear() { diff --git a/test/scripts/lacuna-click-limit-test.js b/test/scripts/lacuna-click-limit-test.js index 90bbcff..7e5fad3 100644 --- a/test/scripts/lacuna-click-limit-test.js +++ b/test/scripts/lacuna-click-limit-test.js @@ -1,30 +1,18 @@ -// This test repeatedly calls `empire.getStatus()`` so as to test retrying +// This test repeatedly calls `empire.getStatus()` so as to test retrying // calls when we hit the 60 clicks per minute limit. import lacuna from './test-instance' -import { log } from '@tlecommunity/super-core' +import { call, log } from '@tlecommunity/super-core' let num = 0 let getStatus = () => { num += 1 log.info(`Getting status (${num})`) - return lacuna.empire.getStatus([]).then(getStatus) + return call(() => lacuna.empire.getStatus()).then(getStatus) } -// Start it off -lacuna - .authenticate() - .then(() => { - // Spam the server real good. - return Promise.all([ - lacuna.empire.getStatus([]).then(getStatus), - lacuna.empire.getStatus([]).then(getStatus), - lacuna.empire.getStatus([]).then(getStatus), - lacuna.empire.getStatus([]).then(getStatus), - lacuna.empire.getStatus([]).then(getStatus), - ]) - }) - .catch((err) => { - log.error(err) - }) +// `test-instance` has already logged in. Spam the server real good. +Promise.all([getStatus(), getStatus(), getStatus(), getStatus(), getStatus()]).catch((err) => { + log.error(err) +}) diff --git a/test/scripts/lacuna-test.js b/test/scripts/lacuna-test.js index 3354a35..ce33174 100644 --- a/test/scripts/lacuna-test.js +++ b/test/scripts/lacuna-test.js @@ -1,43 +1,41 @@ import lacuna from './test-instance' -import { log, util } from '@tlecommunity/super-core' - -import { cache } from '@tlecommunity/super-core' +import { call, findPlanet, findBuilding, colonies, log, util } from '@tlecommunity/super-core' import _ from 'lodash' -lacuna - .authenticate() - .then((sessionId) => { - log.info(`Session ID is ${sessionId}`) - return lacuna.empire.getStatus([]) +// `test-instance` has already logged in by the time this runs. +Promise.resolve() + .then(() => { + log.info(`Session ID is ${lacuna.session.get()}`) + return call(() => lacuna.empire.getStatus()) }) .then((result) => { log.info(`My name is ${result.empire.name}`) log.info(`I have ${result.empire.essentia} Essentia`) - return lacuna.empire.findPlanet('Preyulara') + return findPlanet('Jamie') }) .then((planet) => { log.info(`My home planet is: ${planet.name} (${planet.id})`) - // Kill the session to test how the client handles it. - cache.put('session', '') + // Kill the session to test how `call()` handles it. + lacuna.session.set('') - return lacuna.body.findBuilding(planet.id, 'Planetary Command Center') + return findBuilding(planet.id, 'Planetary Command Center') }) .then((planetaryCommand) => { log.info(`The home planet's PCC is at level ${planetaryCommand.level}`) - return lacuna.buildings.planetaryCommand.viewPlans([planetaryCommand.id]) + return call(() => lacuna.planetaryCommand.viewPlans({ building_id: planetaryCommand.id })) }) .then((result) => { let numOfPlans = result.plans.length log.info(`There are ${numOfPlans} plans on ${result.status.body.name}`) - return lacuna.empire.colonies() + return colonies() }) .then((result) => { - let colonies = _.map(result, 'name').join(', ') - log.info(`These are my colonies: ${colonies}`) + let colonyNames = _.map(result, 'name').join(', ') + log.info(`These are my colonies: ${colonyNames}`) }) .then(() => { log.info('Done') diff --git a/test/scripts/test-instance.js b/test/scripts/test-instance.js index e9a1cde..76440c2 100644 --- a/test/scripts/test-instance.js +++ b/test/scripts/test-instance.js @@ -1,7 +1,9 @@ -import { lacuna } from '@tlecommunity/super-core' +import { lacuna, login } from '@tlecommunity/super-core' -export default lacuna.init({ - empire: 'lacuna.js Test', - password: '123qwe', // Don't be creepy, okay? :) +await login({ + empire: 'nataliethistime', + password: '1234qwer', // Don't be creepy, okay? :) server: 'https://lacuna.allosaurus-chromatic.ts.net', }) + +export default lacuna -- 2.51.2