5
5
from datetime import datetime , timedelta
6
6
from typing import Dict , Optional , Union , Tuple
7
7
from urllib .parse import urlsplit , parse_qs
8
+ from random import choices
9
+ import string
8
10
9
11
from aiohttp import ClientSession , ClientResponse
10
12
from aiohttp .client_exceptions import ClientError , ClientResponseError
@@ -39,11 +41,17 @@ class API: # pylint: disable=too-many-instance-attributes
39
41
"""Define a class for interacting with the MyQ iOS App API."""
40
42
41
43
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 ,
43
49
) -> None :
44
50
"""Initialize."""
45
51
self .__credentials = {"username" : username , "password" : password }
46
- self ._myqrequests = MyQRequest (websession or ClientSession ())
52
+ self ._myqrequests = MyQRequest (
53
+ websession or ClientSession (), useragent = useragent
54
+ )
47
55
self ._authentication_task = None # type:Optional[asyncio.Task]
48
56
self ._codeverifier = None # type: Optional[str]
49
57
self ._invalid_credentials = False # type: bool
@@ -381,7 +389,6 @@ async def _oauth_authenticate(self) -> Tuple[str, int]:
381
389
websession = session ,
382
390
headers = {
383
391
"Cookie" : resp .cookies .output (attrs = []),
384
- "User-Agent" : "null" ,
385
392
},
386
393
allow_redirects = False ,
387
394
login_request = True ,
@@ -398,7 +405,6 @@ async def _oauth_authenticate(self) -> Tuple[str, int]:
398
405
websession = session ,
399
406
headers = {
400
407
"Content-Type" : "application/x-www-form-urlencoded" ,
401
- "User-Agent" : "null" ,
402
408
},
403
409
data = {
404
410
"client_id" : OAUTH_CLIENT_ID ,
@@ -641,8 +647,48 @@ async def update_device_info(self, for_account: str = None) -> None:
641
647
async def login (username : str , password : str , websession : ClientSession = None ) -> API :
642
648
"""Log in to the API."""
643
649
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
+
644
688
# 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
+ )
646
692
_LOGGER .debug ("Performing initial authentication into MyQ" )
647
693
try :
648
694
await api .authenticate (wait = True )
0 commit comments