55from datetime import datetime , timedelta
66from typing import Dict , 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
@@ -39,11 +41,17 @@ class API: # pylint: disable=too-many-instance-attributes
3941 """Define a class for interacting with the MyQ iOS App API."""
4042
4143 def __init__ (
42- self , username : str , password : str , websession : ClientSession = None
44+ self ,
45+ username : str ,
46+ password : str ,
47+ websession : ClientSession = None ,
48+ useragent : Optional [str ] = None ,
4349 ) -> None :
4450 """Initialize."""
4551 self .__credentials = {"username" : username , "password" : password }
46- self ._myqrequests = MyQRequest (websession or ClientSession ())
52+ self ._myqrequests = MyQRequest (
53+ websession or ClientSession (), useragent = useragent
54+ )
4755 self ._authentication_task = None # type:Optional[asyncio.Task]
4856 self ._codeverifier = None # type: Optional[str]
4957 self ._invalid_credentials = False # type: bool
@@ -381,7 +389,6 @@ async def _oauth_authenticate(self) -> Tuple[str, int]:
381389 websession = session ,
382390 headers = {
383391 "Cookie" : resp .cookies .output (attrs = []),
384- "User-Agent" : "null" ,
385392 },
386393 allow_redirects = False ,
387394 login_request = True ,
@@ -398,7 +405,6 @@ async def _oauth_authenticate(self) -> Tuple[str, int]:
398405 websession = session ,
399406 headers = {
400407 "Content-Type" : "application/x-www-form-urlencoded" ,
401- "User-Agent" : "null" ,
402408 },
403409 data = {
404410 "client_id" : OAUTH_CLIENT_ID ,
@@ -641,8 +647,48 @@ async def update_device_info(self, for_account: str = None) -> None:
641647async def login (username : str , password : str , websession : ClientSession = None ) -> API :
642648 """Log in to the API."""
643649
650+ # Retrieve user agent from GitHub if not provided for login.
651+ _LOGGER .debug ("No user agent provided, trying to retrieve from GitHub." )
652+ url = f"https://raw.githubusercontent.com/arraylabs/pymyq/master/.USER_AGENT"
653+
654+ try :
655+ async with ClientSession () as session :
656+ async with session .get (url ) as resp :
657+ useragent = await resp .text ()
658+ resp .raise_for_status ()
659+ _LOGGER .debug (f"Retrieved user agent { useragent } from GitHub." )
660+
661+ except ClientError as exc :
662+ # Default user agent to random string with length of 5 if failure to retrieve it from GitHub.
663+ useragent = "#RANDOM:5"
664+ _LOGGER .warning (
665+ f"Failed retrieving user agent from GitHub, will use randomized user agent "
666+ f"instead: { str (exc )} "
667+ )
668+
669+ # Check if value for useragent is to create a random user agent.
670+ useragent_list = useragent .split (":" )
671+ if useragent_list [0 ] == "#RANDOM" :
672+ # Create a random string, check if length is provided for the random string, if not then default is 5.
673+ try :
674+ randomlength = int (useragent_list [1 ]) if len (useragent_list ) == 2 else 5
675+ except ValueError :
676+ _LOGGER .debug (
677+ f"Random length value { useragent_list [1 ]} in user agent { useragent } is not an integer. "
678+ f"Setting to 5 instead."
679+ )
680+ randomlength = 5
681+
682+ # Create the random user agent.
683+ useragent = "" .join (
684+ choices (string .ascii_letters + string .digits , k = randomlength )
685+ )
686+ _LOGGER .debug (f"User agent set to randomized value: { useragent } ." )
687+
644688 # Set the user agent in the headers.
645- api = API (username = username , password = password , websession = websession )
689+ api = API (
690+ username = username , password = password , websession = websession , useragent = useragent
691+ )
646692 _LOGGER .debug ("Performing initial authentication into MyQ" )
647693 try :
648694 await api .authenticate (wait = True )
0 commit comments