2023-06-10 17:39:32 +00:00
|
|
|
import requests
|
2023-06-11 18:51:03 +00:00
|
|
|
from nullptr.models.system import System
|
|
|
|
from nullptr.models.waypoint import Waypoint
|
|
|
|
from nullptr.models.marketplace import Marketplace
|
2023-06-12 08:51:01 +00:00
|
|
|
from nullptr.models.jumpgate import Jumpgate
|
2023-06-15 19:10:33 +00:00
|
|
|
from nullptr.models.ship import Ship
|
2024-02-03 20:20:04 +00:00
|
|
|
from nullptr.models.shipyard import Shipyard
|
2023-06-11 18:51:03 +00:00
|
|
|
from .util import *
|
2024-01-25 18:57:49 +00:00
|
|
|
from time import sleep, time
|
2024-02-09 14:52:30 +00:00
|
|
|
|
|
|
|
class ApiError(AppError):
|
2023-06-10 17:39:32 +00:00
|
|
|
def __init__(self, msg, code):
|
|
|
|
super().__init__(msg)
|
|
|
|
self.code = code
|
2023-06-12 10:23:08 +00:00
|
|
|
|
|
|
|
class ApiLimitError(Exception):
|
|
|
|
pass
|
|
|
|
|
2023-06-10 17:39:32 +00:00
|
|
|
class Api:
|
2024-02-09 14:52:30 +00:00
|
|
|
def __init__(self, c, agent):
|
2023-06-10 17:39:32 +00:00
|
|
|
self.agent = agent
|
2024-02-09 14:52:30 +00:00
|
|
|
self.store = c.store
|
2023-06-15 19:10:33 +00:00
|
|
|
self.requests_sent = 0
|
2023-07-16 16:48:45 +00:00
|
|
|
self.last_meta = None
|
2023-06-15 19:10:33 +00:00
|
|
|
self.last_result = None
|
2023-06-10 17:39:32 +00:00
|
|
|
self.root = 'https://api.spacetraders.io/v2/'
|
|
|
|
|
|
|
|
def token(self):
|
|
|
|
if self.agent.token is None:
|
|
|
|
raise ApiError('no token. Please register', 1337)
|
|
|
|
return self.agent.token
|
2023-06-12 10:23:08 +00:00
|
|
|
|
2023-06-10 18:49:50 +00:00
|
|
|
def request(self, method, path, data=None, need_token=True, params={}):
|
2023-06-12 10:23:08 +00:00
|
|
|
try:
|
2024-01-25 18:57:49 +00:00
|
|
|
start = time()
|
|
|
|
result = self.request_once(method, path, data, need_token, params)
|
|
|
|
dur = time() - start
|
|
|
|
# print(f'api {dur:.03}')
|
|
|
|
return result
|
2023-06-25 18:21:49 +00:00
|
|
|
except (ApiLimitError, requests.exceptions.Timeout):
|
2024-01-02 05:35:26 +00:00
|
|
|
# print('oops, hit the limit. take a break')
|
2023-06-12 10:23:08 +00:00
|
|
|
sleep(10)
|
|
|
|
return self.request_once(method, path, data, need_token, params)
|
|
|
|
|
|
|
|
def request_once(self, method, path, data=None, need_token=True, params={}):
|
2023-06-10 17:39:32 +00:00
|
|
|
headers = {}
|
|
|
|
if need_token:
|
|
|
|
headers['Authorization'] = 'Bearer ' + self.token()
|
2023-06-15 19:10:33 +00:00
|
|
|
self.requests_sent += 1
|
2023-06-10 17:39:32 +00:00
|
|
|
if method == 'get':
|
|
|
|
params['limit'] = 20
|
|
|
|
r = requests.request(method, self.root+path, json=data, headers=headers, params=params)
|
2023-06-12 10:23:08 +00:00
|
|
|
if r.status_code == 429:
|
|
|
|
raise ApiLimitError()
|
2023-06-10 17:39:32 +00:00
|
|
|
result = r.json()
|
|
|
|
self.last_result = result
|
|
|
|
if result is None:
|
|
|
|
raise ApiError('http call failed', r.status_code)
|
2023-06-11 18:51:03 +00:00
|
|
|
if 'meta' in result:
|
|
|
|
self.last_meta = result['meta']
|
|
|
|
if 'data' not in result:
|
2023-06-10 17:39:32 +00:00
|
|
|
error_code = sg(result, 'error.code', -1)
|
|
|
|
self.last_error = error_code
|
|
|
|
error_message = sg(result, 'error.message')
|
|
|
|
raise ApiError(error_message, error_code)
|
|
|
|
else:
|
|
|
|
self.last_error = 0
|
|
|
|
return result['data']
|
2023-12-28 18:49:00 +00:00
|
|
|
|
|
|
|
######## Account #########
|
2023-06-10 17:39:32 +00:00
|
|
|
def register(self, faction):
|
|
|
|
callsign = self.agent.symbol
|
|
|
|
data = {
|
|
|
|
'symbol': callsign,
|
|
|
|
'faction': faction
|
|
|
|
}
|
|
|
|
result = self.request('post', 'register', data, need_token=False)
|
|
|
|
token = mg(result, 'token')
|
2023-07-10 17:25:01 +00:00
|
|
|
self.agent.update(mg(result, 'agent'))
|
2023-06-10 17:39:32 +00:00
|
|
|
self.agent.token = token
|
|
|
|
|
2024-01-21 19:21:38 +00:00
|
|
|
def status(self):
|
|
|
|
try:
|
|
|
|
self.request('get', '')
|
|
|
|
except ApiError:
|
|
|
|
pass
|
|
|
|
return self.last_result
|
|
|
|
|
2023-06-10 17:39:32 +00:00
|
|
|
def info(self):
|
|
|
|
data = self.request('get', 'my/agent')
|
|
|
|
self.agent.update(data)
|
|
|
|
return self.agent
|
2023-06-10 18:49:50 +00:00
|
|
|
|
2023-12-28 18:49:00 +00:00
|
|
|
######## Atlas #########
|
2023-06-10 18:49:50 +00:00
|
|
|
def list_systems(self, page=1):
|
|
|
|
data = self.request('get', 'systems', params={'page': page})
|
2023-06-11 18:51:03 +00:00
|
|
|
#pprint(self.last_meta)
|
2023-07-10 19:10:49 +00:00
|
|
|
systems = self.store.update_list(System, data)
|
|
|
|
for s in data:
|
|
|
|
self.store.update_list(Waypoint, mg(s, 'waypoints'))
|
|
|
|
return systems
|
2023-06-11 18:51:03 +00:00
|
|
|
|
|
|
|
def list_waypoints(self, system):
|
|
|
|
data = self.request('get', f'systems/{system}/waypoints/')
|
2023-12-28 18:49:00 +00:00
|
|
|
tp = total_pages(self.last_meta)
|
2024-01-02 05:35:26 +00:00
|
|
|
for p in range(tp):
|
|
|
|
data += self.request('get', f'systems/{system}/waypoints/', params={'page': p+1})
|
2023-06-12 20:36:58 +00:00
|
|
|
# pprint(data)
|
2023-06-11 18:51:03 +00:00
|
|
|
return self.store.update_list(Waypoint, data)
|
|
|
|
|
|
|
|
def marketplace(self, waypoint):
|
2023-07-10 17:25:01 +00:00
|
|
|
system = waypoint.system
|
2023-06-11 18:51:03 +00:00
|
|
|
data = self.request('get', f'systems/{system}/waypoints/{waypoint}/market')
|
2023-06-16 11:03:19 +00:00
|
|
|
return self.store.update(Marketplace, data)
|
2023-06-12 08:51:01 +00:00
|
|
|
|
|
|
|
def jumps(self, waypoint):
|
2023-07-10 17:25:01 +00:00
|
|
|
data = self.request('get', f'systems/{waypoint.system}/waypoints/{waypoint}/jump-gate')
|
2023-06-12 08:51:01 +00:00
|
|
|
symbol = str(waypoint)
|
2023-06-17 12:59:52 +00:00
|
|
|
return self.store.update(Jumpgate, data, symbol)
|
2023-06-12 08:51:01 +00:00
|
|
|
|
2023-12-28 18:49:00 +00:00
|
|
|
def shipyard(self, wp):
|
2024-02-03 20:20:04 +00:00
|
|
|
data = self.request('get', f'systems/{wp.system}/waypoints/{wp}/shipyard')
|
|
|
|
symbol = str(wp)
|
|
|
|
|
|
|
|
return self.store.update(Shipyard, data, symbol)
|
2023-12-28 18:49:00 +00:00
|
|
|
|
|
|
|
######## Fleet #########
|
2023-06-15 19:10:33 +00:00
|
|
|
def list_ships(self):
|
|
|
|
data = self.request('get', 'my/ships')
|
2024-01-27 14:05:33 +00:00
|
|
|
tp = total_pages(self.last_meta)
|
|
|
|
for p in range(1, tp):
|
|
|
|
data += self.request('get', 'my/ships', params={'page': p+1})
|
2023-06-15 19:10:33 +00:00
|
|
|
return self.store.update_list(Ship, data)
|
|
|
|
|
2023-12-28 18:49:00 +00:00
|
|
|
def refuel(self, ship, from_cargo=False):
|
|
|
|
fuel_need = ship.fuel_capacity - ship.fuel_current
|
|
|
|
fuel_avail = ship.get_cargo('FUEL') * 100
|
|
|
|
units = fuel_need
|
|
|
|
if from_cargo:
|
|
|
|
units = min(units, fuel_avail)
|
|
|
|
data = {'fromCargo': from_cargo, 'units': units }
|
|
|
|
data = self.request('post', f'my/ships/{ship}/refuel', data)
|
2024-01-13 10:27:32 +00:00
|
|
|
self.log_transaction(data)
|
2023-12-28 18:49:00 +00:00
|
|
|
if from_cargo:
|
|
|
|
boxes = ceil(float(units) / 100)
|
|
|
|
ship.take_cargo('FUEL', boxes)
|
|
|
|
if 'fuel' in data:
|
|
|
|
ship.update(data)
|
|
|
|
if 'agent' in data:
|
|
|
|
self.agent.update(data['agent'])
|
|
|
|
return data
|
|
|
|
|
|
|
|
######## Contract #########
|
2023-06-16 11:03:19 +00:00
|
|
|
def list_contracts(self):
|
|
|
|
data = self.request('get', 'my/contracts')
|
|
|
|
return self.store.update_list('Contract', data)
|
|
|
|
|
|
|
|
def negotiate(self, ship):
|
|
|
|
data = self.request('post', f'my/ships/{ship}/negotiate/contract')
|
|
|
|
if data is not None and 'contract' in data:
|
|
|
|
contract = self.store.update('Contract', data['contract'])
|
|
|
|
return contract
|
2023-12-28 18:49:00 +00:00
|
|
|
|
|
|
|
def accept_contract(self, contract):
|
|
|
|
data = self.request('post', f'my/contracts/{contract.symbol.lower()}/accept')
|
|
|
|
if 'contract' in data:
|
|
|
|
contract.update(data['contract'])
|
|
|
|
if 'agent' in data:
|
|
|
|
self.agent.update(data['agent'])
|
|
|
|
return contract
|
|
|
|
|
2023-06-17 12:59:52 +00:00
|
|
|
def deliver(self, ship, typ, contract):
|
|
|
|
units = ship.get_cargo(typ)
|
|
|
|
if units == 0:
|
|
|
|
print("Resource not in cargo")
|
|
|
|
return {}
|
|
|
|
data = {
|
|
|
|
'shipSymbol': str(ship),
|
|
|
|
'tradeSymbol': typ.upper(),
|
|
|
|
'units': units
|
|
|
|
}
|
2023-06-18 18:47:34 +00:00
|
|
|
data = self.request('post', f'my/contracts/{contract.symbol.lower()}/deliver', data)
|
2023-06-17 12:59:52 +00:00
|
|
|
if 'cargo' in data:
|
|
|
|
ship.update(data)
|
|
|
|
if 'contract' in data:
|
|
|
|
contract.update(data['contract'])
|
|
|
|
return contract
|
|
|
|
|
|
|
|
def fulfill(self, contract):
|
2023-06-18 18:47:34 +00:00
|
|
|
data = self.request('post', f'my/contracts/{contract.symbol.lower()}/fulfill')
|
2023-06-17 12:59:52 +00:00
|
|
|
if 'contract' in data:
|
|
|
|
contract.update(data['contract'])
|
|
|
|
if 'agent' in data:
|
|
|
|
self.agent.update(data['agent'])
|
|
|
|
return contract
|
2023-12-28 18:49:00 +00:00
|
|
|
|
|
|
|
######## Nav #########
|
2023-06-15 20:37:33 +00:00
|
|
|
def navigate(self, ship, wp):
|
|
|
|
data = {'waypointSymbol': str(wp)}
|
|
|
|
response = self.request('post', f'my/ships/{ship}/navigate', data)
|
2024-01-09 19:07:27 +00:00
|
|
|
ship.log(f'nav to {wp}')
|
2023-06-15 20:37:33 +00:00
|
|
|
ship.update(response)
|
2023-06-12 08:51:01 +00:00
|
|
|
|
2023-06-15 20:37:33 +00:00
|
|
|
def dock(self, ship):
|
|
|
|
data = self.request('post', f'my/ships/{ship}/dock')
|
|
|
|
ship.update(data)
|
|
|
|
return data
|
|
|
|
|
|
|
|
def orbit(self, ship):
|
|
|
|
data = self.request('post', f'my/ships/{ship}/orbit')
|
|
|
|
ship.update(data)
|
|
|
|
return data
|
2023-12-28 18:49:00 +00:00
|
|
|
|
|
|
|
def flight_mode(self, ship, mode):
|
|
|
|
data = {'flightMode': mode}
|
|
|
|
data = self.request('patch', f'my/ships/{ship}/nav', data)
|
2024-01-09 19:39:11 +00:00
|
|
|
ship.update({'nav':data})
|
2023-12-28 18:49:00 +00:00
|
|
|
return data
|
2023-06-15 20:37:33 +00:00
|
|
|
|
2023-12-28 18:49:00 +00:00
|
|
|
def jump(self, ship, waypoint):
|
|
|
|
if type(waypoint) == Waypoint:
|
|
|
|
waypoint = waypoint.symbol
|
|
|
|
data = {
|
|
|
|
"waypointSymbol": waypoint
|
|
|
|
}
|
|
|
|
data = self.request('post', f'my/ships/{ship}/jump', data)
|
|
|
|
if 'nav' in data:
|
2023-06-16 12:41:11 +00:00
|
|
|
ship.update(data)
|
2023-12-28 18:49:00 +00:00
|
|
|
return ship
|
|
|
|
|
|
|
|
######## Extraction #########
|
|
|
|
def siphon(self, ship):
|
|
|
|
data = self.request('post', f'my/ships/{ship}/siphon')
|
|
|
|
ship.update(data)
|
2024-01-20 19:33:50 +00:00
|
|
|
amt = mg(data, 'siphon.yield.units')
|
|
|
|
rec = mg(data, 'siphon.yield.symbol')
|
|
|
|
ship.log(f"siphoned {amt} {rec}")
|
|
|
|
ship.location.extracted += amt
|
2023-12-28 18:49:00 +00:00
|
|
|
return data['siphon']
|
|
|
|
|
|
|
|
def extract(self, ship, survey=None):
|
|
|
|
data = {}
|
|
|
|
url = f'my/ships/{ship}/extract'
|
|
|
|
if survey is not None:
|
2024-01-25 18:57:49 +00:00
|
|
|
data= survey.api_dict()
|
2023-12-28 18:49:00 +00:00
|
|
|
url += '/survey'
|
|
|
|
try:
|
|
|
|
data = self.request('post', url, data=data)
|
|
|
|
except ApiError as e:
|
|
|
|
if e.code in [ 4221, 4224]:
|
|
|
|
survey.exhausted = True
|
|
|
|
else:
|
|
|
|
raise e
|
|
|
|
ship.update(data)
|
2024-01-27 14:05:33 +00:00
|
|
|
amt = sg(data, 'extraction.yield.units', 0)
|
|
|
|
rec = sg(data, 'extraction.yield.symbol', 'nothing')
|
2024-01-20 19:33:50 +00:00
|
|
|
ship.log(f"extracted {amt} {rec}")
|
|
|
|
ship.location.extracted += amt
|
2023-06-15 20:37:33 +00:00
|
|
|
return data
|
2023-06-16 12:41:11 +00:00
|
|
|
|
2023-12-28 18:49:00 +00:00
|
|
|
def survey(self, ship):
|
|
|
|
data = self.request('post', f'my/ships/{ship}/survey')
|
|
|
|
ship.update(data)
|
|
|
|
result = self.store.update_list('Survey', mg(data, 'surveys'))
|
|
|
|
return result
|
2023-06-16 21:05:47 +00:00
|
|
|
|
2024-01-13 10:27:32 +00:00
|
|
|
|
2023-12-28 18:49:00 +00:00
|
|
|
######## Commerce #########
|
2024-01-20 19:33:50 +00:00
|
|
|
def transaction_cost(self, data):
|
|
|
|
if not 'transaction' in data: return 0
|
|
|
|
act = mg(data,'transaction.type')
|
|
|
|
minus = -1 if act == 'PURCHASE' else 1
|
|
|
|
units = mg(data, 'transaction.units')
|
|
|
|
ppu = mg(data, 'transaction.pricePerUnit')
|
|
|
|
return ppu * units * minus
|
|
|
|
|
2024-01-13 10:27:32 +00:00
|
|
|
def log_transaction(self, data):
|
|
|
|
if not 'transaction' in data: return
|
|
|
|
typ = mg(data, 'transaction.tradeSymbol')
|
|
|
|
ppu = mg(data, 'transaction.pricePerUnit')
|
|
|
|
shipsym = mg(data, 'transaction.shipSymbol')
|
|
|
|
ship = self.store.get('Ship', shipsym)
|
|
|
|
units = mg(data, 'transaction.units')
|
|
|
|
act = mg(data,'transaction.type')
|
|
|
|
ship.log(f'{act} {units} of {typ} for {ppu} at {ship.location}')
|
|
|
|
|
2024-01-06 06:17:53 +00:00
|
|
|
def sell(self, ship, typ,units=None):
|
|
|
|
if units is None:
|
|
|
|
units = ship.get_cargo(typ)
|
2023-06-16 21:05:47 +00:00
|
|
|
data = {
|
|
|
|
'symbol': typ,
|
|
|
|
'units': units
|
|
|
|
}
|
|
|
|
data = self.request('post', f'my/ships/{ship}/sell', data)
|
2024-01-13 10:27:32 +00:00
|
|
|
self.log_transaction(data)
|
2023-06-16 21:05:47 +00:00
|
|
|
if 'cargo' in data:
|
|
|
|
ship.update(data)
|
|
|
|
if 'agent' in data:
|
|
|
|
self.agent.update(data['agent'])
|
|
|
|
return data
|
|
|
|
|
|
|
|
def buy(self, ship, typ, amt):
|
|
|
|
data = {
|
|
|
|
'symbol': typ,
|
|
|
|
'units': amt
|
|
|
|
}
|
|
|
|
data = self.request('post', f'my/ships/{ship}/purchase', data)
|
2024-01-13 10:27:32 +00:00
|
|
|
self.log_transaction(data)
|
2023-06-16 21:05:47 +00:00
|
|
|
if 'cargo' in data:
|
|
|
|
ship.update(data)
|
|
|
|
if 'agent' in data:
|
|
|
|
self.agent.update(data['agent'])
|
|
|
|
return data
|
|
|
|
|
|
|
|
def jettison(self, ship, typ):
|
|
|
|
units = ship.get_cargo(typ)
|
|
|
|
if units == 0:
|
|
|
|
print('cargo not found')
|
|
|
|
return
|
|
|
|
data = {
|
|
|
|
'symbol': typ,
|
|
|
|
'units': units
|
|
|
|
}
|
|
|
|
data = self.request('post', f'my/ships/{ship.symbol}/jettison', data)
|
2024-01-09 19:07:27 +00:00
|
|
|
ship.log(f'drop {units} of {typ}')
|
2023-06-16 21:05:47 +00:00
|
|
|
if 'cargo' in data:
|
|
|
|
ship.update(data)
|
|
|
|
if 'agent' in data:
|
|
|
|
self.agent.update(data['agent'])
|
|
|
|
return data
|
2023-06-17 12:59:52 +00:00
|
|
|
|
2024-01-20 19:33:50 +00:00
|
|
|
def transfer(self, sship, dship, typ, amt):
|
|
|
|
data = {
|
|
|
|
'tradeSymbol': typ,
|
|
|
|
'units': amt,
|
|
|
|
'shipSymbol': dship.symbol
|
|
|
|
}
|
|
|
|
data = self.request('post', f'my/ships/{sship.symbol}/transfer', data)
|
2024-01-24 18:03:57 +00:00
|
|
|
sship.log(f'tra {amt} {typ} to {dship}')
|
|
|
|
dship.log(f'rec {amt} {typ} from {sship}', 10)
|
2024-01-20 19:33:50 +00:00
|
|
|
if 'cargo' in data:
|
|
|
|
sship.update(data)
|
|
|
|
dship.put_cargo(typ, amt)
|
|
|
|
|
2023-06-17 12:59:52 +00:00
|
|
|
def purchase(self, typ, wp):
|
|
|
|
data = {
|
|
|
|
'shipType': typ,
|
|
|
|
'waypointSymbol': str(wp)
|
|
|
|
}
|
|
|
|
data = self.request('post', 'my/ships', data)
|
|
|
|
if 'agent' in data:
|
|
|
|
self.agent.update(data['agent'])
|
|
|
|
if 'ship' in data:
|
|
|
|
ship = self.store.update('Ship', data['ship'])
|
2023-12-28 18:49:00 +00:00
|
|
|
return ship
|