diff --git a/plug.py b/plug.py index 68a2d26b..e6c4cd34 100644 --- a/plug.py +++ b/plug.py @@ -7,9 +7,8 @@ import os import sys import tkinter as tk from builtins import object, str -from typing import Any, Callable, List, Mapping, MutableMapping, Optional, Tuple - -import ttk +from tkinter import ttk +from typing import Any, Callable, List, Mapping, MutableMapping, Optional import companion import myNotebook as nb # noqa: N813 @@ -200,7 +199,7 @@ def provides(fn_name: str) -> List[str]: def invoke( - plugin_name: str, fallback: str | None, fn_name: str, *args: Tuple + plugin_name: str, fallback: str | None, fn_name: str, *args: Any ) -> Optional[str]: """ Invoke a function on a named plugin. diff --git a/plugins/edsm.py b/plugins/edsm.py index 130a794d..635b3559 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -38,12 +38,14 @@ from datetime import datetime, timedelta, timezone from queue import Queue from threading import Thread from time import sleep -from typing import TYPE_CHECKING, Any, List, Literal, Mapping, MutableMapping, Optional, Set, Tuple, Union, cast +from tkinter import ttk +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Mapping, MutableMapping, Optional, Set, Tuple, Union, cast import requests import killswitch import monitor +import myNotebook import myNotebook as nb # noqa: N813 import plug from companion import CAPIData @@ -82,8 +84,8 @@ class This: self.session: requests.Session = requests.Session() self.session.headers['User-Agent'] = user_agent self.queue: Queue = Queue() # Items to be sent to EDSM by worker thread - self.discarded_events: Set[str] = [] # List discarded events from EDSM - self.lastlookup: requests.Response # Result of last system lookup + self.discarded_events: Set[str] = set() # List discarded events from EDSM + self.lastlookup: Dict[str, Any] # Result of last system lookup # Game state self.multicrew: bool = False # don't send captain's ship info to EDSM while on a crew @@ -91,13 +93,13 @@ class This: self.newgame: bool = False # starting up - batch initial burst of events self.newgame_docked: bool = False # starting up while docked self.navbeaconscan: int = 0 # batch up burst of Scan events after NavBeaconScan - self.system_link: tk.Widget = None - self.system: tk.Tk = None - self.system_address: Optional[int] = None # Frontier SystemAddress - self.system_population: Optional[int] = None - self.station_link: tk.Widget = None - self.station: Optional[str] = None - self.station_marketid: Optional[int] = None # Frontier MarketID + self.system_link: tk.Widget | None = None + self.system: tk.Tk | None = None + self.system_address: int | None = None # Frontier SystemAddress + self.system_population: int | None = None + self.station_link: tk.Widget | None = None + self.station: str | None = None + self.station_marketid: int | None = None # Frontier MarketID self.on_foot = False self._IMG_KNOWN = None @@ -107,19 +109,19 @@ class This: self.thread: Optional[threading.Thread] = None - self.log = None - self.log_button = None + self.log: tk.IntVar | None = None + self.log_button: ttk.Checkbutton | None = None - self.label = None + self.label: tk.Widget | None = None - self.cmdr_label = None - self.cmdr_text = None + self.cmdr_label: myNotebook.Label | None = None + self.cmdr_text: myNotebook.Label | None = None - self.user_label = None - self.user = None + self.user_label: myNotebook.Label | None = None + self.user: myNotebook.Entry | None = None - self.apikey_label = None - self.apikey = None + self.apikey_label: myNotebook.Label | None = None + self.apikey: myNotebook.Entry | None = None this = This() @@ -267,7 +269,7 @@ def plugin_stop() -> None: logger.debug('Done.') -def plugin_prefs(parent: tk.Tk, cmdr: str, is_beta: bool) -> tk.Frame: +def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame: """ Plugin preferences setup hook. @@ -300,7 +302,8 @@ def plugin_prefs(parent: tk.Tk, cmdr: str, is_beta: bool) -> tk.Frame: frame, text=_('Send flight log and Cmdr status to EDSM'), variable=this.log, command=prefsvarchanged ) - this.log_button.grid(columnspan=2, padx=BUTTONX, pady=(5, 0), sticky=tk.W) + if this.log_button: + 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 @@ -315,61 +318,77 @@ def plugin_prefs(parent: tk.Tk, cmdr: str, is_beta: bool) -> tk.Frame: cur_row = 10 - this.label.grid(columnspan=2, padx=PADX, sticky=tk.W) + if this.label: + this.label.grid(columnspan=2, padx=PADX, sticky=tk.W) - # LANG: Game Commander name label in EDSM settings - this.cmdr_label = nb.Label(frame, text=_('Cmdr')) # Main window - this.cmdr_label.grid(row=cur_row, padx=PADX, sticky=tk.W) - this.cmdr_text = nb.Label(frame) - this.cmdr_text.grid(row=cur_row, column=1, padx=PADX, pady=PADY, sticky=tk.W) + if this.cmdr_label and this.cmdr_text: + # LANG: Game Commander name label in EDSM settings + this.cmdr_label = nb.Label(frame, text=_('Cmdr')) # Main window + this.cmdr_label.grid(row=cur_row, padx=PADX, sticky=tk.W) + this.cmdr_text = nb.Label(frame) + this.cmdr_text.grid(row=cur_row, column=1, padx=PADX, pady=PADY, sticky=tk.W) cur_row += 1 - # LANG: EDSM Commander name label in EDSM settings - this.user_label = nb.Label(frame, text=_('Commander Name')) # EDSM setting - this.user_label.grid(row=cur_row, padx=PADX, sticky=tk.W) - this.user = nb.Entry(frame) - this.user.grid(row=cur_row, column=1, padx=PADX, pady=PADY, sticky=tk.EW) + if this.user_label and this.label: + # LANG: EDSM Commander name label in EDSM settings + this.user_label = nb.Label(frame, text=_('Commander Name')) # EDSM setting + this.user_label.grid(row=cur_row, padx=PADX, sticky=tk.W) + this.user = nb.Entry(frame) + this.user.grid(row=cur_row, column=1, padx=PADX, pady=PADY, sticky=tk.EW) cur_row += 1 - # LANG: EDSM API key label - this.apikey_label = nb.Label(frame, text=_('API Key')) # EDSM setting - this.apikey_label.grid(row=cur_row, padx=PADX, sticky=tk.W) - this.apikey = nb.Entry(frame) - this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=PADY, sticky=tk.EW) + if this.apikey_label and this.apikey: + # LANG: EDSM API key label + this.apikey_label = nb.Label(frame, text=_('API Key')) # EDSM setting + this.apikey_label.grid(row=cur_row, padx=PADX, sticky=tk.W) + this.apikey = nb.Entry(frame) + this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=PADY, sticky=tk.EW) prefs_cmdr_changed(cmdr, is_beta) return frame -def prefs_cmdr_changed(cmdr: str, is_beta: bool) -> None: +def prefs_cmdr_changed(cmdr: str | None, is_beta: bool) -> None: # noqa: CCR001 """ Handle the Commander name changing whilst Settings was open. :param cmdr: The new current Commander name. :param is_beta: Whether game beta was detected. """ - this.log_button['state'] = tk.NORMAL if cmdr and not is_beta else tk.DISABLED - this.user['state'] = tk.NORMAL - this.user.delete(0, tk.END) - this.apikey['state'] = tk.NORMAL - this.apikey.delete(0, tk.END) + if this.log_button: + this.log_button['state'] = tk.NORMAL if cmdr and not is_beta else tk.DISABLED + + if this.user: + this.user['state'] = tk.NORMAL + this.user.delete(0, tk.END) + + if this.apikey: + this.apikey['state'] = tk.NORMAL + this.apikey.delete(0, tk.END) + if cmdr: - this.cmdr_text['text'] = f'{cmdr}{" [Beta]" if is_beta else ""}' + if this.cmdr_text: + this.cmdr_text['text'] = f'{cmdr}{" [Beta]" if is_beta else ""}' + cred = credentials(cmdr) if cred: - this.user.insert(0, cred[0]) - this.apikey.insert(0, cred[1]) + if this.user: + this.user.insert(0, cred[0]) + + if this.apikey: + this.apikey.insert(0, cred[1]) else: - # LANG: We have no data on the current commander - this.cmdr_text['text'] = _('None') + if this.cmdr_text: + # LANG: We have no data on the current commander + this.cmdr_text['text'] = _('None') to_set: Union[Literal['normal'], Literal['disabled']] = tk.DISABLED - if cmdr and not is_beta and this.log.get(): + if cmdr and not is_beta and this.log and this.log.get(): to_set = tk.NORMAL set_prefs_ui_states(to_set) @@ -378,7 +397,7 @@ def prefs_cmdr_changed(cmdr: str, is_beta: bool) -> None: def prefsvarchanged() -> None: """Handle the 'Send data to EDSM' tickbox changing state.""" to_set = tk.DISABLED - if this.log.get(): + if this.log and this.log.get() and this.log_button: to_set = this.log_button['state'] set_prefs_ui_states(to_set) @@ -390,13 +409,17 @@ def set_prefs_ui_states(state: str) -> None: :param state: the state to set each entry to """ - this.label['state'] = state - this.cmdr_label['state'] = state - this.cmdr_text['state'] = state - this.user_label['state'] = state - this.user['state'] = state - this.apikey_label['state'] = state - this.apikey['state'] = state + if ( + this.label and this.cmdr_label and this.cmdr_text and this.user_label and this.user + and this.apikey_label and this.apikey + ): + this.label['state'] = state + this.cmdr_label['state'] = state + this.cmdr_text['state'] = state + this.user_label['state'] = state + this.user['state'] = state + this.apikey_label['state'] = state + this.apikey['state'] = state def prefs_changed(cmdr: str, is_beta: bool) -> None: @@ -406,7 +429,8 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None: :param cmdr: Name of Commander. :param is_beta: Whether game beta was detected. """ - config.set('edsm_out', this.log.get()) + if this.log: + config.set('edsm_out', this.log.get()) if cmdr and not is_beta: # TODO: remove this when config is rewritten. @@ -414,17 +438,18 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None: usernames: List[str] = config.get_list('edsm_usernames', default=[]) apikeys: List[str] = config.get_list('edsm_apikeys', default=[]) - if cmdr in cmdrs: - idx = cmdrs.index(cmdr) - usernames.extend([''] * (1 + idx - len(usernames))) - usernames[idx] = this.user.get().strip() - apikeys.extend([''] * (1 + idx - len(apikeys))) - apikeys[idx] = this.apikey.get().strip() + if this.user and this.apikey: + if cmdr in cmdrs: + idx = cmdrs.index(cmdr) + usernames.extend([''] * (1 + idx - len(usernames))) + usernames[idx] = this.user.get().strip() + apikeys.extend([''] * (1 + idx - len(apikeys))) + apikeys[idx] = this.apikey.get().strip() - else: - config.set('edsm_cmdrs', cmdrs + [cmdr]) - usernames.append(this.user.get().strip()) - apikeys.append(this.apikey.get().strip()) + else: + config.set('edsm_cmdrs', cmdrs + [cmdr]) + usernames.append(this.user.get().strip()) + apikeys.append(this.apikey.get().strip()) config.set('edsm_usernames', usernames) config.set('edsm_apikeys', apikeys) @@ -545,12 +570,13 @@ entry: {entry!r}''' else: to_set = '' - this.station_link['text'] = to_set - this.station_link['url'] = station_url(str(this.system), str(this.station)) - this.station_link.update_idletasks() + if this.station_link: + this.station_link['text'] = to_set + this.station_link['url'] = station_url(str(this.system), str(this.station)) + this.station_link.update_idletasks() # Update display of 'EDSM Status' image - if this.system_link['text'] != system: + if this.system_link and this.system_link['text'] != system: this.system_link['text'] = system if system else '' this.system_link['image'] = '' this.system_link.update_idletasks() @@ -639,7 +665,7 @@ Queueing: {entry!r}''' # Update system data -def cmdr_data(data: CAPIData, is_beta: bool) -> Optional[str]: +def cmdr_data(data: CAPIData, is_beta: bool) -> Optional[str]: # noqa: CCR001 """ Process new CAPI data. @@ -663,27 +689,29 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> Optional[str]: # TODO: Fire off the EDSM API call to trigger the callback for the icons if config.get_str('system_provider') == 'EDSM': - this.system_link['text'] = this.system - # Do *NOT* set 'url' here, as it's set to a function that will call - # through correctly. We don't want a static string. - this.system_link.update_idletasks() + if this.system_link: + this.system_link['text'] = this.system + # Do *NOT* set 'url' here, as it's set to a function that will call + # through correctly. We don't want a static string. + this.system_link.update_idletasks() if config.get_str('station_provider') == 'EDSM': - if data['commander']['docked'] or this.on_foot and this.station: - this.station_link['text'] = this.station + if this.station_link: + if data['commander']['docked'] or this.on_foot and this.station: + this.station_link['text'] = this.station - elif data['lastStarport']['name'] and data['lastStarport']['name'] != "": - this.station_link['text'] = STATION_UNDOCKED + elif data['lastStarport']['name'] and data['lastStarport']['name'] != "": + this.station_link['text'] = STATION_UNDOCKED - else: - this.station_link['text'] = '' + else: + this.station_link['text'] = '' - # Do *NOT* set 'url' here, as it's set to a function that will call - # through correctly. We don't want a static string. + # Do *NOT* set 'url' here, as it's set to a function that will call + # through correctly. We don't want a static string. - this.station_link.update_idletasks() + this.station_link.update_idletasks() - if not this.system_link['text']: + if this.system_link and not this.system_link['text']: this.system_link['text'] = system this.system_link['image'] = '' this.system_link.update_idletasks() @@ -759,9 +787,12 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently retrying = 0 while retrying < 3: + if item is None: + item = cast(Tuple[str, str, str, Mapping[str, Any]], ("", {})) + should_skip, new_item = killswitch.check_killswitch( 'plugins.edsm.worker', - item if item is not None else cast(Tuple[str, Mapping[str, Any]], ("", {})), + item, logger ) @@ -838,8 +869,12 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently if any(p for p in pending if p['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked')): data_elided = data.copy() data_elided['apiKey'] = '' - data_elided['message'] = data_elided['message'].decode('utf-8') - data_elided['commanderName'] = data_elided['commanderName'].decode('utf-8') + if isinstance(data_elided['message'], bytes): + data_elided['message'] = data_elided['message'].decode('utf-8') + + if isinstance(data_elided['commanderName'], bytes): + data_elided['commanderName'] = data_elided['commanderName'].decode('utf-8') + logger.trace_if( 'journal.locations', "pending has at least one of ('CarrierJump', 'FSDJump', 'Location', 'Docked')" @@ -858,11 +893,11 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently 'journal.locations', f'Overall POST data (elided) is:\n{json.dumps(data_elided, indent=2)}' ) - r = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT) - logger.trace_if('plugin.edsm.api', f'API response content: {r.content!r}') - r.raise_for_status() + response = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT) + logger.trace_if('plugin.edsm.api', f'API response content: {response.content!r}') + response.raise_for_status() - reply = r.json() + reply = response.json() msg_num = reply['msgnum'] msg = reply['msg'] # 1xx = OK @@ -893,7 +928,7 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently # Update main window's system status this.lastlookup = r # calls update_status in main thread - if not config.shutting_down: + if not config.shutting_down and this.system_link is not None: this.system_link.event_generate('<>', when="tail") if r['msgnum'] // 100 != 1: # type: ignore @@ -996,18 +1031,19 @@ def update_status(event=None) -> None: # msgnum: 1xx = OK, 2xx = fatal error, 3xx = error, 4xx = ignorable errors. def edsm_notify_system(reply: Mapping[str, Any]) -> None: """Update the image next to the system link.""" - if not reply: - this.system_link['image'] = this._IMG_ERROR - # LANG: EDSM Plugin - Error connecting to EDSM API - plug.show_error(_("Error: Can't connect to EDSM")) + if this.system_link is not None: + if not reply: + this.system_link['image'] = this._IMG_ERROR + # LANG: EDSM Plugin - Error connecting to EDSM API + plug.show_error(_("Error: Can't connect to EDSM")) - elif reply['msgnum'] // 100 not in (1, 4): - this.system_link['image'] = this._IMG_ERROR - # LANG: EDSM Plugin - Error message from EDSM API - plug.show_error(_('Error: EDSM {MSG}').format(MSG=reply['msg'])) + elif reply['msgnum'] // 100 not in (1, 4): + this.system_link['image'] = this._IMG_ERROR + # LANG: EDSM Plugin - Error message from EDSM API + plug.show_error(_('Error: EDSM {MSG}').format(MSG=reply['msg'])) - elif reply.get('systemCreated'): - this.system_link['image'] = this._IMG_NEW + elif reply.get('systemCreated'): + this.system_link['image'] = this._IMG_NEW - else: - this.system_link['image'] = this._IMG_KNOWN + else: + this.system_link['image'] = this._IMG_KNOWN