Skip to content

Commit 0901256

Browse files
committed
Merge branch 'master' of https://github.com/arraylabs/pymyq into account_class
2 parents 1b76bc5 + 1799f04 commit 0901256

File tree

5 files changed

+76
-16
lines changed

5 files changed

+76
-16
lines changed

pymyq/__version__.py

+1-1
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

+51-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from datetime import datetime, timedelta
66
from typing import Dict, List, 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
@@ -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:
565571
async 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)

pymyq/request.py

+14-5
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,

requirements_dev.txt

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
-i https://pypi.python.org/simple
2-
aiohttp>=3.7
3-
beautifulsoup4>=4.9.3
1+
-r requirements.txt
2+
pre-commit==2.10.1
43
black==20.8b1
5-
flake8>=3.8.4
6-
pkce>=1.0.2
4+
flake8==3.8.4
5+
isort==5.7.0
6+
pylint>=2.6.0
7+
setuptools>=53.0.0
78
twine>=3.3.0
89
wheel>=0.36.2

requirements_test.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-r requirements_dev.txt
2+
pytest>=6.2.2
3+
pytest-cov>=2.11.1
4+
pytest-timeout>=1.4.2

0 commit comments

Comments
 (0)