Defer client session init

This commit is contained in:
DevilXD committed 2022-01-23 12:40:16 +01:00
1 parent 0e5560f441
commit 3d3eb3b005
5 files changed
+55 -46

No files matched your search

+5 -3
View File
@@ -180,6 +180,7 @@ class Channel:
To get this monstrous thing, you have to walk a chain of requests. To get this monstrous thing, you have to walk a chain of requests.
Streamer page (HTML) --parse-> Streamer Settings (JavaScript) --parse-> Spade URL Streamer page (HTML) --parse-> Streamer Settings (JavaScript) --parse-> Spade URL
""" """
assert self._twitch._session is not None
async with self._twitch._session.get(self.url) as response: async with self._twitch._session.get(self.url) as response:
streamer_html = await response.text(encoding="utf8") streamer_html = await response.text(encoding="utf8")
match = re.search( match = re.search(
@@ -303,14 +304,15 @@ class Channel:
""" """
if not self.online: if not self.online:
return False return False
session = self._twitch._session
if session is None:
return False
if self._spade_url is None: if self._spade_url is None:
self._spade_url = await self.get_spade_url() self._spade_url = await self.get_spade_url()
logger.debug(f"Sending minute-watched to {self.name}") logger.debug(f"Sending minute-watched to {self.name}")
for attempt in range(5): for attempt in range(5):
try: try:
async with self._twitch._session.post( async with session.post(self._spade_url, data=self._payload) as response:
self._spade_url, data=self._payload
) as response:
return response.status == 204 return response.status == 204
except (aiohttp.ClientConnectionError, aiohttp.ServerTimeoutError): except (aiohttp.ClientConnectionError, aiohttp.ServerTimeoutError):
continue continue
+1 -1
View File
@@ -712,7 +712,7 @@ class TrayIcon:
def start(self): def start(self):
if self.icon is None: if self.icon is None:
loop = self._manager._twitch._loop loop = asyncio.get_running_loop()
drop = self._manager.progress._drop drop = self._manager.progress._drop
# we need this because tray icon lives in a separate thread # we need this because tray icon lives in a separate thread
+1 -1
View File
@@ -94,7 +94,7 @@ logging.getLogger("TwitchDrops.websocket").setLevel(options.debug_ws)
# client run # client run
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
client = Twitch(loop, options) client = Twitch(options)
signal.signal(signal.SIGINT, lambda *_: client.close()) signal.signal(signal.SIGINT, lambda *_: client.close())
signal.signal(signal.SIGTERM, lambda *_: client.close()) signal.signal(signal.SIGTERM, lambda *_: client.close())
try: try:
+46 -39
View File
@@ -20,9 +20,9 @@ except ModuleNotFoundError as exc:
from channel import Channel from channel import Channel
from websocket import WebsocketPool from websocket import WebsocketPool
from gui import GUIManager, LoginData from gui import GUIManager, LoginData
from inventory import DropsCampaign, Game, TimedDrop from inventory import DropsCampaign, TimedDrop
from exceptions import LoginException, CaptchaRequired from exceptions import LoginException, CaptchaRequired
from utils import AwaitableValue, OrderedSet, task_wrapper from utils import Game, AwaitableValue, OrderedSet, task_wrapper
from constants import ( from constants import (
State, State,
JsonType, JsonType,
@@ -47,29 +47,20 @@ gql_logger = logging.getLogger("TwitchDrops.gql")
class Twitch: class Twitch:
def __init__(self, loop: asyncio.AbstractEventLoop, options: ParsedArgs): def __init__(self, options: ParsedArgs):
self._loop = loop
self.options = options self.options = options
# GUI
self.gui = GUIManager(self)
# Cookies, session and auth
cookie_jar = aiohttp.CookieJar()
if os.path.isfile(COOKIES_PATH):
cookie_jar.load(COOKIES_PATH)
self._session = aiohttp.ClientSession(
loop=loop,
cookie_jar=cookie_jar,
headers={"User-Agent": USER_AGENT},
timeout=aiohttp.ClientTimeout(connect=5, total=10),
)
self._access_token: Optional[str] = None
self._user_id: Optional[int] = None
self._is_logged_in = asyncio.Event()
# State management # State management
self._state: State = State.INVENTORY_FETCH self._state: State = State.INVENTORY_FETCH
self._state_change = asyncio.Event() self._state_change = asyncio.Event()
self.inventory: Dict[Game, List[DropsCampaign]] = {}
self.game: Optional[Game] = None self.game: Optional[Game] = None
self.inventory: Dict[Game, List[DropsCampaign]] = {}
# GUI
self.gui = GUIManager(self)
# Cookies, session and auth
self._session: Optional[aiohttp.ClientSession] = None
self._access_token: Optional[str] = None
self._user_id: Optional[int] = None
self._is_logged_in = asyncio.Event()
# Storing and watching channels # Storing and watching channels
self.channels: OrderedDict[int, Channel] = OrderedDict() self.channels: OrderedDict[int, Channel] = OrderedDict()
self.watching_channel: AwaitableValue[Channel] = AwaitableValue() self.watching_channel: AwaitableValue[Channel] = AwaitableValue()
@@ -79,6 +70,33 @@ class Twitch:
# Websocket # Websocket
self.websocket = WebsocketPool(self) self.websocket = WebsocketPool(self)
async def initialize(self) -> None:
cookie_jar = aiohttp.CookieJar()
if os.path.isfile(COOKIES_PATH):
cookie_jar.load(COOKIES_PATH)
self._session = aiohttp.ClientSession(
cookie_jar=cookie_jar,
headers={"User-Agent": USER_AGENT},
timeout=aiohttp.ClientTimeout(connect=5, total=10),
)
async def shutdown(self) -> None:
start_time = time()
self.gui.print("Exiting...")
self.stop_watching()
if self._watching_task is not None:
self._watching_task.cancel()
self._watching_task = None
# close session and stop websocket
if self._session is not None:
self._session.cookie_jar.save(COOKIES_PATH) # type: ignore
await self._session.close()
self._session = None
await self.websocket.stop()
# wait at least one full second + whatever it takes to complete the closing
# this allows aiohttp to safely close the session
await asyncio.sleep(start_time + 1 - time())
def wait_until_login(self): def wait_until_login(self):
return self._is_logged_in.wait() return self._is_logged_in.wait()
@@ -120,25 +138,6 @@ class Twitch:
""" """
self.gui.print(*args, **kwargs) self.gui.print(*args, **kwargs)
def stop(self):
self.stop_watching()
if self._watching_task is not None:
self._watching_task.cancel()
self._watching_task = None
async def shutdown(self):
start_time = time()
self.gui.print("Exiting...")
self.stop()
# save our cookies
self._session.cookie_jar.save(COOKIES_PATH) # type: ignore
# stop websocket and close session
await self.websocket.stop()
await self._session.close()
# wait at least one full second + whatever it takes to complete the closing
# this allows aiohttp to safely close the session
await asyncio.sleep(start_time + 1 - time())
def is_watching(self, channel: Channel) -> bool: def is_watching(self, channel: Channel) -> bool:
watching_channel = self.watching_channel.get_with_default(None) watching_channel = self.watching_channel.get_with_default(None)
return watching_channel is not None and watching_channel == channel return watching_channel is not None and watching_channel == channel
@@ -152,7 +151,11 @@ class Twitch:
• Selecting a stream to watch, and watching it • Selecting a stream to watch, and watching it
• Changing the stream that's being watched if necessary • Changing the stream that's being watched if necessary
""" """
if self._session is None:
await self.initialize()
self.gui.start() self.gui.start()
if self._watching_task is not None:
self._watching_task.cancel()
self._watching_task = asyncio.create_task(self._watch_loop()) self._watching_task = asyncio.create_task(self._watch_loop())
await self.check_login() await self.check_login()
# Add default topics # Add default topics
@@ -575,6 +578,7 @@ class Twitch:
""" """
if not 8 <= len(password) <= 71: if not 8 <= len(password) <= 71:
return False return False
assert self._session is not None
payload = {"password": password} payload = {"password": password}
async with self._session.post( async with self._session.post(
f"{AUTH_URL}/api/v1/password_strength", json=payload f"{AUTH_URL}/api/v1/password_strength", json=payload
@@ -590,6 +594,7 @@ class Twitch:
async def _login(self) -> str: async def _login(self) -> str:
logger.debug("Login flow started") logger.debug("Login flow started")
assert self._session is not None
payload: JsonType = { payload: JsonType = {
"client_id": CLIENT_ID, "client_id": CLIENT_ID,
@@ -670,6 +675,7 @@ class Twitch:
if self._access_token is not None and self._user_id is not None: if self._access_token is not None and self._user_id is not None:
# we're all good # we're all good
return return
assert self._session is not None
# looks like we're missing something # looks like we're missing something
logger.debug("Checking login") logger.debug("Checking login")
self.gui.login.update("Logging in...", None) self.gui.login.update("Logging in...", None)
@@ -712,6 +718,7 @@ class Twitch:
async def gql_request(self, op: GQLOperation) -> JsonType: async def gql_request(self, op: GQLOperation) -> JsonType:
await self.check_login() await self.check_login()
assert self._session is not None
headers = { headers = {
"Authorization": f"OAuth {self._access_token}", "Authorization": f"OAuth {self._access_token}",
"Client-Id": CLIENT_ID, "Client-Id": CLIENT_ID,
+2 -2
View File
@@ -13,8 +13,8 @@ from typing import Union, List, MutableSet, Iterable, Iterator, Generic, TypeVar
from constants import JsonType from constants import JsonType
_V = TypeVar("_V") _V = TypeVar("_V") # value
_D = TypeVar("_D") _D = TypeVar("_D") # default
logger = logging.getLogger("TwitchDrops") logger = logging.getLogger("TwitchDrops")
NONCE_CHARS = string.ascii_letters + string.digits NONCE_CHARS = string.ascii_letters + string.digits