Skip to content

Commit 1799f04

Browse files
committed
Fix User Agent issue again
1 parent f753cd8 commit 1799f04

File tree

3 files changed

+66
-11
lines changed

3 files changed

+66
-11
lines changed

pymyq/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""Define a version constant."""
2-
__version__ = '3.0.3'
2+
__version__ = "3.0.4"

pymyq/api.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from datetime import datetime, timedelta
66
from typing import Dict, Optional, Union, Tuple
77
from urllib.parse import urlsplit, parse_qs
8+
from random import choices
9+
import string
810

911
from aiohttp import ClientSession, ClientResponse
1012
from 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:
641647
async 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)

pymyq/request.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919
class MyQRequest: # pylint: disable=too-many-instance-attributes
2020
"""Define a class to handle requests to MyQ"""
2121

22-
def __init__(self, websession: ClientSession = None) -> None:
22+
def __init__(self, websession: ClientSession = None, useragent: str = None) -> None:
2323
self._websession = websession or ClientSession()
24+
self._useragent = useragent
2425

25-
@staticmethod
2626
async def _send_request(
27+
self,
2728
method: str,
2829
url: str,
2930
websession: ClientSession,
@@ -38,16 +39,24 @@ async def _send_request(
3839
resp_exc = None
3940
last_status = ""
4041
last_error = ""
42+
43+
if self._useragent is not None:
44+
headers.update({"User-Agent": self._useragent})
45+
4146
while attempt < DEFAULT_REQUEST_RETRIES:
4247
if attempt != 0:
4348
wait_for = min(2 ** attempt, 5)
44-
_LOGGER.debug(f'Request failed with "{last_status} {last_error}" '
45-
f'(attempt #{attempt}/{DEFAULT_REQUEST_RETRIES})"; trying again in {wait_for} seconds')
49+
_LOGGER.debug(
50+
f'Request failed with "{last_status} {last_error}" '
51+
f'(attempt #{attempt}/{DEFAULT_REQUEST_RETRIES})"; trying again in {wait_for} seconds'
52+
)
4653
await asyncio.sleep(wait_for)
4754

4855
attempt += 1
4956
try:
50-
_LOGGER.debug(f"Sending myq api request {url} and headers {headers} with connection pooling")
57+
_LOGGER.debug(
58+
f"Sending myq api request {url} and headers {headers} with connection pooling"
59+
)
5160
resp = await websession.request(
5261
method,
5362
url,

0 commit comments

Comments
 (0)