diff --git a/gui.py b/gui.py index ddc3be55..9f74d11b 100644 --- a/gui.py +++ b/gui.py @@ -1456,6 +1456,7 @@ class _SettingsVars(TypedDict): proxy: StringVar autostart: IntVar priority_only: IntVar + prioritize_by_ending_soonest: IntVar tray_notifications: IntVar @@ -1471,6 +1472,7 @@ def __init__(self, manager: GUIManager, master: ttk.Widget): "tray": IntVar(master, self._settings.autostart_tray), "autostart": IntVar(master, self._settings.autostart), "priority_only": IntVar(master, self._settings.priority_only), + "prioritize_by_ending_soonest": IntVar(master, self._settings.prioritize_by_ending_soonest), "tray_notifications": IntVar(master, self._settings.tray_notifications), } master.rowconfigure(0, weight=1) @@ -1528,6 +1530,12 @@ def __init__(self, manager: GUIManager, master: ttk.Widget): ttk.Checkbutton( checkboxes_frame, variable=self._vars["priority_only"], command=self.priority_only ).grid(column=1, row=irow, sticky="w") + ttk.Label( + checkboxes_frame, text=_("gui", "settings", "general", "prioritize_by_ending_soonest") + ).grid(column=0, row=(irow := irow + 1), sticky="e") + ttk.Checkbutton( + checkboxes_frame, variable=self._vars["prioritize_by_ending_soonest"], command=self.prioritize_by_ending_soonest + ).grid(column=1, row=irow, sticky="w") # proxy frame proxy_frame = ttk.Frame(center_frame2) proxy_frame.grid(column=0, row=2) @@ -1740,6 +1748,9 @@ def priority_delete(self) -> None: def priority_only(self) -> None: self._settings.priority_only = bool(self._vars["priority_only"].get()) + def prioritize_by_ending_soonest(self) -> None: + self._settings.prioritize_by_ending_soonest = bool(self._vars["prioritize_by_ending_soonest"].get()) + def exclude_add(self) -> None: game_name: str = self._exclude_entry.get() if not game_name: @@ -2257,6 +2268,7 @@ async def main(exit_event: asyncio.Event): autostart=False, language="English", priority_only=False, + prioritize_by_ending_soonest=False, autostart_tray=False, exclude={"Lit Game"}, ) diff --git a/settings.py b/settings.py index 1e0ee1a7..33c1f003 100644 --- a/settings.py +++ b/settings.py @@ -18,6 +18,7 @@ class SettingsFile(TypedDict): exclude: set[str] priority: list[str] priority_only: bool + prioritize_by_ending_soonest: bool autostart_tray: bool connection_quality: int tray_notifications: bool @@ -29,6 +30,7 @@ class SettingsFile(TypedDict): "exclude": set(), "autostart": False, "priority_only": True, + "prioritize_by_ending_soonest": False, "autostart_tray": False, "connection_quality": 1, "language": DEFAULT_LANG, @@ -52,6 +54,7 @@ class Settings: exclude: set[str] priority: list[str] priority_only: bool + prioritize_by_ending_soonest: bool autostart_tray: bool connection_quality: int tray_notifications: bool diff --git a/translate.py b/translate.py index 96d96cbc..ce829d59 100644 --- a/translate.py +++ b/translate.py @@ -166,6 +166,7 @@ class GUISettingsGeneral(TypedDict): tray: str tray_notifications: str priority_only: str + prioritize_by_ending_soonest: str proxy: str @@ -363,6 +364,7 @@ class Translation(TypedDict): "tray": "Autostart into tray: ", "tray_notifications": "Tray notifications: ", "priority_only": "Priority Only: ", + "prioritize_by_ending_soonest": "Prioritize by ending soonest: ", "proxy": "Proxy (requires restart):", }, "game_name": "Game name", diff --git a/twitch.py b/twitch.py index 89aed302..76f72458 100644 --- a/twitch.py +++ b/twitch.py @@ -746,14 +746,17 @@ def get_priority(self, channel: Channel) -> int: Return a priority number for a given channel. Higher number, higher priority. - Priority 0 is given to channels streaming a game not on the priority list. - Priority -1 is given to OFFLINE channels, or channels streaming no particular games. + Priority requested games are > 0 + Non-priority games are < 0 + (maxsize - 1) Priority is given to OFFLINE channels, or channels streaming no particular games. + (maxsize - 2) Priority is given to channels streaming games without campaigns. """ if (game := channel.game) is None: # None when OFFLINE or no game set - return -1 + return -(sys.maxsize - 1) elif game not in self.wanted_games: - return 0 + # Any channel thats is filtered out by filter_campaigns() + return -(sys.maxsize - 2) return self.wanted_games[game] @staticmethod @@ -825,22 +828,21 @@ async def _run(self): # figure out which games we want self.wanted_games.clear() priorities = self.gui.settings.priorities() - exclude = self.settings.exclude - priority = self.settings.priority - priority_only = self.settings.priority_only - next_hour = datetime.now(timezone.utc) + timedelta(hours=1) - for campaign in self.inventory: + prioritize_by_ending_soonest = self.settings.prioritize_by_ending_soonest + campaigns = self.inventory + filtered_campaigns = list(filter(self.filter_campaigns, campaigns)) + for i, campaign in enumerate(filtered_campaigns): game = campaign.game - if ( - game not in self.wanted_games # isn't already there - and game.name not in exclude # and isn't excluded - # and isn't excluded by priority_only - and (not priority_only or game.name in priority) - # and can be progressed within the next hour - and campaign.can_earn_within(next_hour) - ): - # non-excluded games with no priority are placed last, below priority ones - self.wanted_games[game] = priorities.get(game.name, 0) + # get users priority preference + game_priority = priorities.get(game.name, 0) + if (game_priority): + if (prioritize_by_ending_soonest): + # list is sorted by end_at so this keeps them in order + self.wanted_games[game] = len(filtered_campaigns) - i + else: + self.wanted_games[game] = game_priority + else: + self.wanted_games[game] = -i full_cleanup = True self.restart_watching() self.change_state(State.CHANNELS_CLEANUP) @@ -1614,6 +1616,23 @@ async def fetch_campaigns( } return self._merge_data(campaign_ids, fetched_data) + def filter_campaigns(self, campaign: list[DropsCampaign]): + exclude = self.settings.exclude + priority = self.settings.priority + priority_only = self.settings.priority_only + game = campaign.game + next_hour = datetime.now(timezone.utc) + timedelta(hours=1) + if ( + game not in self.wanted_games # isn't already there + and game.name not in exclude # and isn't excluded + # and isn't excluded by priority_only + and (not priority_only or game.name in priority) + # and can be progressed within the next hour + and campaign.can_earn_within(next_hour) + ): + return True + return False + async def fetch_inventory(self) -> None: status_update = self.gui.status.update status_update(_("gui", "status", "fetching_inventory"))