Add a prioritization strategy for games outside of the priority list
This commit is contained in:
1 parent
ab37b0a143
commit
a30e8d3ce4
7 files changed
+123
-53
No files matched your search
@@ -105,6 +105,7 @@ JsonType = Dict[str, Any]
|
|||||||
URLType = NewType("URLType", str)
|
URLType = NewType("URLType", str)
|
||||||
TopicProcess: TypeAlias = "abc.Callable[[int, JsonType], Any]"
|
TopicProcess: TypeAlias = "abc.Callable[[int, JsonType], Any]"
|
||||||
# Values
|
# Values
|
||||||
|
MAX_INT = sys.maxsize
|
||||||
BASE_TOPICS = 3
|
BASE_TOPICS = 3
|
||||||
MAX_WEBSOCKETS = 8
|
MAX_WEBSOCKETS = 8
|
||||||
WS_TOPICS_LIMIT = 50
|
WS_TOPICS_LIMIT = 50
|
||||||
@@ -223,6 +224,12 @@ class State(Enum):
|
|||||||
EXIT = auto()
|
EXIT = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class PriorityMode(Enum):
|
||||||
|
PRIORITY_ONLY = 0
|
||||||
|
ENDING_SOONEST = 1
|
||||||
|
LOW_AVBL_FIRST = 2
|
||||||
|
|
||||||
|
|
||||||
class GQLOperation(JsonType):
|
class GQLOperation(JsonType):
|
||||||
def __init__(self, name: str, sha256: str, *, variables: JsonType | None = None):
|
def __init__(self, name: str, sha256: str, *, variables: JsonType | None = None):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ from constants import (
|
|||||||
WS_TOPICS_LIMIT,
|
WS_TOPICS_LIMIT,
|
||||||
OUTPUT_FORMATTER,
|
OUTPUT_FORMATTER,
|
||||||
State,
|
State,
|
||||||
|
PriorityMode,
|
||||||
)
|
)
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
from registry import RegistryKey, ValueType, ValueNotFound
|
from registry import RegistryKey, ValueType, ValueNotFound
|
||||||
@@ -388,18 +389,24 @@ class SelectCombobox(ttk.Combobox):
|
|||||||
self,
|
self,
|
||||||
master: tk.Misc,
|
master: tk.Misc,
|
||||||
*args,
|
*args,
|
||||||
|
width_offset: int = 0,
|
||||||
|
width: int | None = None,
|
||||||
textvariable: tk.StringVar,
|
textvariable: tk.StringVar,
|
||||||
values: list[str] | tuple[str, ...],
|
values: list[str] | tuple[str, ...],
|
||||||
command: abc.Callable[[tk.Event[SelectCombobox]], None] | None = None,
|
command: abc.Callable[[tk.Event[SelectCombobox]], None] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if width is None:
|
||||||
|
width = max(len(v) for v in values)
|
||||||
|
width += width_offset
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master,
|
master,
|
||||||
*args,
|
*args,
|
||||||
|
width=width,
|
||||||
values=values,
|
values=values,
|
||||||
|
state="readonly",
|
||||||
exportselection=False,
|
exportselection=False,
|
||||||
textvariable=textvariable,
|
textvariable=textvariable,
|
||||||
state="readonly",
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
if command is not None:
|
if command is not None:
|
||||||
@@ -1216,7 +1223,9 @@ class InventoryOverview:
|
|||||||
self._cache: ImageCache = manager._cache
|
self._cache: ImageCache = manager._cache
|
||||||
self._settings: Settings = manager._twitch.settings
|
self._settings: Settings = manager._twitch.settings
|
||||||
self._filters = {
|
self._filters = {
|
||||||
"not_linked": IntVar(master, 1),
|
"not_linked": IntVar(
|
||||||
|
master, self._settings.priority_mode is PriorityMode.PRIORITY_ONLY
|
||||||
|
),
|
||||||
"upcoming": IntVar(master, 1),
|
"upcoming": IntVar(master, 1),
|
||||||
"expired": IntVar(master, 0),
|
"expired": IntVar(master, 0),
|
||||||
"excluded": IntVar(master, 0),
|
"excluded": IntVar(master, 0),
|
||||||
@@ -1304,7 +1313,7 @@ class InventoryOverview:
|
|||||||
excluded = bool(self._filters["excluded"].get())
|
excluded = bool(self._filters["excluded"].get())
|
||||||
upcoming = bool(self._filters["upcoming"].get())
|
upcoming = bool(self._filters["upcoming"].get())
|
||||||
finished = bool(self._filters["finished"].get())
|
finished = bool(self._filters["finished"].get())
|
||||||
priority_only = self._settings.priority_only
|
priority_only = self._settings.priority_mode is PriorityMode.PRIORITY_ONLY
|
||||||
if (
|
if (
|
||||||
campaign.remaining_minutes > 0 # don't show sub-only campaigns
|
campaign.remaining_minutes > 0 # don't show sub-only campaigns
|
||||||
and (not_linked or campaign.linked)
|
and (not_linked or campaign.linked)
|
||||||
@@ -1534,23 +1543,32 @@ class _SettingsVars(TypedDict):
|
|||||||
proxy: StringVar
|
proxy: StringVar
|
||||||
autostart: IntVar
|
autostart: IntVar
|
||||||
language: StringVar
|
language: StringVar
|
||||||
priority_only: IntVar
|
priority_mode: StringVar
|
||||||
tray_notifications: IntVar
|
tray_notifications: IntVar
|
||||||
|
|
||||||
|
|
||||||
class SettingsPanel:
|
class SettingsPanel:
|
||||||
AUTOSTART_NAME: str = "TwitchDropsMiner"
|
AUTOSTART_NAME: str = "TwitchDropsMiner"
|
||||||
AUTOSTART_KEY: str = "HKCU/Software/Microsoft/Windows/CurrentVersion/Run"
|
AUTOSTART_KEY: str = "HKCU/Software/Microsoft/Windows/CurrentVersion/Run"
|
||||||
|
PRIORITY_MODES: dict[PriorityMode, str] = {
|
||||||
|
PriorityMode.PRIORITY_ONLY: _("gui", "settings", "priority_modes", "priority_only"),
|
||||||
|
PriorityMode.ENDING_SOONEST: _("gui", "settings", "priority_modes", "ending_soonest"),
|
||||||
|
PriorityMode.LOW_AVBL_FIRST: _("gui", "settings", "priority_modes", "low_availability"),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
def __init__(self, manager: GUIManager, master: ttk.Widget):
|
||||||
self._twitch = manager._twitch
|
self._twitch = manager._twitch
|
||||||
self._settings: Settings = manager._twitch.settings
|
self._settings: Settings = manager._twitch.settings
|
||||||
|
priority_mode = self._settings.priority_mode
|
||||||
|
if priority_mode not in self.PRIORITY_MODES:
|
||||||
|
priority_mode = PriorityMode.PRIORITY_ONLY
|
||||||
|
self._settings.priority_mode = priority_mode
|
||||||
self._vars: _SettingsVars = {
|
self._vars: _SettingsVars = {
|
||||||
|
"autostart": IntVar(master, 0),
|
||||||
"language": StringVar(master, _.current),
|
"language": StringVar(master, _.current),
|
||||||
"proxy": StringVar(master, str(self._settings.proxy)),
|
"proxy": StringVar(master, str(self._settings.proxy)),
|
||||||
"tray": IntVar(master, self._settings.autostart_tray),
|
"tray": IntVar(master, self._settings.autostart_tray),
|
||||||
"autostart": IntVar(master, 0),
|
"priority_mode": StringVar(master, self.PRIORITY_MODES[priority_mode]),
|
||||||
"priority_only": IntVar(master, self._settings.priority_only),
|
|
||||||
"tray_notifications": IntVar(master, self._settings.tray_notifications),
|
"tray_notifications": IntVar(master, self._settings.tray_notifications),
|
||||||
}
|
}
|
||||||
self._game_names: set[str] = set()
|
self._game_names: set[str] = set()
|
||||||
@@ -1577,7 +1595,6 @@ class SettingsPanel:
|
|||||||
ttk.Label(language_frame, text="Language 🌐 (requires restart): ").grid(column=0, row=0)
|
ttk.Label(language_frame, text="Language 🌐 (requires restart): ").grid(column=0, row=0)
|
||||||
SelectCombobox(
|
SelectCombobox(
|
||||||
language_frame,
|
language_frame,
|
||||||
width=12,
|
|
||||||
values=list(_.languages),
|
values=list(_.languages),
|
||||||
textvariable=self._vars["language"],
|
textvariable=self._vars["language"],
|
||||||
command=lambda e: setattr(self._settings, "language", self._vars["language"].get()),
|
command=lambda e: setattr(self._settings, "language", self._vars["language"].get()),
|
||||||
@@ -1607,10 +1624,13 @@ class SettingsPanel:
|
|||||||
command=self.update_notifications,
|
command=self.update_notifications,
|
||||||
).grid(column=1, row=irow, sticky="w")
|
).grid(column=1, row=irow, sticky="w")
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
checkboxes_frame, text=_("gui", "settings", "general", "priority_only")
|
checkboxes_frame, text=_("gui", "settings", "general", "priority_mode")
|
||||||
).grid(column=0, row=(irow := irow + 1), sticky="e")
|
).grid(column=0, row=(irow := irow + 1), sticky="e")
|
||||||
ttk.Checkbutton(
|
SelectCombobox(
|
||||||
checkboxes_frame, variable=self._vars["priority_only"], command=self.priority_only
|
checkboxes_frame,
|
||||||
|
command=self.priority_mode,
|
||||||
|
textvariable=self._vars["priority_mode"],
|
||||||
|
values=list(self.PRIORITY_MODES.values()),
|
||||||
).grid(column=1, row=irow, sticky="w")
|
).grid(column=1, row=irow, sticky="w")
|
||||||
|
|
||||||
# proxy frame
|
# proxy frame
|
||||||
@@ -1808,13 +1828,6 @@ class SettingsPanel:
|
|||||||
self.update_excluded_choices()
|
self.update_excluded_choices()
|
||||||
self.update_priority_choices()
|
self.update_priority_choices()
|
||||||
|
|
||||||
def priorities(self) -> dict[str, int]:
|
|
||||||
# NOTE: we shift the indexes so that 0 can be used as the default one
|
|
||||||
size = self._priority_list.size()
|
|
||||||
return {
|
|
||||||
game_name: size - i for i, game_name in enumerate(self._priority_list.get(0, "end"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def priority_add(self) -> None:
|
def priority_add(self) -> None:
|
||||||
game_name: str = self._priority_entry.get()
|
game_name: str = self._priority_entry.get()
|
||||||
if not game_name:
|
if not game_name:
|
||||||
@@ -1868,8 +1881,12 @@ class SettingsPanel:
|
|||||||
self._settings.alter()
|
self._settings.alter()
|
||||||
self.update_priority_choices()
|
self.update_priority_choices()
|
||||||
|
|
||||||
def priority_only(self) -> None:
|
def priority_mode(self, event: tk.Event[ttk.Combobox]) -> None:
|
||||||
self._settings.priority_only = bool(self._vars["priority_only"].get())
|
mode_name: str = self._vars["priority_mode"].get()
|
||||||
|
for value, name in self.PRIORITY_MODES.items():
|
||||||
|
if mode_name == name:
|
||||||
|
self._settings.priority_mode = value
|
||||||
|
break
|
||||||
|
|
||||||
def exclude_add(self) -> None:
|
def exclude_add(self) -> None:
|
||||||
game_name: str = self._exclude_entry.get()
|
game_name: str = self._exclude_entry.get()
|
||||||
@@ -2390,11 +2407,11 @@ if __name__ == "__main__":
|
|||||||
proxy=URL(),
|
proxy=URL(),
|
||||||
autostart=False,
|
autostart=False,
|
||||||
language="English",
|
language="English",
|
||||||
priority_only=False,
|
|
||||||
autostart_tray=False,
|
autostart_tray=False,
|
||||||
exclude={"Lit Game"},
|
exclude={"Lit Game"},
|
||||||
tray_notifications=True,
|
tray_notifications=True,
|
||||||
alter=lambda: None,
|
alter=lambda: None,
|
||||||
|
priority_mode=PriorityMode.PRIORITY_ONLY,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mock.change_state = lambda state: mock.gui.print(f"State change: {state.value}")
|
mock.change_state = lambda state: mock.gui.print(f"State change: {state.value}")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import math
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
@@ -232,6 +233,14 @@ class TimedDrop(BaseDrop):
|
|||||||
return 1.0
|
return 1.0
|
||||||
return self.current_minutes / self.required_minutes
|
return self.current_minutes / self.required_minutes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def availability(self) -> float:
|
||||||
|
if not self._base_can_earn():
|
||||||
|
# this verifies "self.total_remaining_minutes > 0" and "now < self.ends_at"
|
||||||
|
return math.inf
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
return ((self.ends_at - now).total_seconds() / 60) / self.total_remaining_minutes
|
||||||
|
|
||||||
def _base_earn_conditions(self) -> bool:
|
def _base_earn_conditions(self) -> bool:
|
||||||
return super()._base_earn_conditions() and self.required_minutes > 0
|
return super()._base_earn_conditions() and self.required_minutes > 0
|
||||||
|
|
||||||
@@ -346,6 +355,10 @@ class DropsCampaign:
|
|||||||
def progress(self) -> float:
|
def progress(self) -> float:
|
||||||
return sum(d.progress for d in self.drops) / self.total_drops
|
return sum(d.progress for d in self.drops) / self.total_drops
|
||||||
|
|
||||||
|
@property
|
||||||
|
def availability(self) -> float:
|
||||||
|
return min(d.availability for d in self.drops)
|
||||||
|
|
||||||
def _on_claim(self) -> None:
|
def _on_claim(self) -> None:
|
||||||
invalidate_cache(self, "finished", "claimed_drops", "remaining_drops")
|
invalidate_cache(self, "finished", "claimed_drops", "remaining_drops")
|
||||||
for drop in self.drops:
|
for drop in self.drops:
|
||||||
|
|||||||
+4
-4
@@ -5,7 +5,7 @@ from typing import Any, TypedDict, TYPE_CHECKING
|
|||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from utils import json_load, json_save
|
from utils import json_load, json_save
|
||||||
from constants import SETTINGS_PATH, DEFAULT_LANG
|
from constants import SETTINGS_PATH, DEFAULT_LANG, PriorityMode
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from main import ParsedArgs
|
from main import ParsedArgs
|
||||||
@@ -16,21 +16,21 @@ class SettingsFile(TypedDict):
|
|||||||
language: str
|
language: str
|
||||||
exclude: set[str]
|
exclude: set[str]
|
||||||
priority: list[str]
|
priority: list[str]
|
||||||
priority_only: bool
|
|
||||||
autostart_tray: bool
|
autostart_tray: bool
|
||||||
connection_quality: int
|
connection_quality: int
|
||||||
tray_notifications: bool
|
tray_notifications: bool
|
||||||
|
priority_mode: PriorityMode
|
||||||
|
|
||||||
|
|
||||||
default_settings: SettingsFile = {
|
default_settings: SettingsFile = {
|
||||||
"proxy": URL(),
|
"proxy": URL(),
|
||||||
"priority": [],
|
"priority": [],
|
||||||
"exclude": set(),
|
"exclude": set(),
|
||||||
"priority_only": True,
|
|
||||||
"autostart_tray": False,
|
"autostart_tray": False,
|
||||||
"connection_quality": 1,
|
"connection_quality": 1,
|
||||||
"language": DEFAULT_LANG,
|
"language": DEFAULT_LANG,
|
||||||
"tray_notifications": True,
|
"tray_notifications": True,
|
||||||
|
"priority_mode": PriorityMode.PRIORITY_ONLY,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,10 +48,10 @@ class Settings:
|
|||||||
language: str
|
language: str
|
||||||
exclude: set[str]
|
exclude: set[str]
|
||||||
priority: list[str]
|
priority: list[str]
|
||||||
priority_only: bool
|
|
||||||
autostart_tray: bool
|
autostart_tray: bool
|
||||||
connection_quality: int
|
connection_quality: int
|
||||||
tray_notifications: bool
|
tray_notifications: bool
|
||||||
|
priority_mode: PriorityMode
|
||||||
|
|
||||||
PASSTHROUGH = ("_settings", "_args", "_altered")
|
PASSTHROUGH = ("_settings", "_args", "_altered")
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -165,12 +165,19 @@ class GUISettingsGeneral(TypedDict):
|
|||||||
autostart: str
|
autostart: str
|
||||||
tray: str
|
tray: str
|
||||||
tray_notifications: str
|
tray_notifications: str
|
||||||
priority_only: str
|
priority_mode: str
|
||||||
proxy: str
|
proxy: str
|
||||||
|
|
||||||
|
|
||||||
|
class GUIPriorityModes(TypedDict):
|
||||||
|
priority_only: str
|
||||||
|
ending_soonest: str
|
||||||
|
low_availability: str
|
||||||
|
|
||||||
|
|
||||||
class GUISettings(TypedDict):
|
class GUISettings(TypedDict):
|
||||||
general: GUISettingsGeneral
|
general: GUISettingsGeneral
|
||||||
|
priority_modes: GUIPriorityModes
|
||||||
game_name: str
|
game_name: str
|
||||||
priority: str
|
priority: str
|
||||||
exclude: str
|
exclude: str
|
||||||
@@ -362,9 +369,14 @@ default_translation: Translation = {
|
|||||||
"autostart": "Autostart: ",
|
"autostart": "Autostart: ",
|
||||||
"tray": "Autostart into tray: ",
|
"tray": "Autostart into tray: ",
|
||||||
"tray_notifications": "Tray notifications: ",
|
"tray_notifications": "Tray notifications: ",
|
||||||
"priority_only": "Priority Only: ",
|
"priority_mode": "Priority mode: ",
|
||||||
"proxy": "Proxy (requires restart):",
|
"proxy": "Proxy (requires restart):",
|
||||||
},
|
},
|
||||||
|
"priority_modes": {
|
||||||
|
"priority_only": "Priority list only",
|
||||||
|
"ending_soonest": "Ending soonest",
|
||||||
|
"low_availability": "Low availability first",
|
||||||
|
},
|
||||||
"game_name": "Game name",
|
"game_name": "Game name",
|
||||||
"priority": "Priority",
|
"priority": "Priority",
|
||||||
"exclude": "Exclude",
|
"exclude": "Exclude",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ from utils import (
|
|||||||
)
|
)
|
||||||
from constants import (
|
from constants import (
|
||||||
CALL,
|
CALL,
|
||||||
|
MAX_INT,
|
||||||
DUMP_PATH,
|
DUMP_PATH,
|
||||||
COOKIES_PATH,
|
COOKIES_PATH,
|
||||||
MAX_CHANNELS,
|
MAX_CHANNELS,
|
||||||
@@ -49,6 +50,7 @@ from constants import (
|
|||||||
WATCH_INTERVAL,
|
WATCH_INTERVAL,
|
||||||
State,
|
State,
|
||||||
ClientType,
|
ClientType,
|
||||||
|
PriorityMode,
|
||||||
WebsocketTopic,
|
WebsocketTopic,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -419,7 +421,7 @@ class Twitch:
|
|||||||
# State management
|
# State management
|
||||||
self._state: State = State.IDLE
|
self._state: State = State.IDLE
|
||||||
self._state_change = asyncio.Event()
|
self._state_change = asyncio.Event()
|
||||||
self.wanted_games: dict[Game, int] = {}
|
self.wanted_games: list[Game] = []
|
||||||
self.inventory: list[DropsCampaign] = []
|
self.inventory: list[DropsCampaign] = []
|
||||||
self._drops: dict[str, TimedDrop] = {}
|
self._drops: dict[str, TimedDrop] = {}
|
||||||
self._mnt_triggers: deque[datetime] = deque()
|
self._mnt_triggers: deque[datetime] = deque()
|
||||||
@@ -545,16 +547,16 @@ class Twitch:
|
|||||||
"""
|
"""
|
||||||
Return a priority number for a given channel.
|
Return a priority number for a given channel.
|
||||||
|
|
||||||
Higher number, higher priority.
|
0 has the highest priority.
|
||||||
Priority 0 is given to channels streaming a game not on the priority list.
|
Higher numbers -> lower priority.
|
||||||
Priority -1 is given to OFFLINE channels, or channels streaming no particular games.
|
MAX_INT (a really big number) signifies the lowest possible priority.
|
||||||
"""
|
"""
|
||||||
if (game := channel.game) is None:
|
if (
|
||||||
# None when OFFLINE or no game set
|
(game := channel.game) is None # None when OFFLINE or no game set
|
||||||
return -1
|
or game not in self.wanted_games # we don't care about the played game
|
||||||
elif game not in self.wanted_games:
|
):
|
||||||
return 0
|
return MAX_INT
|
||||||
return self.wanted_games[game]
|
return self.wanted_games.index(game)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _viewers_key(channel: Channel) -> int:
|
def _viewers_key(channel: Channel) -> int:
|
||||||
@@ -633,23 +635,35 @@ class Twitch:
|
|||||||
await drop.claim()
|
await drop.claim()
|
||||||
# figure out which games we want
|
# figure out which games we want
|
||||||
self.wanted_games.clear()
|
self.wanted_games.clear()
|
||||||
priorities = self.gui.settings.priorities()
|
|
||||||
exclude = self.settings.exclude
|
exclude = self.settings.exclude
|
||||||
priority = self.settings.priority
|
priority = self.settings.priority
|
||||||
priority_only = self.settings.priority_only
|
priority_mode = self.settings.priority_mode
|
||||||
|
priority_only = priority_mode is PriorityMode.PRIORITY_ONLY
|
||||||
next_hour = datetime.now(timezone.utc) + timedelta(hours=1)
|
next_hour = datetime.now(timezone.utc) + timedelta(hours=1)
|
||||||
for campaign in self.inventory:
|
# sorted_campaigns: list[DropsCampaign] = list(self.inventory)
|
||||||
game = campaign.game
|
sorted_campaigns: list[DropsCampaign] = self.inventory
|
||||||
|
if not priority_only:
|
||||||
|
if priority_mode is PriorityMode.ENDING_SOONEST:
|
||||||
|
sorted_campaigns.sort(key=lambda c: c.ends_at)
|
||||||
|
elif priority_mode is PriorityMode.LOW_AVBL_FIRST:
|
||||||
|
sorted_campaigns.sort(key=lambda c: c.availability)
|
||||||
|
sorted_campaigns.sort(
|
||||||
|
key=lambda c: (
|
||||||
|
priority.index(c.game.name) if c.game.name in priority else MAX_INT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for campaign in sorted_campaigns:
|
||||||
|
game: Game = campaign.game
|
||||||
if (
|
if (
|
||||||
game not in self.wanted_games # isn't already there
|
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 list or priority mode
|
||||||
# and isn't excluded by priority_only
|
and game.name not in exclude
|
||||||
and (not priority_only or game.name in priority)
|
and (not priority_only or game.name in priority)
|
||||||
# and can be progressed within the next hour
|
# and can be progressed within the next hour
|
||||||
and campaign.can_earn_within(next_hour)
|
and campaign.can_earn_within(next_hour)
|
||||||
):
|
):
|
||||||
# non-excluded games with no priority are placed last, below priority ones
|
# non-excluded games with no priority are placed last, below priority ones
|
||||||
self.wanted_games[game] = priorities.get(game.name, 0)
|
self.wanted_games.append(game)
|
||||||
full_cleanup = True
|
full_cleanup = True
|
||||||
self.restart_watching()
|
self.restart_watching()
|
||||||
self.change_state(State.CHANNELS_CLEANUP)
|
self.change_state(State.CHANNELS_CLEANUP)
|
||||||
@@ -734,7 +748,7 @@ class Twitch:
|
|||||||
new_channels, key=self._viewers_key, reverse=True
|
new_channels, key=self._viewers_key, reverse=True
|
||||||
)
|
)
|
||||||
ordered_channels.sort(key=lambda ch: ch.acl_based, reverse=True)
|
ordered_channels.sort(key=lambda ch: ch.acl_based, reverse=True)
|
||||||
ordered_channels.sort(key=self.get_priority, reverse=True)
|
ordered_channels.sort(key=self.get_priority)
|
||||||
# ensure that we won't end up with more channels than we can handle
|
# ensure that we won't end up with more channels than we can handle
|
||||||
# NOTE: we trim from the end because that's where the non-priority,
|
# NOTE: we trim from the end because that's where the non-priority,
|
||||||
# offline (or online but low viewers) channels end up
|
# offline (or online but low viewers) channels end up
|
||||||
@@ -817,7 +831,7 @@ class Twitch:
|
|||||||
# for a switch (including the watching one)
|
# for a switch (including the watching one)
|
||||||
# NOTE: we need to sort the channels every time because one channel
|
# NOTE: we need to sort the channels every time because one channel
|
||||||
# can end up streaming any game - channels aren't game-tied
|
# can end up streaming any game - channels aren't game-tied
|
||||||
for channel in sorted(channels.values(), key=self.get_priority, reverse=True):
|
for channel in sorted(channels.values(), key=self.get_priority):
|
||||||
if self.can_watch(channel) and self.should_switch(channel):
|
if self.can_watch(channel) and self.should_switch(channel):
|
||||||
new_watching = channel
|
new_watching = channel
|
||||||
break
|
break
|
||||||
@@ -857,6 +871,10 @@ class Twitch:
|
|||||||
interval: float = WATCH_INTERVAL.total_seconds()
|
interval: float = WATCH_INTERVAL.total_seconds()
|
||||||
while True:
|
while True:
|
||||||
channel: Channel = await self.watching_channel.get()
|
channel: Channel = await self.watching_channel.get()
|
||||||
|
if not channel.online:
|
||||||
|
# if the channel isn't online anymore, we stop watching it
|
||||||
|
self.stop_watching()
|
||||||
|
continue
|
||||||
succeeded: bool = await channel.send_watch()
|
succeeded: bool = await channel.send_watch()
|
||||||
if not succeeded:
|
if not succeeded:
|
||||||
logger.log(CALL, f"Watch requested failed for channel: {channel.name}")
|
logger.log(CALL, f"Watch requested failed for channel: {channel.name}")
|
||||||
@@ -985,7 +1003,7 @@ class Twitch:
|
|||||||
watching_order = self.get_priority(watching_channel)
|
watching_order = self.get_priority(watching_channel)
|
||||||
return (
|
return (
|
||||||
# this channel's game is higher order than the watching one's
|
# this channel's game is higher order than the watching one's
|
||||||
channel_order > watching_order
|
channel_order < watching_order
|
||||||
or channel_order == watching_order # or the order is the same
|
or channel_order == watching_order # or the order is the same
|
||||||
# and this channel is ACL-based and the watching channel isn't
|
# and this channel is ACL-based and the watching channel isn't
|
||||||
and channel.acl_based > watching_channel.acl_based
|
and channel.acl_based > watching_channel.acl_based
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import yarl
|
|||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
from PIL import Image as Image_module
|
from PIL import Image as Image_module
|
||||||
|
|
||||||
from constants import JsonType, IS_PACKAGED
|
|
||||||
from exceptions import ExitRequest, ReloadRequest
|
from exceptions import ExitRequest, ReloadRequest
|
||||||
|
from constants import IS_PACKAGED, JsonType, PriorityMode
|
||||||
from constants import _resource_path as resource_path # noqa
|
from constants import _resource_path as resource_path # noqa
|
||||||
|
|
||||||
|
|
||||||
@@ -165,15 +165,17 @@ def invalidate_cache(instance, *attrnames):
|
|||||||
def _serialize(obj: Any) -> Any:
|
def _serialize(obj: Any) -> Any:
|
||||||
# convert data
|
# convert data
|
||||||
d: int | str | float | list[Any] | JsonType
|
d: int | str | float | list[Any] | JsonType
|
||||||
if isinstance(obj, set):
|
if isinstance(obj, datetime):
|
||||||
d = list(obj)
|
|
||||||
elif isinstance(obj, Enum):
|
|
||||||
d = obj.value
|
|
||||||
elif isinstance(obj, datetime):
|
|
||||||
if obj.tzinfo is None:
|
if obj.tzinfo is None:
|
||||||
# assume naive objects are UTC
|
# assume naive objects are UTC
|
||||||
obj = obj.replace(tzinfo=timezone.utc)
|
obj = obj.replace(tzinfo=timezone.utc)
|
||||||
d = obj.timestamp()
|
d = obj.timestamp()
|
||||||
|
elif isinstance(obj, set):
|
||||||
|
d = list(obj)
|
||||||
|
elif isinstance(obj, Enum):
|
||||||
|
# NOTE: IntEnum cannot be used, as it will get serialized as a plain integer,
|
||||||
|
# then loaded back as an integer as well.
|
||||||
|
d = obj.value
|
||||||
elif isinstance(obj, yarl.URL):
|
elif isinstance(obj, yarl.URL):
|
||||||
d = str(obj)
|
d = str(obj)
|
||||||
else:
|
else:
|
||||||
@@ -188,8 +190,9 @@ def _serialize(obj: Any) -> Any:
|
|||||||
_MISSING = object()
|
_MISSING = object()
|
||||||
SERIALIZE_ENV: dict[str, Callable[[Any], object]] = {
|
SERIALIZE_ENV: dict[str, Callable[[Any], object]] = {
|
||||||
"set": set,
|
"set": set,
|
||||||
"datetime": lambda d: datetime.fromtimestamp(d, timezone.utc),
|
|
||||||
"URL": yarl.URL,
|
"URL": yarl.URL,
|
||||||
|
"PriorityMode": PriorityMode,
|
||||||
|
"datetime": lambda d: datetime.fromtimestamp(d, timezone.utc),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user