55from datetime import datetime , timedelta
66from typing import Dict , List , Optional , Union , Tuple
77from urllib .parse import urlsplit , parse_qs
8+ from random import choices
9+ import string
810
911from aiohttp import ClientSession , ClientResponse
1012from aiohttp .client_exceptions import ClientError , ClientResponseError
@@ -37,11 +39,17 @@ class API: # pylint: disable=too-many-instance-attributes
3739 """Define a class for interacting with the MyQ iOS App API."""
3840
3941 def __init__ (
40- self , username : str , password : str , websession : ClientSession = None
42+ self ,
43+ username : str ,
44+ password : str ,
45+ websession : ClientSession = None ,
46+ useragent : Optional [str ] = None ,
4147 ) -> None :
4248 """Initialize."""
4349 self .__credentials = {"username" : username , "password" : password }
44- self ._myqrequests = MyQRequest (websession or ClientSession ())
50+ self ._myqrequests = MyQRequest (
51+ websession or ClientSession (), useragent = useragent
52+ )
4553 self ._authentication_task = None # type:Optional[asyncio.Task]
4654 self ._codeverifier = None # type: Optional[str]
4755 self ._invalid_credentials = False # type: bool
@@ -383,7 +391,6 @@ async def _oauth_authenticate(self) -> Tuple[str, int]:
383391 websession = session ,
384392 headers = {
385393 "Cookie" : resp .cookies .output (attrs = []),
386- "User-Agent" : "null" ,
387394 },
388395 allow_redirects = False ,
389396 login_request = True ,
@@ -400,7 +407,6 @@ async def _oauth_authenticate(self) -> Tuple[str, int]:
400407 websession = session ,
401408 headers = {
402409 "Content-Type" : "application/x-www-form-urlencoded" ,
403- "User-Agent" : "null" ,
404410 },
405411 data = {
406412 "client_id" : OAUTH_CLIENT_ID ,
@@ -565,8 +571,48 @@ async def update_device_info(self) -> None:
565571async def login (username : str , password : str , websession : ClientSession = None ) -> API :
566572 """Log in to the API."""
567573
574+ # Retrieve user agent from GitHub if not provided for login.
575+ _LOGGER .debug ("No user agent provided, trying to retrieve from GitHub." )
576+ url = f"https://raw.githubusercontent.com/arraylabs/pymyq/master/.USER_AGENT"
577+
578+ try :
579+ async with ClientSession () as session :
580+ async with session .get (url ) as resp :
581+ useragent = await resp .text ()
582+ resp .raise_for_status ()
583+ _LOGGER .debug (f"Retrieved user agent { useragent } from GitHub." )
584+
585+ except ClientError as exc :
586+ # Default user agent to random string with length of 5 if failure to retrieve it from GitHub.
587+ useragent = "#RANDOM:5"
588+ _LOGGER .warning (
589+ f"Failed retrieving user agent from GitHub, will use randomized user agent "
590+ f"instead: { str (exc )} "
591+ )
592+
593+ # Check if value for useragent is to create a random user agent.
594+ useragent_list = useragent .split (":" )
595+ if useragent_list [0 ] == "#RANDOM" :
596+ # Create a random string, check if length is provided for the random string, if not then default is 5.
597+ try :
598+ randomlength = int (useragent_list [1 ]) if len (useragent_list ) == 2 else 5
599+ except ValueError :
600+ _LOGGER .debug (
601+ f"Random length value { useragent_list [1 ]} in user agent { useragent } is not an integer. "
602+ f"Setting to 5 instead."
603+ )
604+ randomlength = 5
605+
606+ # Create the random user agent.
607+ useragent = "" .join (
608+ choices (string .ascii_letters + string .digits , k = randomlength )
609+ )
610+ _LOGGER .debug (f"User agent set to randomized value: { useragent } ." )
611+
568612 # Set the user agent in the headers.
569- api = API (username = username , password = password , websession = websession )
613+ api = API (
614+ username = username , password = password , websession = websession , useragent = useragent
615+ )
570616 _LOGGER .debug ("Performing initial authentication into MyQ" )
571617 try :
572618 await api .authenticate (wait = True )
0 commit comments