diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index bbf2dfa0..9c7b4a14 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -5,11 +5,9 @@ from builtins import str
 from builtins import object
 import sys
 from sys import platform
-from collections import OrderedDict
-from functools import partial
 import json
 from os import chdir, environ
-from os.path import dirname, expanduser, isdir, join
+from os.path import dirname, isdir, join
 import re
 import html
 from time import time, localtime, strftime
@@ -34,7 +32,6 @@ import tkinter.messagebox
 from ttkHyperlinkLabel import HyperlinkLabel
 
 if __debug__:
-    from traceback import print_exc
     if platform != 'win32':
         import pdb
         import signal
@@ -55,7 +52,7 @@ from dashboard import dashboard
 from theme import theme
 
 
-SERVER_RETRY = 5	# retry pause for Companion servers [s]
+SERVER_RETRY = 5  # retry pause for Companion servers [s]
 
 SHIPYARD_HTML_TEMPLATE = """
 <!DOCTYPE HTML>
@@ -77,8 +74,8 @@ class AppWindow(object):
 
     # Tkinter Event types
     EVENT_KEYPRESS = 2
-    EVENT_BUTTON   = 4
-    EVENT_VIRTUAL  = 35
+    EVENT_BUTTON = 4
+    EVENT_VIRTUAL = 35
 
     def __init__(self, master):
 
@@ -97,10 +94,10 @@ class AppWindow(object):
             if platform == 'win32':
                 self.w.wm_iconbitmap(default='EDMarketConnector.ico')
             else:
-                self.w.tk.call('wm', 'iconphoto', self.w, '-default', tk.PhotoImage(file = join(config.respath, 'EDMarketConnector.png')))
-            self.theme_icon = tk.PhotoImage(data = 'R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==')
-            self.theme_minimize = tk.BitmapImage(data = '#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n   0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n')
-            self.theme_close    = tk.BitmapImage(data = '#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n   0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n   0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n')
+                self.w.tk.call('wm', 'iconphoto', self.w, '-default', tk.PhotoImage(file=join(config.respath, 'EDMarketConnector.png')))  # noqa: E501
+            self.theme_icon = tk.PhotoImage(data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==')  # noqa: E501
+            self.theme_minimize = tk.BitmapImage(data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n   0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
+            self.theme_close = tk.BitmapImage(data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n   0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n   0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
 
         frame = tk.Frame(self.w, name=appname.lower())
         frame.grid(sticky=tk.NSEW)
@@ -116,10 +113,10 @@ class AppWindow(object):
         self.system_label.grid(row=3, column=0, sticky=tk.W)
         self.station_label.grid(row=4, column=0, sticky=tk.W)
 
-        self.cmdr    = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name = 'cmdr')
-        self.ship    = HyperlinkLabel(frame, compound=tk.RIGHT, url = self.shipyard_url, name = 'ship')
-        self.system  = HyperlinkLabel(frame, compound=tk.RIGHT, url = self.system_url, popup_copy = True, name = 'system')
-        self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url = self.station_url, name = 'station')
+        self.cmdr = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='cmdr')
+        self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.shipyard_url, name='ship')
+        self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.system_url, popup_copy=True, name='system')
+        self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station')
 
         self.cmdr.grid(row=1, column=1, sticky=tk.EW)
         self.ship.grid(row=2, column=1, sticky=tk.EW)
@@ -129,39 +126,41 @@ class AppWindow(object):
         for plugin in plug.PLUGINS:
             appitem = plugin.get_app(frame)
             if appitem:
-                tk.Frame(frame, highlightthickness=1).grid(columnspan=2, sticky=tk.EW)	# separator
-                if isinstance(appitem, tuple) and len(appitem)==2:
+                tk.Frame(frame, highlightthickness=1).grid(columnspan=2, sticky=tk.EW)  # separator
+                if isinstance(appitem, tuple) and len(appitem) == 2:
                     row = frame.grid_size()[1]
                     appitem[0].grid(row=row, column=0, sticky=tk.W)
                     appitem[1].grid(row=row, column=1, sticky=tk.EW)
                 else:
                     appitem.grid(columnspan=2, sticky=tk.EW)
 
-        self.button = ttk.Button(frame, text=_('Update'), width=28, default=tk.ACTIVE, state=tk.DISABLED)	# Update button in main window
-        self.theme_button = tk.Label(frame, width = platform == 'darwin' and 32 or 28, state=tk.DISABLED)
+        # Update button in main window
+        self.button = ttk.Button(frame, text=_('Update'), width=28, default=tk.ACTIVE, state=tk.DISABLED)
+        self.theme_button = tk.Label(frame, width=32 if platform == 'darwin' else 28, state=tk.DISABLED)
         self.status = tk.Label(frame, name='status', anchor=tk.W)
 
         row = frame.grid_size()[1]
         self.button.grid(row=row, columnspan=2, sticky=tk.NSEW)
         self.theme_button.grid(row=row, columnspan=2, sticky=tk.NSEW)
-        theme.register_alternate((self.button, self.theme_button, self.theme_button), {'row':row, 'columnspan':2, 'sticky':tk.NSEW})
+        theme.register_alternate((self.button, self.theme_button, self.theme_button), {'row': row, 'columnspan': 2, 'sticky': tk.NSEW})  # noqa: E501
         self.status.grid(columnspan=2, sticky=tk.EW)
         self.button.bind('<Button-1>', self.getandsend)
         theme.button_bind(self.theme_button, self.getandsend)
 
         for child in frame.winfo_children():
-            child.grid_configure(padx=5, pady=(platform!='win32' or isinstance(child, tk.Frame)) and 2 or 0)
+            child.grid_configure(padx=5, pady=(platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0)
 
         self.menubar = tk.Menu()
-        if platform=='darwin':
+        if platform == 'darwin':
             # Can't handle (de)iconify if topmost is set, so suppress iconify button
-            # http://wiki.tcl.tk/13428 and p15 of https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf
+            # http://wiki.tcl.tk/13428 and p15 of
+            # https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf
             root.call('tk::unsupported::MacWindowStyle', 'style', root, 'document', 'closeBox resizable')
 
             # https://www.tcl.tk/man/tcl/TkCmd/menu.htm
             self.system_menu = tk.Menu(self.menubar, name='apple')
-            self.system_menu.add_command(command=lambda:self.w.call('tk::mac::standardAboutPanel'))
-            self.system_menu.add_command(command=lambda:self.updater.checkForUpdates())
+            self.system_menu.add_command(command=lambda: self.w.call('tk::mac::standardAboutPanel'))
+            self.system_menu.add_command(command=lambda: self.updater.checkForUpdates())
             self.menubar.add_cascade(menu=self.system_menu)
             self.file_menu = tk.Menu(self.menubar, name='file')
             self.file_menu.add_command(command=self.save_raw)
@@ -171,7 +170,7 @@ class AppWindow(object):
             self.menubar.add_cascade(menu=self.edit_menu)
             self.w.bind('<Command-c>', self.copy)
             self.view_menu = tk.Menu(self.menubar, name='view')
-            self.view_menu.add_command(command=lambda:stats.StatsDialog(self))
+            self.view_menu.add_command(command=lambda: stats.StatsDialog(self))
             self.menubar.add_cascade(menu=self.view_menu)
             window_menu = tk.Menu(self.menubar, name='window')
             self.menubar.add_cascade(menu=window_menu)
@@ -183,17 +182,17 @@ class AppWindow(object):
             self.w['menu'] = self.menubar
             # https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm
             self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0')
-            self.w.createcommand('tkAboutDialog', lambda:self.w.call('tk::mac::standardAboutPanel'))
+            self.w.createcommand('tkAboutDialog', lambda: self.w.call('tk::mac::standardAboutPanel'))
             self.w.createcommand("::tk::mac::Quit", self.onexit)
-            self.w.createcommand("::tk::mac::ShowPreferences", lambda:prefs.PreferencesDialog(self.w, self.postprefs))
-            self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify)	# click on app in dock = restore
-            self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw)	# close button shouldn't quit app
-            self.w.resizable(tk.FALSE, tk.FALSE)	# Can't be only resizable on one axis
+            self.w.createcommand("::tk::mac::ShowPreferences", lambda: prefs.PreferencesDialog(self.w, self.postprefs))
+            self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify)  # click on app in dock = restore
+            self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw)  # close button shouldn't quit app
+            self.w.resizable(tk.FALSE, tk.FALSE)  # Can't be only resizable on one axis
         else:
             self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
-            self.file_menu.add_command(command=lambda:stats.StatsDialog(self))
+            self.file_menu.add_command(command=lambda: stats.StatsDialog(self))
             self.file_menu.add_command(command=self.save_raw)
-            self.file_menu.add_command(command=lambda:prefs.PreferencesDialog(self.w, self.postprefs))
+            self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
             self.file_menu.add_separator()
             self.file_menu.add_command(command=self.onexit)
             self.menubar.add_cascade(menu=self.file_menu)
@@ -213,11 +212,13 @@ class AppWindow(object):
                 self.always_ontop = tk.BooleanVar(value=config.getint('always_ontop'))
                 self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
                 self.system_menu.add_separator()
-                self.system_menu.add_checkbutton(label=_('Always on top'), variable = self.always_ontop, command=self.ontop_changed)	# Appearance setting
+                self.system_menu.add_checkbutton(label=_('Always on top'),
+                                                 variable=self.always_ontop,
+                                                 command=self.ontop_changed)  # Appearance setting
                 self.menubar.add_cascade(menu=self.system_menu)
             self.w.bind('<Control-c>', self.copy)
             self.w.protocol("WM_DELETE_WINDOW", self.onexit)
-            theme.register(self.menubar)	# menus and children aren't automatically registered
+            theme.register(self.menubar)  # menus and children aren't automatically registered
             theme.register(self.file_menu)
             theme.register(self.edit_menu)
             theme.register(self.help_menu)
@@ -225,7 +226,9 @@ class AppWindow(object):
             # Alternate title bar and menu for dark theme
             self.theme_menubar = tk.Frame(frame)
             self.theme_menubar.columnconfigure(2, weight=1)
-            theme_titlebar = tk.Label(self.theme_menubar, text=applongname, image=self.theme_icon, cursor='fleur', anchor=tk.W, compound=tk.LEFT)
+            theme_titlebar = tk.Label(self.theme_menubar, text=applongname,
+                                      image=self.theme_icon, cursor='fleur',
+                                      anchor=tk.W, compound=tk.LEFT)
             theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
             self.drag_offset = None
             theme_titlebar.bind('<Button-1>', self.drag_start)
@@ -239,37 +242,49 @@ class AppWindow(object):
             theme.button_bind(theme_close, self.onexit, image=self.theme_close)
             self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
             self.theme_file_menu.grid(row=1, column=0, padx=5, sticky=tk.W)
-            theme.button_bind(self.theme_file_menu, lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height()))
+            theme.button_bind(self.theme_file_menu,
+                              lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
+                                                                e.widget.winfo_rooty()
+                                                                + e.widget.winfo_height()))
             self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
             self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
-            theme.button_bind(self.theme_edit_menu, lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height()))
+            theme.button_bind(self.theme_edit_menu,
+                              lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
+                                                                e.widget.winfo_rooty()
+                                                                + e.widget.winfo_height()))
             self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
             self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
-            theme.button_bind(self.theme_help_menu, lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height()))
+            theme.button_bind(self.theme_help_menu,
+                              lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
+                                                                e.widget.winfo_rooty()
+                                                                + e.widget.winfo_height()))
             tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=5, sticky=tk.EW)
-            theme.register(self.theme_minimize)	# images aren't automatically registered
+            theme.register(self.theme_minimize)  # images aren't automatically registered
             theme.register(self.theme_close)
             self.blank_menubar = tk.Frame(frame)
             tk.Label(self.blank_menubar).grid()
             tk.Label(self.blank_menubar).grid()
             tk.Frame(self.blank_menubar, height=2).grid()
-            theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), {'row':0, 'columnspan':2, 'sticky':tk.NSEW})
+            theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
+                                     {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
             self.w.resizable(tk.TRUE, tk.FALSE)
 
         # update geometry
         if config.get('geometry'):
-            match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry'))
+            match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry'))  # noqa: W605
             if match:
                 if platform == 'darwin':
-                    if int(match.group(2)) >= 0:	# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+                    # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+                    if int(match.group(2)) >= 0:
                         self.w.geometry(config.get('geometry'))
                 elif platform == 'win32':
                     # Check that the titlebar will be at least partly on screen
                     import ctypes
                     from ctypes.wintypes import POINT
                     # https://msdn.microsoft.com/en-us/library/dd145064
-                    MONITOR_DEFAULTTONULL = 0
-                    if ctypes.windll.user32.MonitorFromPoint(POINT(int(match.group(1)) + 16, int(match.group(2)) + 16), MONITOR_DEFAULTTONULL):
+                    MONITOR_DEFAULTTONULL = 0  # noqa: N806
+                    if ctypes.windll.user32.MonitorFromPoint(POINT(int(match.group(1)) + 16, int(match.group(2)) + 16),
+                                                             MONITOR_DEFAULTTONULL):
                         self.w.geometry(config.get('geometry'))
                 else:
                     self.w.geometry(config.get('geometry'))
@@ -278,22 +293,22 @@ class AppWindow(object):
         theme.register(frame)
         theme.apply(self.w)
 
-        self.w.bind('<Map>', self.onmap)			# Special handling for overrideredict
-        self.w.bind('<Enter>', self.onenter)			# Special handling for transparency
-        self.w.bind('<FocusIn>', self.onenter)			#   "
-        self.w.bind('<Leave>', self.onleave)			#   "
-        self.w.bind('<FocusOut>', self.onleave)			#   "
+        self.w.bind('<Map>', self.onmap)  # Special handling for overrideredict
+        self.w.bind('<Enter>', self.onenter)  # Special handling for transparency
+        self.w.bind('<FocusIn>', self.onenter)  # Special handling for transparency
+        self.w.bind('<Leave>', self.onleave)  # Special handling for transparency
+        self.w.bind('<FocusOut>', self.onleave)  # Special handling for transparency
         self.w.bind('<Return>', self.getandsend)
         self.w.bind('<KP_Enter>', self.getandsend)
-        self.w.bind_all('<<Invoke>>', self.getandsend)		# Hotkey monitoring
-        self.w.bind_all('<<JournalEvent>>', self.journal_event)	# Journal monitoring
-        self.w.bind_all('<<DashboardEvent>>', self.dashboard_event)	# Dashboard monitoring
-        self.w.bind_all('<<PluginError>>', self.plugin_error)	# Statusbar
-        self.w.bind_all('<<CompanionAuthEvent>>', self.auth)	# cAPI auth
-        self.w.bind_all('<<Quit>>', self.onexit)		# Updater
+        self.w.bind_all('<<Invoke>>', self.getandsend)  # Hotkey monitoring
+        self.w.bind_all('<<JournalEvent>>', self.journal_event)  # Journal monitoring
+        self.w.bind_all('<<DashboardEvent>>', self.dashboard_event)  # Dashboard monitoring
+        self.w.bind_all('<<PluginError>>', self.plugin_error)  # Statusbar
+        self.w.bind_all('<<CompanionAuthEvent>>', self.auth)  # cAPI auth
+        self.w.bind_all('<<Quit>>', self.onexit)  # Updater
 
         # Start a protocol handler to handle cAPI registration. Requires main loop to be running.
-        self.w.after_idle(lambda:protocolhandler.start(self.w))
+        self.w.after_idle(lambda: protocolhandler.start(self.w))
 
         # Load updater after UI creation (for WinSparkle)
         import update
@@ -305,7 +320,7 @@ class AppWindow(object):
             self.updater.checkForUpdates()  # Sparkle / WinSparkle does this automatically for packaged apps
 
         try:
-            config.get_password('')	# Prod SecureStorage on Linux to initialise
+            config.get_password('')  # Prod SecureStorage on Linux to initialise
         except RuntimeError:
             pass
 
@@ -317,93 +332,93 @@ class AppWindow(object):
         config.delete('password')
         config.delete('logdir')
 
-        self.postprefs(False)	# Companion login happens in callback from monitor
-
+        self.postprefs(False)  # Companion login happens in callback from monitor
 
     # callback after the Preferences dialog is applied
     def postprefs(self, dologin=True):
         self.prefsdialog = None
-        self.set_labels()	# in case language has changed
+        self.set_labels()  # in case language has changed
 
         # Reset links in case plugins changed them
-        self.ship.configure(url = self.shipyard_url)
-        self.system.configure(url = self.system_url)
-        self.station.configure(url = self.station_url)
+        self.ship.configure(url=self.shipyard_url)
+        self.system.configure(url=self.system_url)
+        self.station.configure(url=self.station_url)
 
         # (Re-)install hotkey monitoring
         hotkeymgr.register(self.w, config.getint('hotkey_code'), config.getint('hotkey_mods'))
 
         # (Re-)install log monitoring
         if not monitor.start(self.w):
-            self.status['text'] = 'Error: Check %s' % _('E:D journal file location')	# Location of the new Journal file in E:D 2.2
+            self.status['text'] = f'Error: Check {_("E:D journal file location")}'
 
         if dologin and monitor.cmdr:
-            self.login()	# Login if not already logged in with this Cmdr
+            self.login()  # Login if not already logged in with this Cmdr
 
     # set main window labels, e.g. after language change
     def set_labels(self):
-        self.cmdr_label['text']    = _('Cmdr') + ':'	# Main window
-        self.ship_label['text']    = (monitor.state['Captain'] and _('Role') or	# Multicrew role label in main window
-                                      _('Ship')) + ':'	# Main window
-        self.system_label['text']  = _('System') + ':'	# Main window
-        self.station_label['text'] = _('Station') + ':'	# Main window
-        self.button['text'] = self.theme_button['text'] = _('Update')	# Update button in main window
+        self.cmdr_label['text'] = _('Cmdr') + ':'  # Main window
+        # Multicrew role label in main window
+        self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or _('Ship')) + ':'  # Main window
+        self.system_label['text'] = _('System') + ':'  # Main window
+        self.station_label['text'] = _('Station') + ':'  # Main window
+        self.button['text'] = self.theme_button['text'] = _('Update')  # Update button in main window
         if platform == 'darwin':
-            self.menubar.entryconfigure(1, label=_('File'))	# Menu title
-            self.menubar.entryconfigure(2, label=_('Edit'))	# Menu title
-            self.menubar.entryconfigure(3, label=_('View'))	# Menu title on OSX
-            self.menubar.entryconfigure(4, label=_('Window'))	# Menu title on OSX
-            self.menubar.entryconfigure(5, label=_('Help'))	# Menu title
-            self.system_menu.entryconfigure(0, label=_("About {APP}").format(APP=applongname))	# App menu entry on OSX
-            self.system_menu.entryconfigure(1, label=_("Check for Updates..."))	# Menu item
-            self.file_menu.entryconfigure(0, label=_('Save Raw Data...'))	# Menu item
-            self.view_menu.entryconfigure(0, label=_('Status'))	# Menu item
-            self.help_menu.entryconfigure(1, label=_('Privacy Policy'))	# Help menu item
-            self.help_menu.entryconfigure(2, label=_('Release Notes'))	# Help menu item
+            self.menubar.entryconfigure(1, label=_('File'))  # Menu title
+            self.menubar.entryconfigure(2, label=_('Edit'))  # Menu title
+            self.menubar.entryconfigure(3, label=_('View'))  # Menu title on OSX
+            self.menubar.entryconfigure(4, label=_('Window'))  # Menu title on OSX
+            self.menubar.entryconfigure(5, label=_('Help'))  # Menu title
+            self.system_menu.entryconfigure(0, label=_("About {APP}").format(APP=applongname))  # App menu entry on OSX
+            self.system_menu.entryconfigure(1, label=_("Check for Updates..."))  # Menu item
+            self.file_menu.entryconfigure(0, label=_('Save Raw Data...'))  # Menu item
+            self.view_menu.entryconfigure(0, label=_('Status'))  # Menu item
+            self.help_menu.entryconfigure(1, label=_('Privacy Policy'))  # Help menu item
+            self.help_menu.entryconfigure(2, label=_('Release Notes'))  # Help menu item
         else:
-            self.menubar.entryconfigure(1, label=_('File'))	# Menu title
-            self.menubar.entryconfigure(2, label=_('Edit'))	# Menu title
-            self.menubar.entryconfigure(3, label=_('Help'))	# Menu title
-            self.theme_file_menu['text'] = _('File')	# Menu title
-            self.theme_edit_menu['text'] = _('Edit')	# Menu title
-            self.theme_help_menu['text'] = _('Help')	# Menu title
+            self.menubar.entryconfigure(1, label=_('File'))  # Menu title
+            self.menubar.entryconfigure(2, label=_('Edit'))  # Menu title
+            self.menubar.entryconfigure(3, label=_('Help'))  # Menu title
+            self.theme_file_menu['text'] = _('File')  # Menu title
+            self.theme_edit_menu['text'] = _('Edit')  # Menu title
+            self.theme_help_menu['text'] = _('Help')  # Menu title
 
-            ## File menu
-            self.file_menu.entryconfigure(0, label=_('Status'))	# Menu item
-            self.file_menu.entryconfigure(1, label=_('Save Raw Data...'))	# Menu item
-            self.file_menu.entryconfigure(2, label=_('Settings'))	# Item in the File menu on Windows
-            self.file_menu.entryconfigure(4, label=_('Exit'))	# Item in the File menu on Windows
+            # File menu
+            self.file_menu.entryconfigure(0, label=_('Status'))  # Menu item
+            self.file_menu.entryconfigure(1, label=_('Save Raw Data...'))  # Menu item
+            self.file_menu.entryconfigure(2, label=_('Settings'))  # Item in the File menu on Windows
+            self.file_menu.entryconfigure(4, label=_('Exit'))  # Item in the File menu on Windows
 
-            ## Help menu
-            self.help_menu.entryconfigure(0, label=_('Documentation'))	# Help menu item
-            self.help_menu.entryconfigure(1, label=_('Privacy Policy'))	# Help menu item
-            self.help_menu.entryconfigure(2, label=_('Release Notes'))	# Help menu item
-            self.help_menu.entryconfigure(3, label=_('Check for Updates...'))	# Menu item
-            self.help_menu.entryconfigure(4, label=_("About {APP}").format(APP=applongname))	# App menu entry
+            # Help menu
+            self.help_menu.entryconfigure(0, label=_('Documentation'))  # Help menu item
+            self.help_menu.entryconfigure(1, label=_('Privacy Policy'))  # Help menu item
+            self.help_menu.entryconfigure(2, label=_('Release Notes'))  # Help menu item
+            self.help_menu.entryconfigure(3, label=_('Check for Updates...'))  # Menu item
+            self.help_menu.entryconfigure(4, label=_("About {APP}").format(APP=applongname))  # App menu entry
 
-        ## Edit menu
-        self.edit_menu.entryconfigure(0, label=_('Copy'))	# As in Copy and Paste
+        # Edit menu
+        self.edit_menu.entryconfigure(0, label=_('Copy'))  # As in Copy and Paste
 
     def login(self):
         if not self.status['text']:
             self.status['text'] = _('Logging in...')
         self.button['state'] = self.theme_button['state'] = tk.DISABLED
         if platform == 'darwin':
-            self.view_menu.entryconfigure(0, state=tk.DISABLED)	# Status
-            self.file_menu.entryconfigure(0, state=tk.DISABLED)	# Save Raw Data
+            self.view_menu.entryconfigure(0, state=tk.DISABLED)  # Status
+            self.file_menu.entryconfigure(0, state=tk.DISABLED)  # Save Raw Data
         else:
-            self.file_menu.entryconfigure(0, state=tk.DISABLED)	# Status
-            self.file_menu.entryconfigure(1, state=tk.DISABLED)	# Save Raw Data
+            self.file_menu.entryconfigure(0, state=tk.DISABLED)  # Status
+            self.file_menu.entryconfigure(1, state=tk.DISABLED)  # Save Raw Data
         self.w.update_idletasks()
         try:
             if companion.session.login(monitor.cmdr, monitor.is_beta):
-                self.status['text'] = _('Authentication successful')	# Successfully authenticated with the Frontier website
+                # Successfully authenticated with the Frontier website
+                self.status['text'] = _('Authentication successful')
                 if platform == 'darwin':
-                    self.view_menu.entryconfigure(0, state=tk.NORMAL)	# Status
-                    self.file_menu.entryconfigure(0, state=tk.NORMAL)	# Save Raw Data
+                    self.view_menu.entryconfigure(0, state=tk.NORMAL)  # Status
+                    self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Save Raw Data
                 else:
-                    self.file_menu.entryconfigure(0, state=tk.NORMAL)	# Status
-                    self.file_menu.entryconfigure(1, state=tk.NORMAL)	# Save Raw Data
+                    self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
+                    self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
         except (companion.CredentialsError, companion.ServerError, companion.ServerLagging) as e:
             self.status['text'] = str(e)
         except Exception as e:
@@ -418,7 +433,7 @@ class AppWindow(object):
         play_bad = False
 
         if not monitor.cmdr or not monitor.mode or monitor.state['Captain'] or not monitor.system:
-            return	# In CQC or on crew - do nothing
+            return  # In CQC or on crew - do nothing
 
         if companion.session.state == companion.Session.STATE_AUTH:
             # Attempt another Auth
@@ -426,10 +441,10 @@ class AppWindow(object):
             return
 
         if not retrying:
-            if time() < self.holdofftime:	# Was invoked by key while in cooldown
+            if time() < self.holdofftime:  # Was invoked by key while in cooldown
                 self.status['text'] = ''
                 if play_sound and (self.holdofftime-time()) < companion.holdoff*0.75:
-                    hotkeymgr.play_bad()	# Don't play sound in first few seconds to prevent repeats
+                    hotkeymgr.play_bad()  # Don't play sound in first few seconds to prevent repeats
                 return
             elif play_sound:
                 hotkeymgr.play_good()
@@ -444,31 +459,41 @@ class AppWindow(object):
 
             # Validation
             if not data.get('commander', {}).get('name'):
-                self.status['text'] = _("Who are you?!")		# Shouldn't happen
-            elif (not data.get('lastSystem', {}).get('name') or
-                  (data['commander'].get('docked') and not data.get('lastStarport', {}).get('name'))):	# Only care if docked
-                self.status['text'] = _("Where are you?!")		# Shouldn't happen
+                self.status['text'] = _("Who are you?!")  # Shouldn't happen
+            elif (not data.get('lastSystem', {}).get('name')
+                  or (data['commander'].get('docked')
+                      and not data.get('lastStarport', {}).get('name'))):  # Only care if docked
+                self.status['text'] = _("Where are you?!")  # Shouldn't happen
             elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'):
-                self.status['text'] = _("What are you flying?!")	# Shouldn't happen
+                self.status['text'] = _("What are you flying?!")  # Shouldn't happen
             elif monitor.cmdr and data['commander']['name'] != monitor.cmdr:
-                raise companion.CmdrError()				# Companion API return doesn't match Journal
-            elif ((auto_update and not data['commander'].get('docked')) or
-                  (data['lastSystem']['name'] != monitor.system) or
-                  ((data['commander']['docked'] and data['lastStarport']['name'] or None) != monitor.station) or
-                  (data['ship']['id'] != monitor.state['ShipID']) or
-                  (data['ship']['name'].lower() != monitor.state['ShipType'])):
+                # Companion API return doesn't match Journal
+                raise companion.CmdrError()
+            elif ((auto_update and not data['commander'].get('docked'))
+                  or (data['lastSystem']['name'] != monitor.system)
+                  or ((data['commander']['docked']
+                       and data['lastStarport']['name'] or None) != monitor.station)
+                  or (data['ship']['id'] != monitor.state['ShipID'])
+                  or (data['ship']['name'].lower() != monitor.state['ShipType'])):
                 raise companion.ServerLagging()
 
             else:
 
-                if __debug__:	# Recording
+                if __debug__:  # Recording
                     if isdir('dump'):
-                        with open('dump/%s%s.%s.json' % (data['lastSystem']['name'], data['commander'].get('docked') and '.'+data['lastStarport']['name'] or '', strftime('%Y-%m-%dT%H.%M.%S', localtime())), 'wb') as h:
-                            h.write(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ')).encode('utf-8'))
+                        with open('dump/{system}{station}.{timestamp}.json'.format(
+                                system=data['lastSystem']['name'],
+                                station=data['commander'].get('docked') and '.'+data['lastStarport']['name'] or '',
+                                timestamp=strftime('%Y-%m-%dT%H.%M.%S', localtime())), 'wb') as h:
+                            h.write(json.dumps(data,
+                                               ensure_ascii=False,
+                                               indent=2,
+                                               sort_keys=True,
+                                               separators=(',', ': ')).encode('utf-8'))
 
-                if not monitor.state['ShipType']:	# Started game in SRV or fighter
+                if not monitor.state['ShipType']:  # Started game in SRV or fighter
                     self.ship['text'] = companion.ship_map.get(data['ship']['name'].lower(), data['ship']['name'])
-                    monitor.state['ShipID'] =   data['ship']['id']
+                    monitor.state['ShipID'] = data['ship']['id']
                     monitor.state['ShipType'] = data['ship']['name'].lower()
 
                 if data['commander'].get('credits') is not None:
@@ -485,16 +510,19 @@ class AppWindow(object):
                 if config.getint('output') & (config.OUT_STATION_ANY):
                     if not data['commander'].get('docked'):
                         if not self.status['text']:
-                            # Signal as error because the user might actually be docked but the server hosting the Companion API hasn't caught up
+                            # Signal as error because the user might actually be docked
+                            # but the server hosting the Companion API hasn't caught up
                             self.status['text'] = _("You're not docked at a station!")
                             play_bad = True
-                    elif (config.getint('output') & config.OUT_MKT_EDDN) and not (data['lastStarport'].get('commodities') or data['lastStarport'].get('modules')):	# Ignore possibly missing shipyard info
+                    # Ignore possibly missing shipyard info
+                    elif (config.getint('output') & config.OUT_MKT_EDDN)\
+                            and not (data['lastStarport'].get('commodities') or data['lastStarport'].get('modules')):
                         if not self.status['text']:
                             self.status['text'] = _("Station doesn't have anything!")
                     elif not data['lastStarport'].get('commodities'):
                         if not self.status['text']:
                             self.status['text'] = _("Station doesn't have a market!")
-                    elif config.getint('output') & (config.OUT_MKT_CSV|config.OUT_MKT_TD):
+                    elif config.getint('output') & (config.OUT_MKT_CSV | config.OUT_MKT_TD):
                         # Fixup anomalies in the commodity data
                         fixed = companion.fixup(data)
                         if config.getint('output') & config.OUT_MKT_CSV:
@@ -511,10 +539,10 @@ class AppWindow(object):
                 play_bad = True
             else:
                 # Retry once if Companion server is unresponsive
-                self.w.after(int(SERVER_RETRY * 1000), lambda:self.getandsend(event, True))
-                return	# early exit to avoid starting cooldown count
+                self.w.after(int(SERVER_RETRY * 1000), lambda: self.getandsend(event, True))
+                return  # early exit to avoid starting cooldown count
 
-        except companion.CmdrError as e:	# Companion API return doesn't match Journal
+        except companion.CmdrError as e:  # Companion API return doesn't match Journal
             self.status['text'] = str(e)
             play_bad = True
             companion.session.invalidate()
@@ -552,8 +580,8 @@ class AppWindow(object):
                   data.get('lastStarport', {}).get('ships', {}).get('shipyard_list')):
                 self.eddn.export_shipyard(data, monitor.is_beta)
             elif tries > 1:  # bogus data - retry
-                self.w.after(int(SERVER_RETRY * 1000), lambda:self.retry_for_shipyard(tries-1))
-        except:
+                self.w.after(int(SERVER_RETRY * 1000), lambda: self.retry_for_shipyard(tries-1))
+        except Exception:
             pass
 
     # Handle event(s) from the journal
@@ -564,9 +592,9 @@ class AppWindow(object):
             return {
                 None: '',
                 'Idle': '',
-                'FighterCon': _('Fighter'),	# Multicrew role
-                'FireCon':    _('Gunner'),	# Multicrew role
-                'FlightCon':  _('Helm'),	# Multicrew role
+                'FighterCon': _('Fighter'),  # Multicrew role
+                'FireCon':    _('Gunner'),  # Multicrew role
+                'FlightCon':  _('Helm'),  # Multicrew role
             }.get(role, role)
 
         while True:
@@ -577,26 +605,43 @@ class AppWindow(object):
             # Update main window
             self.cooldown()
             if monitor.cmdr and monitor.state['Captain']:
-                self.cmdr['text'] = '%s / %s' % (monitor.cmdr, monitor.state['Captain'])
-                self.ship_label['text'] = _('Role') + ':'	# Multicrew role label in main window
-                self.ship.configure(state = tk.NORMAL, text = crewroletext(monitor.state['Role']), url = None)
+                self.cmdr['text'] = f'{monitor.cmdr} / {monitor.state["Captain"]}'
+                self.ship_label['text'] = _('Role') + ':'  # Multicrew role label in main window
+                self.ship.configure(state=tk.NORMAL, text=crewroletext(monitor.state['Role']), url=None)
             elif monitor.cmdr:
                 if monitor.group:
-                    self.cmdr['text'] = '%s / %s' % (monitor.cmdr, monitor.group)
+                    self.cmdr['text'] = f'{monitor.cmdr} / {monitor.group}'
                 else:
                     self.cmdr['text'] = monitor.cmdr
-                self.ship_label['text'] = _('Ship') + ':'	# Main window
-                self.ship.configure(text = monitor.state['ShipName'] or companion.ship_map.get(monitor.state['ShipType'], monitor.state['ShipType']) or '',
-                                    url = self.shipyard_url)
+                self.ship_label['text'] = _('Ship') + ':'  # Main window
+                self.ship.configure(
+                    text=monitor.state['ShipName']
+                    or companion.ship_map.get(monitor.state['ShipType'], monitor.state['ShipType'])
+                    or '',
+                    url=self.shipyard_url)
             else:
                 self.cmdr['text'] = ''
-                self.ship_label['text'] = _('Ship') + ':'	# Main window
+                self.ship_label['text'] = _('Ship') + ':'  # Main window
                 self.ship['text'] = ''
 
-            self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED)	# Copy
+            self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED)  # Copy
 
-            if entry['event'] in ['Undocked', 'StartJump', 'SetUserShipName', 'ShipyardBuy', 'ShipyardSell', 'ShipyardSwap', 'ModuleBuy', 'ModuleSell', 'MaterialCollected', 'MaterialDiscarded', 'ScientificResearch', 'EngineerCraft', 'Synthesis', 'JoinACrew']:
-                self.status['text'] = ''	# Periodically clear any old error
+            if entry['event'] in (
+                    'Undocked',
+                    'StartJump',
+                    'SetUserShipName',
+                    'ShipyardBuy',
+                    'ShipyardSell',
+                    'ShipyardSwap',
+                    'ModuleBuy',
+                    'ModuleSell',
+                    'MaterialCollected',
+                    'MaterialDiscarded',
+                    'ScientificResearch',
+                    'EngineerCraft',
+                    'Synthesis',
+                    'JoinACrew'):
+                self.status['text'] = ''  # Periodically clear any old error
             self.w.update_idletasks()
 
             # Companion login
@@ -606,7 +651,7 @@ class AppWindow(object):
                 self.login()
 
             if not entry['event'] or not monitor.mode:
-                return	# Startup or in CQC
+                return  # Startup or in CQC
 
             if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started:
                 # Disable WinSparkle automatic update checks, IFF configured to do so when in-game
@@ -618,8 +663,7 @@ class AppWindow(object):
                     logger.info("Can't start Status monitoring")
 
             # Export loadout
-            if entry['event'] == 'Loadout'\
-                    and not monitor.state['Captain']\
+            if entry['event'] == 'Loadout' and not monitor.state['Captain']\
                     and config.getint('output') & config.OUT_SHIP:
                 monitor.export_ship()
 
@@ -636,7 +680,11 @@ class AppWindow(object):
                     hotkeymgr.play_bad()
 
             # Auto-Update after docking, but not if auth callback is pending
-            if entry['event'] in ['StartUp', 'Location', 'Docked'] and monitor.station and not config.getint('output') & config.OUT_MKT_MANUAL and config.getint('output') & config.OUT_STATION_ANY and companion.session.state != companion.Session.STATE_AUTH:
+            if entry['event'] in ('StartUp', 'Location', 'Docked')\
+                    and monitor.station\
+                    and not config.getint('output') & config.OUT_MKT_MANUAL\
+                    and config.getint('output') & config.OUT_STATION_ANY\
+                    and companion.session.state != companion.Session.STATE_AUTH:
                 self.w.after(int(SERVER_RETRY * 1000), self.getandsend)
 
             if entry['event'] == 'ShutDown':
@@ -698,7 +746,7 @@ class AppWindow(object):
                 provider_name=html.escape(str(provider)),
                 ship_name=html.escape(str(shipname))
             ), file=f)
-        
+
         return f'file://localhost/{file_name}'
 
     def system_url(self, system):
@@ -709,10 +757,12 @@ class AppWindow(object):
 
     def cooldown(self):
         if time() < self.holdofftime:
-            self.button['text'] = self.theme_button['text'] = _('cooldown {SS}s').format(SS = int(self.holdofftime - time()))	# Update button in main window
+            # Update button in main window
+            self.button['text'] = self.theme_button['text'] \
+                = _('cooldown {SS}s').format(SS=int(self.holdofftime - time()))
             self.w.after(1000, self.cooldown)
         else:
-            self.button['text'] = self.theme_button['text'] = _('Update')	# Update button in main window
+            self.button['text'] = self.theme_button['text'] = _('Update')  # Update button in main window
             self.button['state'] = self.theme_button['state'] = (monitor.cmdr and
                                                                  monitor.mode and
                                                                  not monitor.state['Captain'] and
@@ -726,7 +776,7 @@ class AppWindow(object):
     def copy(self, event=None):
         if monitor.system:
             self.w.clipboard_clear()
-            self.w.clipboard_append(monitor.station and '%s,%s' % (monitor.system, monitor.station) or monitor.system)
+            self.w.clipboard_append(monitor.station and f'{monitor.system},{monitor.station}' or monitor.system)
 
     def help_general(self, event=None):
         webbrowser.open('https://github.com/EDCD/EDMarketConnector/wiki')
@@ -754,11 +804,11 @@ class AppWindow(object):
                 self.transient(parent)
 
             # position over parent
-            if platform!='darwin' or parent.winfo_rooty()>0:        # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-                self.geometry("+%d+%d" % (parent.winfo_rootx(), parent.winfo_rooty()))
+            if platform != 'darwin' or parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+                self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
 
             # remove decoration
-            if platform=='win32':
+            if platform == 'win32':
                 self.attributes('-toolwindow', tk.TRUE)
                 
             self.resizable(tk.FALSE, tk.FALSE)
@@ -862,7 +912,7 @@ class AppWindow(object):
         # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
         if platform != 'darwin' or self.w.winfo_rooty() > 0:
             config.set('geometry', '+{1}+{2}'.format(*self.w.geometry().split('+')))
-        self.w.withdraw()	# Following items can take a few seconds, so hide the main window while they happen
+        self.w.withdraw()  # Following items can take a few seconds, so hide the main window while they happen
         protocolhandler.close()
         hotkeymgr.unregister()
         dashboard.close()
@@ -888,7 +938,7 @@ class AppWindow(object):
         self.w.iconify()
         self.w.update_idletasks()  # Size and windows styles get recalculated here
         self.w.wait_visibility()  # Need main window to be re-created before returning
-        theme.active = None		# So theme will be re-applied on map
+        theme.active = None  # So theme will be re-applied on map
 
     def onmap(self, event=None):
         if event.widget == self.w:
@@ -987,7 +1037,7 @@ if __name__ == "__main__":
 
     #abinit = A.B()
 
-    Translations.install(config.get('language') or None)	# Can generate errors so wait til log set up
+    Translations.install(config.get('language') or None)  # Can generate errors so wait til log set up
 
     root = tk.Tk(className=appname.lower())
     app = AppWindow(root)