diff --git a/plugins/inara.py b/plugins/inara.py index 57d8d58e..5a253bb5 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -4,7 +4,9 @@ from collections import OrderedDict import json -from typing import Any, Union +from typing import Any, AnyStr, Dict, List, Mapping, Optional, OrderedDict as OrderedDictT, \ + Sequence, TYPE_CHECKING, Union + import requests import sys import time @@ -21,38 +23,41 @@ from config import appname, applongname, appversion, config import plug logger = logging.getLogger(appname) +if TYPE_CHECKING: + def _(x): + return x + _TIMEOUT = 20 -FAKE = ['CQC', 'Training', 'Destination'] # Fake systems that shouldn't be sent to Inara +FAKE = ('CQC', 'Training', 'Destination') # Fake systems that shouldn't be sent to Inara CREDIT_RATIO = 1.05 # Update credits if they change by 5% over the course of a session -this: Any = sys.modules[__name__] # For holding module globals +this: Any = sys.modules[__name__] # For holding module globals this.session = requests.Session() -this.queue = Queue() # Items to be sent to Inara by worker thread -this.lastlocation = None # eventData from the last Commander's Flight Log event -this.lastship = None # eventData from the last addCommanderShip or setCommanderShip event +this.queue = Queue() # Items to be sent to Inara by worker thread +this.lastlocation = None # eventData from the last Commander's Flight Log event +this.lastship = None # eventData from the last addCommanderShip or setCommanderShip event # Cached Cmdr state -this.events = [] # Unsent events -this.cmdr = None -this.FID = None # Frontier ID -this.multicrew = False # don't send captain's ship info to Inara while on a crew -this.newuser = False # just entered API Key - send state immediately -this.newsession = True # starting a new session - wait for Cargo event -this.undocked = False # just undocked -this.suppress_docked = False # Skip initial Docked event if started docked -this.cargo = None -this.materials = None -this.lastcredits = 0 # Send credit update soon after Startup / new game -this.storedmodules = None -this.loadout = None -this.fleet = None -this.shipswap = False # just swapped ship +this.events: List[OrderedDictT[str, Any]] = [] # Unsent events +this.cmdr: Optional[str] = None +this.FID: Optional[str] = None # Frontier ID +this.multicrew: bool = False # don't send captain's ship info to Inara while on a crew +this.newuser: bool = False # just entered API Key - send state immediately +this.newsession: bool = True # starting a new session - wait for Cargo event +this.undocked: bool = False # just undocked +this.suppress_docked = False # Skip initial Docked event if started docked +this.cargo: Optional[OrderedDictT[str, Any]] = None +this.materials: Optional[OrderedDictT[str, Any]] = None +this.lastcredits: int = 0 # Send credit update soon after Startup / new game +this.storedmodules: Optional[OrderedDictT[str, Any]] = None +this.loadout: Optional[OrderedDictT[str, Any]] = None +this.fleet: Optional[List[OrderedDictT[str, Any]]] = None +this.shipswap: bool = False # just swapped ship # last time we updated, if unset in config this is 0, which means an instant update LAST_UPDATE_CONF_KEY = 'inara_last_update' -# this.last_update_time = config.getint(LAST_UPDATE_CONF_KEY) FLOOD_LIMIT_SECONDS = 30 # minimum time between sending events this.timer_run = True @@ -67,7 +72,8 @@ this.station = None this.station_marketid = None STATION_UNDOCKED: str = '×' # "Station" name to display when not docked = U+00D7 -def system_url(system_name): + +def system_url(system_name: str): if this.system_address: return requests.utils.requote_uri(f'https://inara.cz/galaxy-starsystem/?search={this.system_address}') @@ -76,17 +82,21 @@ def system_url(system_name): return this.system -def station_url(system_name, station_name): + +def station_url(system_name: Optional[str], station_name: Optional[str]): if system_name: if station_name: - return requests.utils.requote_uri(f'https://inara.cz/galaxy-station/?search={system_name}%20[{station_name}]') + return requests.utils.requote_uri( + f'https://inara.cz/galaxy-station/?search={system_name}%20[{station_name}]' + ) + return system_url(system_name) - return this.station or this.system + return this.station if this.station else this.system def plugin_start3(plugin_dir): - this.thread = Thread(target = worker, name = 'Inara worker') + this.thread = Thread(target=worker, name='Inara worker') this.thread.daemon = True this.thread.start() @@ -95,12 +105,14 @@ def plugin_start3(plugin_dir): this.timer_thread.start() return 'Inara' -def plugin_app(parent): - this.system_link = parent.children['system'] # system label in main window - this.station_link = parent.children['station'] # station label in main window + +def plugin_app(parent: tk.Tk): + this.system_link = parent.children['system'] # system label in main window + this.station_link = parent.children['station'] # station label in main window this.system_link.bind_all('<>', update_location) this.system_link.bind_all('<>', update_ship) + def plugin_stop(): # Send any unsent events call() @@ -112,25 +124,40 @@ def plugin_stop(): this.timer_run = False -def plugin_prefs(parent, cmdr, is_beta): +def plugin_prefs(parent: tk.Tk, cmdr: str, is_beta: bool): PADX = 10 - BUTTONX = 12 # indent Checkbuttons and Radiobuttons + BUTTONX = 12 # indent Checkbuttons and Radiobuttons PADY = 2 # close spacing frame = nb.Frame(parent) frame.columnconfigure(1, weight=1) - HyperlinkLabel(frame, text='Inara', background=nb.Label().cget('background'), url='https://inara.cz/', underline=True).grid(columnspan=2, padx=PADX, sticky=tk.W) # Don't translate - this.log = tk.IntVar(value = config.getint('inara_out') and 1) - this.log_button = nb.Checkbutton(frame, text=_('Send flight log and Cmdr status to Inara'), variable=this.log, command=prefsvarchanged) - this.log_button.grid(columnspan=2, padx=BUTTONX, pady=(5,0), sticky=tk.W) + HyperlinkLabel( + frame, text='Inara', background=nb.Label().cget('background'), url='https://inara.cz/', underline=True + ).grid(columnspan=2, padx=PADX, sticky=tk.W) # Don't translate + + this.log = tk.IntVar(value=config.getint('inara_out') and 1) + this.log_button = nb.Checkbutton( + frame, text=_('Send flight log and Cmdr status to Inara'), variable=this.log, command=prefsvarchanged + ) + + this.log_button.grid(columnspan=2, padx=BUTTONX, pady=(5, 0), sticky=tk.W) + + nb.Label(frame).grid(sticky=tk.W) # big spacer + + # Section heading in settings + this.label = HyperlinkLabel( + frame, + text=_('Inara credentials'), + background=nb.Label().cget('background'), + url='https://inara.cz/settings-api', + underline=True + ) - nb.Label(frame).grid(sticky=tk.W) # big spacer - this.label = HyperlinkLabel(frame, text=_('Inara credentials'), background=nb.Label().cget('background'), url='https://inara.cz/settings-api', underline=True) # Section heading in settings this.label.grid(columnspan=2, padx=PADX, sticky=tk.W) - this.apikey_label = nb.Label(frame, text=_('API Key')) # EDSM setting + this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting this.apikey_label.grid(row=12, padx=PADX, sticky=tk.W) this.apikey = nb.Entry(frame) this.apikey.grid(row=12, column=1, padx=PADX, pady=PADY, sticky=tk.EW) @@ -139,26 +166,43 @@ def plugin_prefs(parent, cmdr, is_beta): return frame -def prefs_cmdr_changed(cmdr, is_beta): - this.log_button['state'] = cmdr and not is_beta and tk.NORMAL or tk.DISABLED + +def prefs_cmdr_changed(cmdr: str, is_beta: bool): + this.log_button['state'] = tk.NORMAL if cmdr and not is_beta else tk.DISABLED this.apikey['state'] = tk.NORMAL this.apikey.delete(0, tk.END) if cmdr: cred = credentials(cmdr) if cred: this.apikey.insert(0, cred) - this.label['state'] = this.apikey_label['state'] = this.apikey['state'] = cmdr and not is_beta and this.log.get() and tk.NORMAL or tk.DISABLED + + state = tk.DISABLED + if cmdr and not is_beta and this.log.get(): + state = tk.NORMAL + + this.label['state'] = state + this.apikey_label['state'] = state + this.apikey['state'] = state + def prefsvarchanged(): - this.label['state'] = this.apikey_label['state'] = this.apikey['state'] = this.log.get() and this.log_button['state'] or tk.DISABLED + state = tk.DISABLED + if this.log.get(): + state = this.log_button['state'] -def prefs_changed(cmdr, is_beta): + this.label['state'] = state + this.apikey_label['state'] = state + this.apikey['state'] = state + + +def prefs_changed(cmdr: str, is_beta: bool): changed = config.getint('inara_out') != this.log.get() config.set('inara_out', this.log.get()) # Override standard URL functions if config.get('system_provider') == 'Inara': this.system_link['url'] = system_url(this.system) + if config.get('station_provider') == 'Inara': this.station_link['url'] = station_url(this.system, this.station) @@ -172,48 +216,59 @@ def prefs_changed(cmdr, is_beta): apikeys.extend([''] * (1 + idx - len(apikeys))) changed |= (apikeys[idx] != this.apikey.get().strip()) apikeys[idx] = this.apikey.get().strip() + else: config.set('inara_cmdrs', cmdrs + [cmdr]) changed = True apikeys.append(this.apikey.get().strip()) + config.set('inara_apikeys', apikeys) if this.log.get() and changed: - this.newuser = True # Send basic info at next Journal event - add_event('getCommanderProfile', time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), { 'searchName': cmdr }) + this.newuser = True # Send basic info at next Journal event + add_event('getCommanderProfile', time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), {'searchName': cmdr}) call() -def credentials(cmdr): - # Credentials for cmdr + +def credentials(cmdr: str) -> Optional[str]: + """ + credentials fetches the credentials for the given commander + + :param cmdr: Commander name to search for credentials + :return: Credentials for the given commander or None + """ if not cmdr: return None cmdrs = config.get('inara_cmdrs') or [] if cmdr in cmdrs and config.get('inara_apikeys'): return config.get('inara_apikeys')[cmdrs.index(cmdr)] + else: return None -def journal_entry(cmdr, is_beta, system, station, entry, state): - +def journal_entry(cmdr: str, is_beta: bool, system: str, station: str, entry: Dict[str, Any], state: Dict[str, Any]): # Send any unsent events when switching accounts if cmdr and cmdr != this.cmdr: call(force=True) + event_name = entry['event'] this.cmdr = cmdr this.FID = state['FID'] this.multicrew = bool(state['Role']) - if entry['event'] == 'LoadGame' or this.newuser: + if event_name == 'LoadGame' or this.newuser: # clear cached state - if entry['event'] == 'LoadGame': + if event_name == 'LoadGame': # User setup Inara API while at the loading screen - proceed as for new session this.newuser = False this.newsession = True + else: this.newuser = True this.newsession = False + this.undocked = False this.suppress_docked = False this.cargo = None @@ -227,15 +282,17 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): this.system_address = None this.station = None this.station_marketid = None - elif entry['event'] in ['Resurrect', 'ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy']: + + elif event_name in ('Resurrect', 'ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy'): # Events that mean a significant change in credits so we should send credits after next "Update" this.lastcredits = 0 - elif entry['event'] in ['ShipyardNew', 'ShipyardSwap'] or (entry['event'] == 'Location' and entry['Docked']): + + elif event_name in ('ShipyardNew', 'ShipyardSwap') or (event_name == 'Location' and entry['Docked']): this.suppress_docked = True # Always update, even if we're not the *current* system or station provider. - this.system_address = entry.get('SystemAddress') or this.system_address - this.system = entry.get('StarSystem') or this.system + this.system_address = entry.get('SystemAddress', this.system_address) + this.system = entry.get('StarSystem', this.system) # We need pop == 0 to set the value so as to clear 'x' in systems with # no stations. @@ -243,131 +300,162 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): if pop is not None: this.system_population = pop - this.station = entry.get('StationName') or this.station - this.station_marketid = entry.get('MarketID') or this.station_marketid + this.station = entry.get('StationName', this.station) + this.station_marketid = entry.get('MarketID', this.station_marketid) or this.station_marketid # We might pick up StationName in DockingRequested, make sure we clear it if leaving - if entry['event'] in ('Undocked', 'FSDJump', 'SupercruiseEntry'): + if event_name in ('Undocked', 'FSDJump', 'SupercruiseEntry'): this.station = None this.station_marketid = None - # Send location and status on new game or StartUp. Assumes Cargo is the last event on a new game (other than Docked). - # Always send an update on Docked, FSDJump, Undocked+SuperCruise, Promotion, EngineerProgress and PowerPlay affiliation. - # Also send material and cargo (if changed) whenever we send an update. - if config.getint('inara_out') and not is_beta and not this.multicrew and credentials(cmdr): try: # Dump starting state to Inara - if (this.newuser or - entry['event'] == 'StartUp' or - (this.newsession and entry['event'] == 'Cargo')): + event_name == 'StartUp' or + (this.newsession and event_name == 'Cargo')): + this.newuser = False this.newsession = False # Send rank info to Inara on startup - add_event('setCommanderRankPilot', entry['timestamp'], - [ - OrderedDict([ - ('rankName', k.lower()), - ('rankValue', v[0]), - ('rankProgress', v[1] / 100.0), - ]) for k,v in state['Rank'].items() if v is not None - ]) - add_event('setCommanderReputationMajorFaction', entry['timestamp'], - [ - OrderedDict([ - ('majorfactionName', k.lower()), - ('majorfactionReputation', v / 100.0), - ]) for k,v in state['Reputation'].items() if v is not None - ]) - if state['Engineers']: # Not populated < 3.3 - add_event('setCommanderRankEngineer', entry['timestamp'], - [ - OrderedDict([ - ('engineerName', k), - type(v) is tuple and ('rankValue', v[0]) or ('rankStage', v), - ]) for k,v in state['Engineers'].items() - ]) + add_event( + 'setCommanderRankPilot', + entry['timestamp'], + [ + OrderedDict([ + ('rankName', k.lower()), + ('rankValue', v[0]), + ('rankProgress', v[1] / 100.0), + ]) for k, v in state['Rank'].items() if v is not None + ] + ) + + add_event( + 'setCommanderReputationMajorFaction', + entry['timestamp'], + [ + OrderedDict([('majorfactionName', k.lower()), ('majorfactionReputation', v / 100.0)]) + for k, v in state['Reputation'].items() if v is not None + ] + ) + + if state['Engineers']: # Not populated < 3.3 + add_event( + 'setCommanderRankEngineer', + entry['timestamp'], + [ + OrderedDict( + [('engineerName', k), isinstance(v, tuple) and ('rankValue', v[0]) or ('rankStage', v)] + ) + for k, v in state['Engineers'].items() + ] + ) # Update location - add_event('setCommanderTravelLocation', entry['timestamp'], - OrderedDict([ - ('starsystemName', system), - ('stationName', station), # Can be None - ])) + add_event( + 'setCommanderTravelLocation', + entry['timestamp'], + OrderedDict([ + ('starsystemName', system), + ('stationName', station), # Can be None + ]) + ) # Update ship - if state['ShipID']: # Unknown if started in Fighter or SRV + if state['ShipID']: # Unknown if started in Fighter or SRV data = OrderedDict([ ('shipType', state['ShipType']), ('shipGameID', state['ShipID']), - ('shipName', state['ShipName']), # Can be None - ('shipIdent', state['ShipIdent']), # Can be None + ('shipName', state['ShipName']), # Can be None + ('shipIdent', state['ShipIdent']), # Can be None ('isCurrentShip', True), ]) + if state['HullValue']: data['shipHullValue'] = state['HullValue'] + if state['ModulesValue']: data['shipModulesValue'] = state['ModulesValue'] + data['shipRebuyCost'] = state['Rebuy'] add_event('setCommanderShip', entry['timestamp'], data) this.loadout = make_loadout(state) add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout) - + call() # Call here just to be sure that if we can send, we do, otherwise it'll get it in the next tick - # Promotions - elif entry['event'] == 'Promotion': - for k,v in state['Rank'].items(): + elif event_name == 'Promotion': + for k, v in state['Rank'].items(): if k in entry: - add_event('setCommanderRankPilot', entry['timestamp'], - OrderedDict([ - ('rankName', k.lower()), - ('rankValue', v[0]), - ('rankProgress', 0), - ])) - elif entry['event'] == 'EngineerProgress' and 'Engineer' in entry: - add_event('setCommanderRankEngineer', entry['timestamp'], - OrderedDict([ - ('engineerName', entry['Engineer']), - 'Rank' in entry and ('rankValue', entry['Rank']) or ('rankStage', entry['Progress']), - ])) + add_event( + 'setCommanderRankPilot', + entry['timestamp'], + OrderedDict([ + ('rankName', k.lower()), + ('rankValue', v[0]), + ('rankProgress', 0), + ]) + ) + + elif event_name == 'EngineerProgress' and 'Engineer' in entry: + add_event( + 'setCommanderRankEngineer', + entry['timestamp'], + OrderedDict([ + ('engineerName', entry['Engineer']), + ('rankValue', entry['Rank']) if 'Rank' in entry else ('rankStage', entry['Progress']), + ]) + ) # PowerPlay status change - if entry['event'] == 'PowerplayJoin': - add_event('setCommanderRankPower', entry['timestamp'], - OrderedDict([ - ('powerName', entry['Power']), - ('rankValue', 1), - ])) - elif entry['event'] == 'PowerplayLeave': - add_event('setCommanderRankPower', entry['timestamp'], - OrderedDict([ - ('powerName', entry['Power']), - ('rankValue', 0), - ])) - elif entry['event'] == 'PowerplayDefect': - add_event('setCommanderRankPower', entry['timestamp'], - OrderedDict([ - ('powerName', entry['ToPower']), - ('rankValue', 1), - ])) + if event_name == 'PowerplayJoin': + add_event( + 'setCommanderRankPower', + entry['timestamp'], + OrderedDict([ + ('powerName', entry['Power']), + ('rankValue', 1), + ]) + ) + + elif event_name == 'PowerplayLeave': + add_event( + 'setCommanderRankPower', + entry['timestamp'], + OrderedDict([ + ('powerName', entry['Power']), + ('rankValue', 0), + ]) + ) + + elif event_name == 'PowerplayDefect': + add_event( + 'setCommanderRankPower', + entry['timestamp'], + OrderedDict([ + ('powerName', entry['ToPower']), + ('rankValue', 1), + ]) + ) # Ship change - if entry['event'] == 'Loadout' and this.shipswap: + if event_name == 'Loadout' and this.shipswap: data = OrderedDict([ ('shipType', state['ShipType']), ('shipGameID', state['ShipID']), - ('shipName', state['ShipName']), # Can be None - ('shipIdent', state['ShipIdent']), # Can be None + ('shipName', state['ShipName']), # Can be None + ('shipIdent', state['ShipIdent']), # Can be None ('isCurrentShip', True), ]) + if state['HullValue']: data['shipHullValue'] = state['HullValue'] + if state['ModulesValue']: data['shipModulesValue'] = state['ModulesValue'] + data['shipRebuyCost'] = state['Rebuy'] add_event('setCommanderShip', entry['timestamp'], data) @@ -376,82 +464,112 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): this.shipswap = False # Location change - elif entry['event'] == 'Docked': + elif event_name == 'Docked': if this.undocked: # Undocked and now docking again. Don't send. this.undocked = False + elif this.suppress_docked: # Don't send initial Docked event on new game this.suppress_docked = False + else: - add_event('addCommanderTravelDock', entry['timestamp'], - OrderedDict([ - ('starsystemName', system), - ('stationName', station), - ('shipType', state['ShipType']), - ('shipGameID', state['ShipID']), - ])) - elif entry['event'] == 'Undocked': + add_event( + 'addCommanderTravelDock', + entry['timestamp'], + OrderedDict([ + ('starsystemName', system), + ('stationName', station), + ('shipType', state['ShipType']), + ('shipGameID', state['ShipID']), + ]) + ) + + elif event_name == 'Undocked': this.undocked = True this.station = None - elif entry['event'] == 'SupercruiseEntry': + + elif event_name == 'SupercruiseEntry': if this.undocked: # Staying in system after undocking - send any pending events from in-station action - add_event('setCommanderTravelLocation', entry['timestamp'], - OrderedDict([ - ('starsystemName', system), - ('shipType', state['ShipType']), - ('shipGameID', state['ShipID']), - ])) + add_event( + 'setCommanderTravelLocation', + entry['timestamp'], + OrderedDict([ + ('starsystemName', system), + ('shipType', state['ShipType']), + ('shipGameID', state['ShipID']), + ]) + ) + this.undocked = False - elif entry['event'] == 'FSDJump': + + elif event_name == 'FSDJump': this.undocked = False - add_event('addCommanderTravelFSDJump', entry['timestamp'], - OrderedDict([ - ('starsystemName', entry['StarSystem']), - ('jumpDistance', entry['JumpDist']), - ('shipType', state['ShipType']), - ('shipGameID', state['ShipID']), - ])) + add_event( + 'addCommanderTravelFSDJump', + entry['timestamp'], + OrderedDict([ + ('starsystemName', entry['StarSystem']), + ('jumpDistance', entry['JumpDist']), + ('shipType', state['ShipType']), + ('shipGameID', state['ShipID']), + ]) + ) if entry.get('Factions'): - add_event('setCommanderReputationMinorFaction', entry['timestamp'], - [ - OrderedDict([ - ('minorfactionName', f['Name']), - ('minorfactionReputation', f['MyReputation']/100.0), - ]) for f in entry['Factions'] - ]) - elif entry['event'] == 'CarrierJump': - add_event('addCommanderTravelCarrierJump', entry['timestamp'], - OrderedDict([ - ('starsystemName', entry['StarSystem']), - ('stationName', entry['StationName']), - ('marketID', entry['MarketID']), - ('shipType', state['ShipType']), - ('shipGameID', state['ShipID']), - ])) + add_event( + 'setCommanderReputationMinorFaction', + entry['timestamp'], + [ + OrderedDict( + [('minorfactionName', f['Name']), ('minorfactionReputation', f['MyReputation']/100.0)] + ) + for f in entry['Factions'] + ] + ) + + elif event_name == 'CarrierJump': + add_event( + 'addCommanderTravelCarrierJump', + entry['timestamp'], + OrderedDict([ + ('starsystemName', entry['StarSystem']), + ('stationName', entry['StationName']), + ('marketID', entry['MarketID']), + ('shipType', state['ShipType']), + ('shipGameID', state['ShipID']), + ]) + ) + if entry.get('Factions'): - add_event('setCommanderReputationMinorFaction', entry['timestamp'], - [ - OrderedDict([ - ('minorfactionName', f['Name']), - ('minorfactionReputation', f['MyReputation']/100.0), - ]) for f in entry['Factions'] - ]) + add_event( + 'setCommanderReputationMinorFaction', + entry['timestamp'], + [ + OrderedDict( + [('minorfactionName', f['Name']), ('minorfactionReputation', f['MyReputation']/100.0)] + ) + for f in entry['Factions'] + ] + ) + # Ignore the following 'Docked' event this.suppress_docked = True - cargo = [OrderedDict([('itemName', k), ('itemCount', state['Cargo'][k])]) for k in sorted(state['Cargo'])] # Send cargo and materials if changed if this.cargo != cargo: add_event('setCommanderInventoryCargo', entry['timestamp'], cargo) this.cargo = cargo + materials = [] - for category in ['Raw', 'Manufactured', 'Encoded']: - materials.extend([ OrderedDict([('itemName', k), ('itemCount', state[category][k])]) for k in sorted(state[category]) ]) + for category in ('Raw', 'Manufactured', 'Encoded'): + materials.extend( + [OrderedDict([('itemName', k), ('itemCount', state[category][k])]) for k in sorted(state[category])] + ) + if this.materials != materials: add_event('setCommanderInventoryMaterials', entry['timestamp'], materials) this.materials = materials @@ -461,65 +579,82 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): return str(e) # Send credits and stats to Inara on startup only - otherwise may be out of date - if entry['event'] == 'LoadGame': - add_event('setCommanderCredits', entry['timestamp'], - OrderedDict([ - ('commanderCredits', state['Credits']), - ('commanderLoan', state['Loan']), - ])) + if event_name == 'LoadGame': + add_event( + 'setCommanderCredits', + entry['timestamp'], + OrderedDict([('commanderCredits', state['Credits']), ('commanderLoan', state['Loan'])]) + ) + this.lastcredits = state['Credits'] - elif entry['event'] == 'Statistics': - add_event('setCommanderGameStatistics', entry['timestamp'], state['Statistics']) # may be out of date + + elif event_name == 'Statistics': + add_event('setCommanderGameStatistics', entry['timestamp'], state['Statistics']) # may be out of date # Selling / swapping ships - if entry['event'] == 'ShipyardNew': - add_event('addCommanderShip', entry['timestamp'], - OrderedDict([ - ('shipType', entry['ShipType']), - ('shipGameID', entry['NewShipID']), - ])) - this.shipswap = True # Want subsequent Loadout event to be sent immediately + if event_name == 'ShipyardNew': + add_event( + 'addCommanderShip', + entry['timestamp'], + OrderedDict([('shipType', entry['ShipType']), ('shipGameID', entry['NewShipID'])]) + ) + + this.shipswap = True # Want subsequent Loadout event to be sent immediately + + elif event_name in ('ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy', 'ShipyardSwap'): + if event_name == 'ShipyardSwap': + this.shipswap = True # Don't know new ship name and ident 'til the following Loadout event - elif entry['event'] in ['ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy', 'ShipyardSwap']: - if entry['event'] == 'ShipyardSwap': - this.shipswap = True # Don't know new ship name and ident 'til the following Loadout event if 'StoreShipID' in entry: - add_event('setCommanderShip', entry['timestamp'], - OrderedDict([ - ('shipType', entry['StoreOldShip']), - ('shipGameID', entry['StoreShipID']), - ('starsystemName', system), - ('stationName', station), - ])) + add_event( + 'setCommanderShip', + entry['timestamp'], + OrderedDict([ + ('shipType', entry['StoreOldShip']), + ('shipGameID', entry['StoreShipID']), + ('starsystemName', system), + ('stationName', station), + ]) + ) + elif 'SellShipID' in entry: - add_event('delCommanderShip', entry['timestamp'], - OrderedDict([ - ('shipType', entry.get('SellOldShip', entry['ShipType'])), - ('shipGameID', entry['SellShipID']), - ])) + add_event( + 'delCommanderShip', + entry['timestamp'], + OrderedDict([ + ('shipType', entry.get('SellOldShip', entry['ShipType'])), + ('shipGameID', entry['SellShipID']), + ]) + ) - elif entry['event'] == 'SetUserShipName': - add_event('setCommanderShip', entry['timestamp'], - OrderedDict([ - ('shipType', state['ShipType']), - ('shipGameID', state['ShipID']), - ('shipName', state['ShipName']), # Can be None - ('shipIdent', state['ShipIdent']), # Can be None - ('isCurrentShip', True), - ])) + elif event_name == 'SetUserShipName': + add_event( + 'setCommanderShip', + entry['timestamp'], + OrderedDict([ + ('shipType', state['ShipType']), + ('shipGameID', state['ShipID']), + ('shipName', state['ShipName']), # Can be None + ('shipIdent', state['ShipIdent']), # Can be None + ('isCurrentShip', True), + ]) + ) - elif entry['event'] == 'ShipyardTransfer': - add_event('setCommanderShipTransfer', entry['timestamp'], - OrderedDict([ - ('shipType', entry['ShipType']), - ('shipGameID', entry['ShipID']), - ('starsystemName', system), - ('stationName', station), - ('transferTime', entry['TransferTime']), - ])) + elif event_name == 'ShipyardTransfer': + add_event( + 'setCommanderShipTransfer', + entry['timestamp'], + OrderedDict([ + ('shipType', entry['ShipType']), + ('shipGameID', entry['ShipID']), + ('starsystemName', system), + ('stationName', station), + ('transferTime', entry['TransferTime']), + ]) + ) # Fleet - if entry['event'] == 'StoredShips': + if event_name == 'StoredShips': fleet = sorted( [{ 'shipType': x['ShipType'], @@ -535,32 +670,38 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): 'shipGameID': x['ShipID'], 'shipName': x.get('Name'), 'isHot': x['Hot'], - 'starsystemName': x.get('StarSystem'), # Not present for ships in transit - 'marketID': x.get('ShipMarketID'), # " + 'starsystemName': x.get('StarSystem'), # Not present for ships in transit + 'marketID': x.get('ShipMarketID'), # " } for x in entry['ShipsRemote']], - key = itemgetter('shipGameID') + key=itemgetter('shipGameID') ) + if this.fleet != fleet: this.fleet = fleet - this.events = [x for x in this.events if x['eventName'] != 'setCommanderShip'] # Remove any unsent + this.events = [x for x in this.events if x['eventName'] != 'setCommanderShip'] # Remove any unsent for ship in this.fleet: add_event('setCommanderShip', entry['timestamp'], ship) # Loadout - if entry['event'] == 'Loadout' and not this.newsession: + if event_name == 'Loadout' and not this.newsession: loadout = make_loadout(state) if this.loadout != loadout: this.loadout = loadout - this.events = [x for x in this.events if x['eventName'] != 'setCommanderShipLoadout' or x['shipGameID'] != this.loadout['shipGameID']] # Remove any unsent for this ship + # Remove any unsent for this ship + this.events = [ + e for e in this.events + if e['eventName'] != 'setCommanderShipLoadout' or e['shipGameID'] != this.loadout['shipGameID'] + ] + add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout) # Stored modules - if entry['event'] == 'StoredModules': - items = dict([(x['StorageSlot'], x) for x in entry['Items']]) # Impose an order + if event_name == 'StoredModules': + items = {mod['StorageSlot']: mod for mod in entry['Items']} # Impose an order modules = [] for slot in sorted(items): item = items[slot] - module = OrderedDict([ + module: OrderedDictT[str, Any] = OrderedDict([ ('itemName', item['Name']), ('itemValue', item['BuyPrice']), ('isHot', item['Hot']), @@ -569,6 +710,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): # Location can be absent if in transit if 'StarSystem' in item: module['starsystemName'] = item['StarSystem'] + if 'MarketID' in item: module['marketID'] = item['MarketID'] @@ -576,6 +718,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): module['engineering'] = OrderedDict([('blueprintName', item['EngineerModifications'])]) if 'Level' in item: module['engineering']['blueprintLevel'] = item['Level'] + if 'Quality' in item: module['engineering']['blueprintQuality'] = item['Quality'] @@ -584,11 +727,12 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): if this.storedmodules != modules: # Only send on change this.storedmodules = modules - this.events = [x for x in this.events if x['eventName'] != 'setCommanderStorageModules'] # Remove any unsent + # Remove any unsent + this.events = list(filter(lambda e: e['eventName'] != 'setCommanderStorageModules', this.events)) add_event('setCommanderStorageModules', entry['timestamp'], this.storedmodules) # Missions - if entry['event'] == 'MissionAccepted': + if event_name == 'MissionAccepted': data = OrderedDict([ ('missionName', entry['Name']), ('missionGameID', entry['MissionID']), @@ -598,9 +742,10 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): ('stationNameOrigin', station), ('minorfactionNameOrigin', entry['Faction']), ]) + # optional mission-specific properties for (iprop, prop) in [ - ('missionExpiry', 'Expiry'), # Listed as optional in the docs, but always seems to be present + ('missionExpiry', 'Expiry'), # Listed as optional in the docs, but always seems to be present ('starsystemNameTarget', 'DestinationSystem'), ('stationNameTarget', 'DestinationStation'), ('minorfactionNameTarget', 'TargetFaction'), @@ -614,97 +759,137 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): ('passengerIsVIP', 'PassengerVIPs'), ('passengerIsWanted', 'PassengerWanted'), ]: + if prop in entry: data[iprop] = entry[prop] + add_event('addCommanderMission', entry['timestamp'], data) - elif entry['event'] == 'MissionAbandoned': - add_event('setCommanderMissionAbandoned', entry['timestamp'], { 'missionGameID': entry['MissionID'] }) + elif event_name == 'MissionAbandoned': + add_event('setCommanderMissionAbandoned', entry['timestamp'], {'missionGameID': entry['MissionID']}) - elif entry['event'] == 'MissionCompleted': + elif event_name == 'MissionCompleted': for x in entry.get('PermitsAwarded', []): - add_event('addCommanderPermit', entry['timestamp'], { 'starsystemName': x }) + add_event('addCommanderPermit', entry['timestamp'], {'starsystemName': x}) - data = OrderedDict([ ('missionGameID', entry['MissionID']) ]) + data = OrderedDict([('missionGameID', entry['MissionID'])]) if 'Donation' in entry: data['donationCredits'] = entry['Donation'] + if 'Reward' in entry: data['rewardCredits'] = entry['Reward'] + if 'PermitsAwarded' in entry: - data['rewardPermits'] = [{ 'starsystemName': x } for x in entry['PermitsAwarded']] + data['rewardPermits'] = [{'starsystemName': x} for x in entry['PermitsAwarded']] + if 'CommodityReward' in entry: - data['rewardCommodities'] = [{ 'itemName': x['Name'], 'itemCount': x['Count'] } for x in entry['CommodityReward']] + data['rewardCommodities'] = [{'itemName': x['Name'], 'itemCount': x['Count']} + for x in entry['CommodityReward']] + if 'MaterialsReward' in entry: - data['rewardMaterials'] = [{ 'itemName': x['Name'], 'itemCount': x['Count'] } for x in entry['MaterialsReward']] + data['rewardMaterials'] = [{'itemName': x['Name'], 'itemCount': x['Count']} + for x in entry['MaterialsReward']] + factioneffects = [] for faction in entry.get('FactionEffects', []): - effect = OrderedDict([ ('minorfactionName', faction['Faction']) ]) + effect: OrderedDictT[str, Any] = OrderedDict([('minorfactionName', faction['Faction'])]) for influence in faction.get('Influence', []): if 'Influence' in influence: - effect['influenceGain'] = len(effect.get('influenceGain', '')) > len(influence['Influence']) and effect['influenceGain'] or influence['Influence'] # pick highest + highest_gain = influence['Influence'] + if len(effect.get('influenceGain', '')) > len(highest_gain): + highest_gain = effect['influenceGain'] + + effect['influenceGain'] = highest_gain + if 'Reputation' in faction: effect['reputationGain'] = faction['Reputation'] + factioneffects.append(effect) + if factioneffects: data['minorfactionEffects'] = factioneffects + add_event('setCommanderMissionCompleted', entry['timestamp'], data) - elif entry['event'] == 'MissionFailed': - add_event('setCommanderMissionFailed', entry['timestamp'], { 'missionGameID': entry['MissionID'] }) + elif event_name == 'MissionFailed': + add_event('setCommanderMissionFailed', entry['timestamp'], {'missionGameID': entry['MissionID']}) # Combat - if entry['event'] == 'Died': - data = OrderedDict([ ('starsystemName', system) ]) + if event_name == 'Died': + data = OrderedDict([('starsystemName', system)]) if 'Killers' in entry: data['wingOpponentNames'] = [x['Name'] for x in entry['Killers']] + elif 'KillerName' in entry: data['opponentName'] = entry['KillerName'] + add_event('addCommanderCombatDeath', entry['timestamp'], data) - elif entry['event'] == 'Interdicted': + elif event_name == 'Interdicted': data = OrderedDict([('starsystemName', system), ('isPlayer', entry['IsPlayer']), ('isSubmit', entry['Submitted']), - ]) + ]) + if 'Interdictor' in entry: data['opponentName'] = entry['Interdictor'] + elif 'Faction' in entry: data['opponentName'] = entry['Faction'] + elif 'Power' in entry: data['opponentName'] = entry['Power'] + add_event('addCommanderCombatInterdicted', entry['timestamp'], data) - elif entry['event'] == 'Interdiction': - data = OrderedDict([('starsystemName', system), - ('isPlayer', entry['IsPlayer']), - ('isSuccess', entry['Success']), + elif event_name == 'Interdiction': + data: OrderedDictT[str, Any] = OrderedDict([ + ('starsystemName', system), + ('isPlayer', entry['IsPlayer']), + ('isSuccess', entry['Success']), ]) + if 'Interdicted' in entry: data['opponentName'] = entry['Interdicted'] + elif 'Faction' in entry: data['opponentName'] = entry['Faction'] + elif 'Power' in entry: data['opponentName'] = entry['Power'] + add_event('addCommanderCombatInterdiction', entry['timestamp'], data) - elif entry['event'] == 'EscapeInterdiction': - add_event('addCommanderCombatInterdictionEscape', entry['timestamp'], - OrderedDict([('starsystemName', system), - ('opponentName', entry['Interdictor']), - ('isPlayer', entry['IsPlayer']), - ])) + elif event_name == 'EscapeInterdiction': + add_event( + 'addCommanderCombatInterdictionEscape', + entry['timestamp'], + OrderedDict([ + ('starsystemName', system), + ('opponentName', entry['Interdictor']), + ('isPlayer', entry['IsPlayer']), + ]) + ) - elif entry['event'] == 'PVPKill': - add_event('addCommanderCombatKill', entry['timestamp'], - OrderedDict([('starsystemName', system), - ('opponentName', entry['Victim']), - ])) + elif event_name == 'PVPKill': + add_event( + 'addCommanderCombatKill', + entry['timestamp'], + OrderedDict([ + ('starsystemName', system), + ('opponentName', entry['Victim']), + ]) + ) # Community Goals - if entry['event'] == 'CommunityGoal': - this.events = [x for x in this.events if x['eventName'] not in ['setCommunityGoal', 'setCommanderCommunityGoalProgress']] # Remove any unsent - for goal in entry['CurrentGoals']: + if event_name == 'CommunityGoal': + # Remove any unsent + this.events = list(filter( + lambda e: e['eventName'] not in ('setCommunityGoal', 'setCommanderCommunityGoalProgress'), + this.events + )) + for goal in entry['CurrentGoals']: data = OrderedDict([ ('communitygoalGameID', goal['CGID']), ('communitygoalName', goal['Title']), @@ -715,13 +900,17 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): ('contributorsNum', goal['NumContributors']), ('contributionsTotal', goal['CurrentTotal']), ]) + if 'TierReached' in goal: data['tierReached'] = int(goal['TierReached'].split()[-1]) + if 'TopRankSize' in goal: data['topRankSize'] = goal['TopRankSize'] + if 'TopTier' in goal: data['tierMax'] = int(goal['TopTier']['Name'].split()[-1]) data['completionBonus'] = goal['TopTier']['Bonus'] + add_event('setCommunityGoal', entry['timestamp'], data) data = OrderedDict([ @@ -729,24 +918,36 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): ('contribution', goal['PlayerContribution']), ('percentileBand', goal['PlayerPercentileBand']), ]) + if 'Bonus' in goal: data['percentileBandReward'] = goal['Bonus'] + if 'PlayerInTopRank' in goal: data['isTopRank'] = goal['PlayerInTopRank'] + add_event('setCommanderCommunityGoalProgress', entry['timestamp'], data) # Friends - if entry['event'] == 'Friends': + if event_name == 'Friends': if entry['Status'] in ['Added', 'Online']: - add_event('addCommanderFriend', entry['timestamp'], - OrderedDict([('commanderName', entry['Name']), - ('gamePlatform', 'pc'), - ])) + add_event( + 'addCommanderFriend', + entry['timestamp'], + OrderedDict([ + ('commanderName', entry['Name']), + ('gamePlatform', 'pc'), + ]) + ) + elif entry['Status'] in ['Declined', 'Lost']: - add_event('delCommanderFriend', entry['timestamp'], - OrderedDict([('commanderName', entry['Name']), - ('gamePlatform', 'pc'), - ])) + add_event( + 'delCommanderFriend', + entry['timestamp'], + OrderedDict([ + ('commanderName', entry['Name']), + ('gamePlatform', 'pc'), + ]) + ) this.newuser = False @@ -757,30 +958,44 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): this.system_link.update_idletasks() if config.get('station_provider') == 'Inara': - this.station_link['text'] = this.station or (this.system_population and this.system_population > 0 and STATION_UNDOCKED or '') + to_set = this.station + if not to_set: + if this.system_population is not None and this.system_population > 0: + to_set = STATION_UNDOCKED + else: + to_set = '' + + this.station_link['text'] = to_set this.station_link['url'] = station_url(this.system, this.station) this.station_link.update_idletasks() + def cmdr_data(data, is_beta): this.cmdr = data['commander']['name'] # Always store initially, even if we're not the *current* system provider. if not this.station_marketid: this.station_marketid = data['commander']['docked'] and data['lastStarport']['id'] + # Only trust CAPI if these aren't yet set - this.system = this.system or data['lastSystem']['name'] - this.station = this.station or data['commander']['docked'] and data['lastStarport']['name'] + this.system = this.system if this.system else data['lastSystem']['name'] + + if not this.station and data['commander']['docked']: + this.station = data['lastStarport']['name'] # Override standard URL functions if config.get('system_provider') == 'Inara': this.system_link['text'] = this.system this.system_link['url'] = system_url(this.system) this.system_link.update_idletasks() + if config.get('station_provider') == 'Inara': if data['commander']['docked']: this.station_link['text'] = this.station + elif data['lastStarport']['name'] and data['lastStarport']['name'] != "": this.station_link['text'] = STATION_UNDOCKED + else: this.station_link['text'] = '' @@ -789,53 +1004,68 @@ def cmdr_data(data, is_beta): if config.getint('inara_out') and not is_beta and not this.multicrew and credentials(this.cmdr): if not (CREDIT_RATIO > this.lastcredits / data['commander']['credits'] > 1/CREDIT_RATIO): - this.events = [x for x in this.events if x['eventName'] != 'setCommanderCredits'] # Remove any unsent - add_event('setCommanderCredits', data['timestamp'], - OrderedDict([ - ('commanderCredits', data['commander']['credits']), - ('commanderLoan', data['commander'].get('debt', 0)), - ])) + this.events = [x for x in this.events if x['eventName'] != 'setCommanderCredits'] # Remove any unsent + add_event( + 'setCommanderCredits', + data['timestamp'], + OrderedDict([ + ('commanderCredits', data['commander']['credits']), + ('commanderLoan', data['commander'].get('debt', 0)), + ]) + ) + this.lastcredits = float(data['commander']['credits']) -def make_loadout(state): +def make_loadout(state: Dict[str, Any]) -> OrderedDictT[str, Any]: modules = [] for m in state['Modules'].values(): - module = OrderedDict([ + module: OrderedDictT[str, Any] = OrderedDict([ ('slotName', m['Slot']), ('itemName', m['Item']), ('itemHealth', m['Health']), ('isOn', m['On']), ('itemPriority', m['Priority']), ]) + if 'AmmoInClip' in m: module['itemAmmoClip'] = m['AmmoInClip'] + if 'AmmoInHopper' in m: module['itemAmmoHopper'] = m['AmmoInHopper'] + if 'Value' in m: module['itemValue'] = m['Value'] + if 'Hot' in m: module['isHot'] = m['Hot'] + if 'Engineering' in m: - engineering = OrderedDict([ + engineering: OrderedDictT[str, Any] = OrderedDict([ ('blueprintName', m['Engineering']['BlueprintName']), ('blueprintLevel', m['Engineering']['Level']), ('blueprintQuality', m['Engineering']['Quality']), ]) + if 'ExperimentalEffect' in m['Engineering']: engineering['experimentalEffect'] = m['Engineering']['ExperimentalEffect'] + engineering['modifiers'] = [] for mod in m['Engineering']['Modifiers']: - modifier = OrderedDict([ + modifier: OrderedDictT[str, Any] = OrderedDict([ ('name', mod['Label']), ]) + if 'OriginalValue' in mod: modifier['value'] = mod['Value'] modifier['originalValue'] = mod['OriginalValue'] modifier['lessIsGood'] = mod['LessIsGood'] + else: modifier['value'] = mod['ValueStr'] + engineering['modifiers'].append(modifier) + module['engineering'] = engineering modules.append(module) @@ -846,25 +1076,52 @@ def make_loadout(state): ('shipLoadout', modules), ]) -def add_event(name, timestamp, data): + +EVENT_DATA = Mapping[AnyStr, Any] + + +def add_event(name: str, timestamp: str, data: Union[EVENT_DATA, Sequence[EVENT_DATA]]): + """ + Add an event to the event queue + + :param name: name of the event + :param timestamp: timestamp for the event + :param data: data to be sent in the payload + """ + this.events.append(OrderedDict([ ('eventName', name), ('eventTimestamp', timestamp), ('eventData', data), ])) -def call_timer(wait=FLOOD_LIMIT_SECONDS): + +def call_timer(wait: int = FLOOD_LIMIT_SECONDS): + """ + call_timer runs in its own thread polling out to INARA once every FLOOD_LIMIT_SECONDS + + :param wait: time to wait between polls, defaults to FLOOD_LIMIT_SECONDS + """ while this.timer_run: time.sleep(wait) if this.timer_run: # check again in here just in case we're closing and the stars align call() -# Queue a call to Inara, handled in Worker thread + def call(callback=None, force=False): + """ + call queues a call out to the inara API + + Note that it will not allow a call more than once every FLOOD_LIMIT_SECONDS + unless the force parameter is True. + + :param callback: Unused and ignored. , defaults to None + :param force: Whether or not to ignore flood limits, defaults to False + """ if not this.events: return - if (time.time() - config.getint(LAST_UPDATE_CONF_KEY)) <= FLOOD_LIMIT_SECONDS and not force: + if (time.time() - config.getint(LAST_UPDATE_CONF_KEY)) <= FLOOD_LIMIT_SECONDS and not force: return config.set(LAST_UPDATE_CONF_KEY, int(time.time())) @@ -877,34 +1134,45 @@ def call(callback=None, force=False): ('commanderName', this.cmdr), ('commanderFrontierID', this.FID), ])), - ('events', list(this.events)), # shallow copy + ('events', list(this.events)), # shallow copy ]) + this.events = [] this.queue.put(('https://inara.cz/inapi/v1/', data, None)) # Worker thread + + def worker(): + """ + worker is the main thread worker and backbone of the plugin. + + As events are added to `this.queue`, the worker thread will push them to the API + """ + while True: item = this.queue.get() if not item: - return # Closing + return # Closing else: (url, data, callback) = item retrying = 0 while retrying < 3: try: - r = this.session.post(url, data=json.dumps(data, separators = (',', ':')), timeout=_TIMEOUT) + r = this.session.post(url, data=json.dumps(data, separators=(',', ':')), timeout=_TIMEOUT) r.raise_for_status() reply = r.json() status = reply['header']['eventStatus'] if callback: callback(reply) + elif status // 100 != 2: # 2xx == OK (maybe with warnings) # Log fatal errors logger.warning(f'Inara\t{status} {reply["header"].get("eventStatusText", "")}') logger.debug(f'JSON data:\n{json.dumps(data, indent=2, separators = (",", ": "))}') plug.show_error(_('Error: Inara {MSG}').format(MSG=reply['header'].get('eventStatusText', status))) + else: # Log individual errors and warnings for data_event, reply_event in zip(data['events'], reply['events']): @@ -915,6 +1183,7 @@ def worker(): plug.show_error(_('Error: Inara {MSG}').format( MSG=f'{data_event["eventName"]},' f'{reply_event.get("eventStatusText", reply_event["eventStatus"])}')) + if data_event['eventName'] in ('addCommanderTravelCarrierJump', 'addCommanderTravelDock', 'addCommanderTravelFSDJump', @@ -922,33 +1191,47 @@ def worker(): this.lastlocation = reply_event.get('eventData', {}) # calls update_location in main thread this.system_link.event_generate('<>', when="tail") + elif data_event['eventName'] in ['addCommanderShip', 'setCommanderShip']: this.lastship = reply_event.get('eventData', {}) # calls update_ship in main thread this.system_link.event_generate('<>', when="tail") break + except Exception as e: logger.debug('Unable to send events', exc_info=e) retrying += 1 else: if callback: callback(None) + else: plug.show_error(_("Error: Can't connect to Inara")) -# Call inara_notify_location() in this and other interested plugins with Inara's response when changing system or station def update_location(event=None): + """ + Call inara_notify_location in this and other interested plugins with Inara's response when changing system + or station + + :param event: Unused and ignored, defaults to None + """ if this.lastlocation: for plugin in plug.provides('inara_notify_location'): plug.invoke(plugin, None, 'inara_notify_location', this.lastlocation) + def inara_notify_location(eventData): pass -# Call inara_notify_ship() in interested plugins with Inara's response when changing ship + def update_ship(event=None): + """ + Call inara_notify_ship() in interested plugins with Inara's response when changing ship + + :param event: Unused and ignored, defaults to None + """ if this.lastship: for plugin in plug.provides('inara_notify_ship'): plug.invoke(plugin, None, 'inara_notify_ship', this.lastship)