Defer client session init
This commit is contained in:
1 parent
0e5560f441
commit
3d3eb3b005
5 files changed
+55
-46
No files matched your search
+5
-3
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user