* @license MIT License * @version GIT: * @link https://git.keithsolomon.net/keith/Spacetraders */ require_once __DIR__ . '/lib/spacetraders-api.php'; require_once __DIR__ . '/lib/spacetraders-storage.php'; /** * Resolve the active system symbol. * * @param array> $ships Ship records. * @param array $agent Agent record. * * @return string */ function resolveCurrentSystemSymbol( array $ships, array $agent ): string { if (! empty( $ships ) ) { $shipSystem = (string) ( $ships[0]['nav']['systemSymbol'] ?? '' ); if ($shipSystem !== '' ) { return $shipSystem; } } $headquarters = (string) ( $agent['headquarters'] ?? '' ); if ($headquarters === '' ) { return ''; } $hqParts = explode( '-', $headquarters ); if (count( $hqParts ) < 2 ) { return ''; } return $hqParts[0] . '-' . $hqParts[1]; } /** * Collect all marketplace waypoints in a system. * * @param SpacetradersApi $client API client. * @param string $systemSymbol System symbol. * * @return array */ function collectMarketplaceWaypoints( SpacetradersApi $client, string $systemSymbol ): array { $waypoints = array(); $page = 1; $total = 0; do { $waypointsResponse = $client->listWaypoints( $systemSymbol, array( 'page' => $page, 'limit' => 20, ) ); $pageData = $waypointsResponse['data'] ?? array(); if (! is_array( $pageData ) || empty( $pageData ) ) { break; } foreach ( $pageData as $waypoint ) { $waypointSymbol = (string) ( $waypoint['symbol'] ?? '' ); if ($waypointSymbol === '' ) { continue; } foreach ( (array) ( $waypoint['traits'] ?? array() ) as $trait ) { if ((string) ( $trait['symbol'] ?? '' ) === 'MARKETPLACE' ) { $waypoints[] = $waypointSymbol; break; } } } $total = (int) ( $waypointsResponse['meta']['total'] ?? count( $waypoints ) ); $page++; } while ( ( $page - 1 ) * 20 < $total ); sort( $waypoints ); return array_values( array_unique( $waypoints ) ); } /** * Find candidate probe ships. * * @param array> $ships Ship records. * * @return array> */ function getProbeShips( array $ships ): array { $probeShips = array(); foreach ( $ships as $ship ) { $frameSymbol = strtoupper( (string) ( $ship['frame']['symbol'] ?? '' ) ); $role = strtoupper( (string) ( $ship['registration']['role'] ?? '' ) ); if (str_contains( $frameSymbol, 'PROBE' ) || $role === 'SATELLITE' ) { $probeShips[] = $ship; } } return $probeShips; } /** * Convert market payload into export rows including prices. * * @param array $marketData Market payload. * * @return array> */ function buildExportsWithPrices( array $marketData ): array { $exports = (array) ( $marketData['exports'] ?? array() ); $tradeGoods = (array) ( $marketData['tradeGoods'] ?? array() ); $tradeGoodsBySymbol = array(); foreach ( $tradeGoods as $tradeGood ) { $symbol = (string) ( $tradeGood['symbol'] ?? '' ); if ($symbol !== '' ) { $tradeGoodsBySymbol[ $symbol ] = $tradeGood; } } $rows = array(); foreach ( $exports as $export ) { $symbol = (string) ( $export['symbol'] ?? '' ); if ($symbol === '' ) { continue; } $tradeGood = (array) ( $tradeGoodsBySymbol[ $symbol ] ?? array() ); $rows[] = array( 'symbol' => $symbol, 'purchasePrice' => (int) ( $tradeGood['purchasePrice'] ?? 0 ), 'sellPrice' => (int) ( $tradeGood['sellPrice'] ?? 0 ), 'supply' => (string) ( $tradeGood['supply'] ?? '' ), ); } usort( $rows, static function ( array $left, array $right ): int { return strcmp( (string) ( $left['symbol'] ?? '' ), (string) ( $right['symbol'] ?? '' ) ); } ); return $rows; } /** * Scan one full probe step: scan current market (if marketplace), then navigate to next target. * * @param SpacetradersApi $client API client. * @param SpacetradersStorage $storage Storage handler. * @param string $systemSymbol System symbol. * @param string $probeShipSymbol Probe ship symbol. * @param array $marketWaypoints Marketplace waypoints. * * @return array */ function runProbeMarketScanStep( SpacetradersApi $client, SpacetradersStorage $storage, string $systemSymbol, string $probeShipSymbol, array $marketWaypoints ): array { $messages = array(); if ($probeShipSymbol === '' ) { return array( 'messages' => array( 'No probe ship selected.' ), 'error' => true, ); } if (empty( $marketWaypoints ) ) { return array( 'messages' => array( 'No marketplace waypoints were found in this system.' ), 'error' => true, ); } $probeResponse = $client->getShip( $probeShipSymbol ); $probeData = (array) ( $probeResponse['data'] ?? array() ); $shipStatus = (string) ( $probeData['nav']['status'] ?? '' ); $currentWaypoint = (string) ( $probeData['nav']['waypointSymbol'] ?? '' ); if ($shipStatus === 'IN_TRANSIT' ) { $arrival = (string) ( $probeData['nav']['route']['arrival'] ?? '' ); $messages[] = $arrival !== '' ? 'Probe is already in transit. Arrival: ' . date( 'Y-m-d H:i:s', strtotime( $arrival ) ) . '.' : 'Probe is already in transit.'; return array( 'messages' => $messages, 'error' => false, ); } $currentIndex = array_search( $currentWaypoint, $marketWaypoints, true ); if ($currentIndex === false ) { $firstWaypoint = (string) ( $marketWaypoints[0] ?? '' ); if ($firstWaypoint === '' ) { return array( 'messages' => array( 'No marketplace waypoints were found in this system.' ), 'error' => true, ); } if ($shipStatus === 'DOCKED' ) { $client->orbitShip( $probeShipSymbol ); } $client->navigateShip( $probeShipSymbol, $firstWaypoint ); $storage->clearAllCache(); $messages[] = 'Probe navigating to ' . $firstWaypoint . '. Run scan again after arrival.'; return array( 'messages' => $messages, 'error' => false, ); } if ($shipStatus !== 'DOCKED' ) { $client->dockShip( $probeShipSymbol ); $shipStatus = 'DOCKED'; } try { $marketResponse = $client->getWaypointMarket( $systemSymbol, $currentWaypoint ); $marketData = (array) ( $marketResponse['data'] ?? array() ); $exports = buildExportsWithPrices( $marketData ); $storage->upsertMarketScanWaypoint( $systemSymbol, $currentWaypoint, $exports, $probeShipSymbol, '' ); $messages[] = 'Scanned ' . $currentWaypoint . ' (' . count( $exports ) . ' exports).'; } catch (SpacetradersApiException $e) { $storage->upsertMarketScanWaypoint( $systemSymbol, $currentWaypoint, array(), $probeShipSymbol, $e->getMessage() ); $messages[] = 'Scan failed at ' . $currentWaypoint . ': ' . $e->getMessage(); } if (count( $marketWaypoints ) <= 1 ) { $messages[] = 'Scan cycle complete for all marketplace waypoints in ' . $systemSymbol . '.'; return array( 'messages' => $messages, 'error' => false, ); } $nextIndex = ( (int) $currentIndex + 1 ) % count( $marketWaypoints ); $nextWaypoint = (string) ( $marketWaypoints[ $nextIndex ] ?? '' ); if ($nextWaypoint !== '' && $nextWaypoint !== $currentWaypoint ) { if ($shipStatus === 'DOCKED' ) { $client->orbitShip( $probeShipSymbol ); } $client->navigateShip( $probeShipSymbol, $nextWaypoint ); $storage->clearAllCache(); $messages[] = 'Probe navigating to ' . $nextWaypoint . '. Run scan again after arrival.'; } else { $messages[] = 'Scan cycle complete for all marketplace waypoints in ' . $systemSymbol . '.'; } return array( 'messages' => $messages, 'error' => false, ); } $config = require __DIR__ . '/lib/project-config.php'; $storage = new SpacetradersStorage( $config['db_path'] ); $token = $storage->getAgentToken(); $statusMessages = array(); $errorMessage = ''; $agent = array(); $ships = array(); $probeShips = array(); $selectedProbeSymbol = ''; $currentSystemSymbol = ''; $marketWaypoints = array(); $scanRows = array(); $itemSearch = trim( (string) ( $_GET['item'] ?? '' ) ); if (! is_string( $token ) || trim( $token ) === '' ) { $envToken = getenv( 'SPACETRADERS_TOKEN' ); if (is_string( $envToken ) && trim( $envToken ) !== '' ) { $token = trim( $envToken ); $storage->setAgentToken( $token ); } } if (! is_string( $token ) || trim( $token ) === '' ) { $tokenError = 'No token found. Set one in config.php or SPACETRADERS_TOKEN.'; } if (! isset( $tokenError ) ) { $client = new SpacetradersApi( trim( $token ), $config['api_base_url'], (int) $config['api_timeout'], $storage, (int) $config['cache_ttl'] ); } try { if (! isset( $tokenError ) ) { $agentResponse = $client->getMyAgent(); $shipsResponse = $client->listMyShips(); $agent = $agentResponse['data'] ?? $agentResponse; $ships = $shipsResponse['data'] ?? $shipsResponse; $probeShips = getProbeShips( $ships ); $currentSystemSymbol = resolveCurrentSystemSymbol( $ships, $agent ); foreach ( $probeShips as $probeShip ) { $probeSymbol = (string) ( $probeShip['symbol'] ?? '' ); if ($probeSymbol !== '' ) { $selectedProbeSymbol = $probeSymbol; break; } } if (isset( $_POST['probe_ship_symbol'] ) ) { $postedProbeSymbol = trim( (string) $_POST['probe_ship_symbol'] ); foreach ( $probeShips as $probeShip ) { if ((string) ( $probeShip['symbol'] ?? '' ) === $postedProbeSymbol ) { $selectedProbeSymbol = $postedProbeSymbol; break; } } } if ($currentSystemSymbol !== '' ) { $marketWaypoints = collectMarketplaceWaypoints( $client, $currentSystemSymbol ); } if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['run_probe_scan'] ) ) { $scanResult = runProbeMarketScanStep( $client, $storage, $currentSystemSymbol, $selectedProbeSymbol, $marketWaypoints ); foreach ( (array) ( $scanResult['messages'] ?? array() ) as $message ) { $statusMessages[] = (string) $message; } if ((bool) ( $scanResult['error'] ?? false ) ) { $errorMessage = ! empty( $statusMessages ) ? $statusMessages[0] : 'Probe scan failed.'; $statusMessages = array(); } else { $shipsResponse = $client->listMyShips(); $ships = $shipsResponse['data'] ?? $shipsResponse; $probeShips = getProbeShips( $ships ); } } if ($currentSystemSymbol !== '' ) { $scanRows = $storage->getMarketScanWaypointsBySystem( $currentSystemSymbol ); } } } catch (SpacetradersApiException $e) { $errorMessage = $e->getMessage(); } $scanRowsByWaypoint = array(); foreach ( $scanRows as $scanRow ) { $scanWaypointSymbol = (string) ( $scanRow['waypoint_symbol'] ?? '' ); if ($scanWaypointSymbol !== '' ) { $scanRowsByWaypoint[ $scanWaypointSymbol ] = $scanRow; } } $marketCards = array(); foreach ( $marketWaypoints as $waypointSymbol ) { $scanRow = (array) ( $scanRowsByWaypoint[ $waypointSymbol ] ?? array() ); $exports = (array) ( $scanRow['exports'] ?? array() ); $matchesFilter = true; if ($itemSearch !== '' ) { $matchesFilter = false; $needle = strtoupper( $itemSearch ); foreach ( $exports as $export ) { $symbol = strtoupper( (string) ( $export['symbol'] ?? '' ) ); if ($symbol !== '' && str_contains( $symbol, $needle ) ) { $matchesFilter = true; break; } } } if (! $matchesFilter ) { continue; } $marketCards[] = array( 'waypointSymbol' => $waypointSymbol, 'exports' => $exports, 'updatedAt' => (int) ( $scanRow['updated_at'] ?? 0 ), 'error' => (string) ( $scanRow['error_message'] ?? '' ), ); } $selectedProbeShip = array(); foreach ( $probeShips as $probeShip ) { if ((string) ( $probeShip['symbol'] ?? '' ) === $selectedProbeSymbol ) { $selectedProbeShip = $probeShip; break; } } $selectedProbeStatus = (string) ( $selectedProbeShip['nav']['status'] ?? '' ); $selectedProbeArrivalIso = (string) ( $selectedProbeShip['nav']['route']['arrival'] ?? '' ); $selectedProbeWaypoint = (string) ( $selectedProbeShip['nav']['waypointSymbol'] ?? '' ); ?> Spacetraders - All Markets

Spacetraders - All Markets

Back to Markets

System:

Marketplace Waypoints:

Probe Ships:

Selected Probe:

Current Waypoint:

Transit Timer:

Clear
No market cards match the current filters. Run a scan and/or broaden your search.

Last Scan: 0 ? htmlspecialchars( date( 'Y-m-d H:i:s', $cardUpdatedAt ) ) : 'Not scanned yet'; ?>

No export price data yet.

  • Buy:

    Sell: