From 56d4d13d4d787f7fd28dc6bd2a7ea9ec81f111ba Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Mon, 6 Jul 2020 21:26:55 +0200 Subject: [PATCH] Fixed line spacing and comment length This adds newlines after blocks and in other logical places to assist with reading the code. Additionally, comments that were too long to remain inline within a 120 character limit have been moved above the line they reference. --- companion.py | 163 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 116 insertions(+), 47 deletions(-) diff --git a/companion.py b/companion.py index 30c01106..74a2760e 100644 --- a/companion.py +++ b/companion.py @@ -22,10 +22,14 @@ import zlib from config import appname, appversion, config from protocol import protocolhandler +from typing import TYPE_CHECKING +if TYPE_CHECKING: + _ = lambda x: x # noqa -holdoff = 60 # be nice -timeout = 10 # requests timeout -auth_timeout = 30 # timeout for initial auth + +holdoff = 60 # be nice +timeout = 10 # requests timeout +auth_timeout = 30 # timeout for initial auth # Currently the "Elite Dangerous Market Connector (EDCD/Athanasius)" one in # Athanasius' Frontier account @@ -47,7 +51,7 @@ category_map = { 'Narcotics' : 'Legal Drugs', 'Slaves' : 'Slavery', 'Waste ' : 'Waste', - 'NonMarketable' : False, # Don't appear in the in-game market so don't report + 'NonMarketable' : False # Don't appear in the in-game market so don't report } commodity_map = {} @@ -105,47 +109,62 @@ ship_map = { # In practice these arrays aren't very sparse so just convert them to lists with any 'gaps' holding None. def listify(thing): if thing is None: - return [] # data is not present + return [] # data is not present + elif isinstance(thing, list): - return list(thing) # array is not sparse + return list(thing) # array is not sparse + elif isinstance(thing, dict): retval = [] - for k,v in thing.items(): + for k, v in thing.items(): idx = int(k) + if idx >= len(retval): retval.extend([None] * (idx - len(retval))) retval.append(v) else: retval[idx] = v + return retval + else: - assert False, thing # we expect an array or a sparse array - return list(thing) # hope for the best + assert False, thing # we expect an array or a sparse array + return list(thing) # hope for the best class ServerError(Exception): def __init__(self, *args): - self.args = args if args else (_('Error: Frontier server is down'),) # Raised when cannot contact the Companion API server + # Raised when cannot contact the Companion API server + self.args = args if args else (_('Error: Frontier server is down'),) + class ServerLagging(Exception): def __init__(self, *args): - self.args = args if args else (_('Error: Frontier server is lagging'),) # Raised when Companion API server is returning old data, e.g. when the servers are too busy + # Raised when Companion API server is returning old data, e.g. when the servers are too busy + self.args = args if args else (_('Error: Frontier server is lagging'),) + class SKUError(Exception): def __init__(self, *args): - self.args = args if args else (_('Error: Frontier server SKU problem'),) # Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU' + # Raised when the Companion API server thinks that the user has not purchased E:D + # i.e. doesn't have the correct 'SKU' + self.args = args if args else (_('Error: Frontier server SKU problem'),) + class CredentialsError(Exception): def __init__(self, *args): self.args = args if args else (_('Error: Invalid Credentials'),) + class CmdrError(Exception): def __init__(self, *args): - self.args = args if args else (_('Error: Wrong Cmdr'),) # Raised when the user has multiple accounts and the username/password setting is not for the account they're currently playing OR the user has reset their Cmdr and the Companion API server is still returning data for the old Cmdr + # Raised when the user has multiple accounts and the username/password setting is not for + # the account they're currently playing OR the user has reset their Cmdr and the Companion API + # server is still returning data for the old Cmdr + self.args = args if args else (_('Error: Wrong Cmdr'),) class Auth(object): - def __init__(self, cmdr): self.cmdr = cmdr self.session = requests.Session() @@ -166,19 +185,23 @@ class Auth(object): 'client_id': CLIENT_ID, 'refresh_token': tokens[idx], } + r = self.session.post(SERVER_AUTH + URL_TOKEN, data=data, timeout=auth_timeout) if r.status_code == requests.codes.ok: data = r.json() tokens[idx] = data.get('refresh_token', '') config.set('fdev_apikeys', tokens) - config.save() # Save settings now for use by command-line app + config.save() # Save settings now for use by command-line app return data.get('access_token') + else: print('Auth\tCan\'t refresh token for %s' % self.cmdr) self.dump(r) + except: print('Auth\tCan\'t refresh token for %s' % self.cmdr) print_exc() + else: print('Auth\tNo token for %s' % self.cmdr) @@ -193,23 +216,26 @@ class Auth(object): def authorize(self, payload): # Handle OAuth authorization code callback. Returns access token if successful, otherwise raises CredentialsError - if not '?' in payload: + if '?' not in payload: print('Auth\tMalformed response "%s"' % payload) - raise CredentialsError() # Not well formed + raise CredentialsError() # Not well formed data = urllib.parse.parse_qs(payload[payload.index('?')+1:]) if not self.state or not data.get('state') or data['state'][0] != self.state: print('Auth\tUnexpected response "%s"' % payload) - raise CredentialsError() # Unexpected reply + raise CredentialsError() # Unexpected reply if not data.get('code'): print('Auth\tNegative response "%s"' % payload) if data.get('error_description'): raise CredentialsError('Error: %s' % data['error_description'][0]) + elif data.get('error'): raise CredentialsError('Error: %s' % data['error'][0]) + elif data.get('message'): raise CredentialsError('Error: %s' % data['message'][0]) + else: raise CredentialsError() @@ -222,6 +248,7 @@ class Auth(object): 'code': data['code'][0], 'redirect_uri': protocolhandler.redirect, } + r = self.session.post(SERVER_AUTH + URL_TOKEN, data=data, timeout=auth_timeout) data = r.json() if r.status_code == requests.codes.ok: @@ -232,8 +259,9 @@ class Auth(object): tokens = tokens + [''] * (len(cmdrs) - len(tokens)) tokens[idx] = data.get('refresh_token', '') config.set('fdev_apikeys', tokens) - config.save() # Save settings now for use by command-line app + config.save() # Save settings now for use by command-line app return data.get('access_token') + except: print('Auth\tCan\'t get token for %s' % self.cmdr) print_exc() @@ -244,10 +272,13 @@ class Auth(object): self.dump(r) if data.get('error_description'): raise CredentialsError('Error: %s' % data['error_description']) + elif data.get('error'): raise CredentialsError('Error: %s' % data['error']) + elif data.get('message'): raise CredentialsError('Error: %s' % data['message']) + else: raise CredentialsError() @@ -260,7 +291,7 @@ class Auth(object): tokens = tokens + [''] * (len(cmdrs) - len(tokens)) tokens[idx] = '' config.set('fdev_apikeys', tokens) - config.save() # Save settings now for use by command-line app + config.save() # Save settings now for use by command-line app def dump(self, r): print('Auth\t' + r.url, r.status_code, r.reason and r.reason or 'None', r.text) @@ -270,7 +301,6 @@ class Auth(object): class Session(object): - STATE_INIT, STATE_AUTH, STATE_OK = list(range(3)) def __init__(self): @@ -278,10 +308,10 @@ class Session(object): self.credentials = None self.session = None self.auth = None - self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query + self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query # yuck suppress InsecurePlatformWarning under Python < 2.7.9 which lacks SNI support - if sys.version_info < (2,7,9): + if sys.version_info < (2, 7, 9): from requests.packages import urllib3 urllib3.disable_warnings() @@ -289,16 +319,20 @@ class Session(object): # Returns True if login succeeded, False if re-authorization initiated. if not CLIENT_ID: raise CredentialsError() + if not cmdr or is_beta is None: # Use existing credentials if not self.credentials: - raise CredentialsError() # Shouldn't happen + raise CredentialsError() # Shouldn't happen + elif self.state == Session.STATE_OK: - return True # already logged in + return True # already logged in + else: credentials = {'cmdr': cmdr, 'beta': is_beta} if self.credentials == credentials and self.state == Session.STATE_OK: - return True # already logged in + return True # already logged in + else: # changed account or retrying login during auth self.close() @@ -307,11 +341,13 @@ class Session(object): self.server = self.credentials['beta'] and SERVER_BETA or SERVER_LIVE self.state = Session.STATE_INIT self.auth = Auth(self.credentials['cmdr']) + access_token = self.auth.refresh() if access_token: self.auth = None self.start(access_token) return True + else: self.state = Session.STATE_AUTH return False @@ -320,14 +356,16 @@ class Session(object): # Callback from protocol handler def auth_callback(self): if self.state != Session.STATE_AUTH: - raise CredentialsError() # Shouldn't be getting a callback + raise CredentialsError() # Shouldn't be getting a callback + try: self.start(self.auth.authorize(protocolhandler.lastpayload)) self.auth = None + except: - self.state = Session.STATE_INIT # Will try to authorize again on next login or query + self.state = Session.STATE_INIT # Will try to authorize again on next login or query self.auth = None - raise # Bad thing happened + raise # Bad thing happened def start(self, access_token): self.session = requests.Session() @@ -339,11 +377,13 @@ class Session(object): if self.state == Session.STATE_INIT: if self.login(): return self.query(endpoint) + elif self.state == Session.STATE_AUTH: raise CredentialsError() try: r = self.session.get(self.server + endpoint, timeout=timeout) + except: if __debug__: print_exc() raise ServerError() @@ -355,26 +395,31 @@ class Session(object): self.retrying = False self.login() raise CredentialsError() + elif 500 <= r.status_code < 600: # Server error. Typically 500 "Internal Server Error" if server is down self.dump(r) raise ServerError() try: - r.raise_for_status() # Typically 403 "Forbidden" on token expiry - data = r.json() # May also fail here if token expired since response is empty + r.raise_for_status() # Typically 403 "Forbidden" on token expiry + data = r.json() # May also fail here if token expired since response is empty + except: print_exc() self.dump(r) self.close() + if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication self.invalidate() self.retrying = False self.login() raise CredentialsError() + elif self.login(): # Maybe our token expired. Re-authorize in any case self.retrying = True return self.query(endpoint) + else: self.retrying = False raise CredentialsError() @@ -382,6 +427,7 @@ class Session(object): self.retrying = False if 'timestamp' not in data: data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date'])) + return data def profile(self): @@ -391,20 +437,24 @@ class Session(object): data = self.query(URL_QUERY) if data['commander'].get('docked'): services = data['lastStarport'].get('services', {}) + if services.get('commodities'): + marketdata = self.query(URL_MARKET) - if (data['lastStarport']['name'] != marketdata['name'] or - int(data['lastStarport']['id']) != int(marketdata['id'])): + if (data['lastStarport']['name'] != marketdata['name'] or int(data['lastStarport']['id']) != int(marketdata['id'])): raise ServerLagging() + else: data['lastStarport'].update(marketdata) + if services.get('outfitting') or services.get('shipyard'): shipdata = self.query(URL_SHIPYARD) - if (data['lastStarport']['name'] != shipdata['name'] or - int(data['lastStarport']['id']) != int(shipdata['id'])): + if (data['lastStarport']['name'] != shipdata['name'] or int(data['lastStarport']['id']) != int(shipdata['id'])): raise ServerLagging() + else: data['lastStarport'].update(shipdata) + return data def close(self): @@ -412,8 +462,10 @@ class Session(object): if self.session: try: self.session.close() + except: if __debug__: print_exc() + self.session = None def invalidate(self): @@ -425,14 +477,15 @@ class Session(object): print('cAPI\t' + r.url, r.status_code, r.reason and r.reason or 'None', r.text) -# Returns a shallow copy of the received data suitable for export to older tools - English commodity names and anomalies fixed up +# Returns a shallow copy of the received data suitable for export to older tools +# English commodity names and anomalies fixed up def fixup(data): - if not commodity_map: # Lazily populate for f in ['commodity.csv', 'rare_commodity.csv']: with open(join(config.respath, f), 'r') as csvfile: reader = csv.DictReader(csvfile) + for row in reader: commodity_map[row['symbol']] = (row['category'], row['name']) @@ -446,24 +499,33 @@ def fixup(data): if not isinstance(commodity.get(thing), numbers.Number): if __debug__: print('Invalid "%s":"%s" (%s) for "%s"' % (thing, commodity.get(thing), type(commodity.get(thing)), commodity.get('name', ''))) break + else: - if not category_map.get(commodity['categoryname'], True): # Check not marketable i.e. Limpets + # Check not marketable i.e. Limpets + if not category_map.get(commodity['categoryname'], True): pass - elif commodity['demandBracket'] == 0 and commodity['stockBracket'] == 0: # Check not normally stocked e.g. Salvage + + # Check not normally stocked e.g. Salvage + elif commodity['demandBracket'] == 0 and commodity['stockBracket'] == 0: pass - elif commodity.get('legality'): # Check not prohibited + elif commodity.get('legality'): # Check not prohibited pass + elif not commodity.get('categoryname'): if __debug__: print('Missing "categoryname" for "%s"' % commodity.get('name', '')) + elif not commodity.get('name'): if __debug__: print('Missing "name" for a commodity in "%s"' % commodity.get('categoryname', '')) + elif not commodity['demandBracket'] in range(4): if __debug__: print('Invalid "demandBracket":"%s" for "%s"' % (commodity['demandBracket'], commodity['name'])) + elif not commodity['stockBracket'] in range(4): if __debug__: print('Invalid "stockBracket":"%s" for "%s"' % (commodity['stockBracket'], commodity['name'])) + else: # Rewrite text fields - new = dict(commodity) # shallow copy + new = dict(commodity) # shallow copy if commodity['name'] in commodity_map: (new['categoryname'], new['name']) = commodity_map[commodity['name']] elif commodity['categoryname'] in category_map: @@ -488,22 +550,27 @@ def fixup(data): # Return a subset of the received data describing the current ship def ship(data): - def filter_ship(d): filtered = {} for k, v in d.items(): if v == []: - pass # just skip empty fields for brevity + pass # just skip empty fields for brevity + elif k in ['alive', 'cargo', 'cockpitBreached', 'health', 'oxygenRemaining', 'rebuilds', 'starsystem', 'station']: - pass # noisy + pass # noisy + elif k in ['locDescription', 'locName'] or k.endswith('LocDescription') or k.endswith('LocName'): - pass # also noisy, and redundant + pass # also noisy, and redundant + elif k in ['dir', 'LessIsGood']: - pass # dir is not ASCII - remove to simplify handling + pass # dir is not ASCII - remove to simplify handling + elif hasattr(v, 'items'): filtered[k] = filter_ship(v) + else: filtered[k] = v + return filtered # subset of "ship" that's not noisy @@ -515,11 +582,13 @@ def ship_file_name(ship_name, ship_type): name = str(ship_name or ship_map.get(ship_type.lower(), ship_type)).strip() if name.endswith('.'): name = name[:-1] + if name.lower() in ['con', 'prn', 'aux', 'nul', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9']: name = name + '_' - return name.translate({ ord(x): u'_' for x in ['\0', '<', '>', ':', '"', '/', '\\', '|', '?', '*'] }) + + return name.translate({ord(x): u'_' for x in ['\0', '<', '>', ':', '"', '/', '\\', '|', '?', '*']}) # singleton