diff --git a/api.py b/api.py index 968028e..a153566 100644 --- a/api.py +++ b/api.py @@ -1,5 +1,6 @@ import requests, json +# API request handler and data access class WorldState(): def __init__(self): self.client = requests.Session() @@ -12,6 +13,12 @@ class WorldState(): except requests.exceptions.ConnectionError: return False + # aribtrary mapping, some skipped, some not-standard anyway + # better way is probably to do setattr in a loop, but then need to + # change code and handle baro. + # not willing to change it on the off chance much of this is replaced if + # content API becomes useful and names change again + self.fissures = self.ws.get('fissures') self.invasions = self.ws.get('invasions') self.sorties = self.ws.get('sortie') diff --git a/extra_widgets.py b/extra_widgets.py index 8c40d4c..e1a2941 100644 --- a/extra_widgets.py +++ b/extra_widgets.py @@ -1,8 +1,14 @@ +# custom widgets, code is largely ripped from placeholders from +# a generated gui_base.py file and additional bits slapped on +# and some minor changes in create_gui, mostly in naming of +# widgets including indexing and subobjects as well with self.i + from PyQt5 import QtWidgets, QtGui, QtCore from datetime import datetime, timedelta import time +# util for clocks utcoffset = time.time() - time.mktime(time.gmtime()) class Fissure(QtWidgets.QWidget): @@ -53,6 +59,7 @@ class Fissure(QtWidgets.QWidget): self.gridLayout_3.addWidget(self.FissureType, 1, 1, 1, 1) def update_gui(self): + # attempt an update, will fail during initial pass before worldstate init so try-except try: self.FissureTier.setText(f"{self.fissure_info['tier']} - T{self.fissure_info['tierNum']}") self.update_timer() @@ -62,6 +69,7 @@ class Fissure(QtWidgets.QWidget): pass def update_timer(self): + # update timer, same as global timers in main eta = round(time.mktime(time.strptime(self.fissure_info['expiry'], '%Y-%m-%dT%H:%M:%S.%fZ')) - time.time() + utcoffset) if eta >= 0: self.FissureTimer.setText(f"{str(eta // 3600) + 'h' if eta // 3600 > 0 else ''} {(eta // 60) % 60}m {eta % 60}s") @@ -113,6 +121,7 @@ class Invasion(QtWidgets.QWidget): self.gridLayout_4.addWidget(self.Reward1, 0, 0, 1, 1) def update_gui(self): + # same as Fissures, progress bars are colored according to factions involved, update css try: faction_colors = {'Grineer': 'red', 'Corpus': 'blue', 'Infested': 'green'} self.Reward1.setText(f"{self.invasion_info['attackerReward']['itemString']}") @@ -123,6 +132,7 @@ class Invasion(QtWidgets.QWidget): pass def update_progress(self): + # invasion progerss tracked with progressbar, set here as psuedo-timer self.InvasionProgress.setProperty('value', self.invasion_info['completion']) class Alert(QtWidgets.QWidget): @@ -162,6 +172,7 @@ class Alert(QtWidgets.QWidget): self.horizontalLayout_6.addWidget(self.AlertTimer) def update_gui(self): + # same as fissure try: self.AlertReward.setText(self.alert_info['mission']['reward']['itemString']) self.update_timer() @@ -169,6 +180,7 @@ class Alert(QtWidgets.QWidget): pass def update_timer(self): + # same as fissure eta = round(time.mktime(time.strptime(self.alert_info['expiry'], '%Y-%m-%dT%H:%M:%S.%fZ')) - time.time() + utcoffset) if eta > 0: self.AlertTimer.setText(f'{str(eta // 86400) + "d " if eta // 86400 > 0 else ""}{str((eta // 3600) % 24) + "h" if eta // 3600 > 0 else ""} {(eta // 60) % 60}m {eta % 60}s') @@ -198,6 +210,7 @@ class BaroItem(QtWidgets.QWidget): self.gridLayout_7.addWidget(self.ItemName, 0, 0, 1, 1) def update_gui(self): + # same as fissure, except no dynamic timer needing updates try: self.ItemName.setText(self.item_info['item']) except: @@ -239,6 +252,7 @@ class Event(QtWidgets.QWidget): self.horizontalLayout_11.addWidget(self.EventTimer) def update_gui(self): + # same as fissure try: self.EventName.setText(self.event_info['description']) self.update_timer() @@ -246,6 +260,7 @@ class Event(QtWidgets.QWidget): pass def update_timer(self): + # same as fissure eta = round(time.mktime(time.strptime(self.event_info['expiry'], '%Y-%m-%dT%H:%M:%S.%fZ')) - time.time() + utcoffset) if eta > 0: self.EventTimer.setText(f'{str(eta // 86400) + "d " if eta // 86400 > 0 else ""}{(eta // 3600) % 24}h') @@ -295,6 +310,7 @@ class NewsBlurb(QtWidgets.QWidget): self.horizontalLayout_12.addWidget(self.NewsBlurbRelTimer) def update_gui(self): + # same as fissure, except populate hyperlink instead of setting labels. try: self.NewsBlurbHyperLink.setText(f'{self.news_info["message"]}') self.update_timer() @@ -302,6 +318,16 @@ class NewsBlurb(QtWidgets.QWidget): pass def update_timer(self): + # timers are wonkier than fissure since there can be both start and end dates + # chosen strat: + # if event starts in the future: + # starts in + # elif event has started and end date not reached: + # ends in + # else if no end date and start date past if it even existed: + # just display time difference to date, as in 1d ago + # + # if time delta is > 1d, only display day, else h and min prefix = '' if self.news_info.get('startDate'): eta = round(time.mktime(time.strptime(self.news_info.get('startDate'), '%Y-%m-%dT%H:%M:%S.%fZ')) - time.time() + utcoffset) diff --git a/gui.py b/gui.py index 95a4127..7baf01f 100644 --- a/gui.py +++ b/gui.py @@ -1,14 +1,18 @@ +import gui_base + from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import QApplication import sys -import gui_base +# GUI constructor from generated gui_base file +# doesn't really need to exist but its here now class GUI(QtWidgets.QMainWindow, gui_base.Ui_MainWindow): def __init__(self, parent=None): super().__init__(parent) self.setupUi(self) +# test code def main(): app = QApplication(sys.argv) gui = ExampleApp() diff --git a/main.py b/main.py index 864f1ab..5bd4908 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +# central controller, main loop + from gui import GUI from api import WorldState from extra_widgets import * @@ -11,25 +13,32 @@ import sys class App(GUI): def __init__(self): super().__init__() + # fetch world state info from API self.ws = WorldState() + # if bad fetch, error for retry until quit or fetched while self.ws.ws == {}: self.network_error() sys.exit(1) + + # remove placeholder templates for given widgets, replaced with actual data later in redraw [widget.setParent(None) for widget in [self.Fissure, self.Invasion, self.Alert, self.BaroItem, self.Event, self.NewsBlurb]] + # update interval for clocks self.timers_timer = QTimer(self) self.timers_timer.timeout.connect(self.update_timers) self.timers_timer.start(1000) + # update interval for data refresh self.ws_update_timer = QTimer(self) self.ws_update_timer.timeout.connect(self.update_world_state) self.ws_update_timer.start(1000 * 60 * 5) + # largely just creating custom widgets, and mapping to existing parent widgets self.redraw_content() def redraw_content(self): - self.redraw_content_dynamic() - self.redraw_content_static() + self.redraw_content_dynamic() # content that varies in number, invasions, fissures, etc + self.redraw_content_static() # static content that won't change/only one, arbi, archons, etc def redraw_content_dynamic(self): self.Fissure_list = [Fissure(self.FissureHolder, fissure_info, fissure_i) for (fissure_i, fissure_info) in enumerate(self.ws.fissures)] @@ -41,6 +50,7 @@ class App(GUI): self.Alert_list = [Alert(self.AlertHolder, alert_info, alert_i) for (alert_i, alert_info) in enumerate(self.ws.alerts)] [self.AlertList.addWidget(i) for i in self.Alert_list] + # if baro key is [], no baro, switch tab to inactive, else populate grid if len(self.ws.baro_items) == 0: self.BaroTabs.setCurrentIndex(0) else: @@ -56,13 +66,14 @@ class App(GUI): def redraw_content_static(self): for i in range(1, 4): - getattr(self, 'SortieM'+str(i)).setText(self.ws.sorties['variants'][i - 1]['missionType']) + getattr(self, 'SortieM' + str(i)).setText(self.ws.sorties['variants'][i - 1]['missionType']) getattr(self, 'SortieM' + str(i) + 'Mod').setText(self.ws.sorties['variants'][i-1]['modifier']) self.ArchonHuntStr.setText('Archon Hunt: ' + self.ws.archon_hunt['boss']) for i in range(1, 4): getattr(self, 'ArchonHuntM' + str(i)).setText(self.ws.archon_hunt['missions'][i - 1]['type']) + # warframestat.us arbi jank fix until content API adds arbis and can use that directly if not self.ws.arbitration: self.ArbitrationTabs.setCurrentIndex(0) else: @@ -70,6 +81,7 @@ class App(GUI): self.ArbitrationMT.setText(self.ws.arbitration['type']) self.ArbitrationFaction.setText(self.ws.arbitration['enemy']) + # nightwave mission mapping if active daily_i = 1 weekly_i = 1 elite_i = 1 @@ -94,25 +106,33 @@ class App(GUI): self.NightwaveTabs.setCurrentIndex(0) def update_timers(self): + # call timer updates and invasion progress changes [i.update_timer() for i in self.Fissure_list] [i.update_progress() for i in self.Invasion_list] [i.update_timer() for i in self.Alert_list] [i.update_timer() for i in self.events_list] [i.update_timer() for i in self.newsblurb_list] + # parse expiry datestring and calc sortie expiry time delta from now to listed expiry sortie_eta = round(time.mktime(time.strptime(self.ws.sorties['expiry'], '%Y-%m-%dT%H:%M:%S.%fZ')) - time.mktime(time.gmtime())) if sortie_eta >= 0: self.SortieResetTimer.setText(f'{str(sortie_eta // 3600) + "h " if sortie_eta // 3600 > 0 else ""}{(sortie_eta // 60) % 60}m {sortie_eta % 60}s') else: self.SortieResetTimer.setText('Expired') + # same as sortie, except expiry happens at Monday 00:00 UTC), 3 days for epoch adjust from Wed + # % week for weekly timer diff since last Monday + # day str listed separately for display formatting weekly_eta = round(time.mktime(time.strptime('1970-1-1T00:00:00.000Z', '%Y-%m-%dT%H:%M:%S.%fZ')) - time.mktime(time.gmtime()) - 3 * 86400) % (7 * 86400) self.WeeklyResetTimeDayStr.setText(str(weekly_eta // 86400) + 'd') self.WeeklyResetTimeStr.setText(f'{(weekly_eta // 3600) % 24}h {(weekly_eta // 60) % 60}m {weekly_eta % 60}s') + # same as above, bounties reset at given time adjust, 2.5hr cycle bounty_eta = round(time.mktime(time.strptime('1970-1-1T00:00:00.000Z', '%Y-%m-%dT%H:%M:%S.%fZ')) - time.mktime(time.gmtime()) + 120) % (9000) self.BountyTimeStr.setText(f'{bounty_eta // 3600}h {(bounty_eta // 60) % 60}m {bounty_eta % 60}s') + # baro reset adjust, 2 week cycle in full. + # arrives at 2 days left, present < 2 days. baro_eta = round(time.mktime(time.strptime('1970-1-1T00:00:00.000Z', '%Y-%m-%dT%H:%M:%S.%fZ')) - time.mktime(time.gmtime()) + (10*86400 + 13 * 3600)) % (14 * 86400) if baro_eta >= 2 * 86400: self.BaroStateStr.setText('Baro Arrives in:') @@ -129,6 +149,7 @@ class App(GUI): self.network_error() def network_error(self): + # slapped on later, just a dialog box for network error msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText('Network Error')