Skip to content

Commit d0c0e82

Browse files
authored
Merge branch 'dev' into dependabot/pip/pytest-cov-7.0.0
2 parents 42a0e65 + 3169dc1 commit d0c0e82

File tree

15 files changed

+309
-251
lines changed

15 files changed

+309
-251
lines changed

README.rst

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ The simplest way to use this package from a terminal is to call ``await Blink.st
5454
5555
async def start():
5656
blink = Blink(session=ClientSession())
57-
await blink.start()
57+
try:
58+
await blink.start()
59+
except BlinkTwoFARequiredError:
60+
await blink.prompt_2fa()
5861
return blink
5962
6063
blink = asyncio.run(start())
@@ -78,7 +81,10 @@ In some cases, having an interactive command-line session is not desired. In th
7881
# Can set no_prompt when initializing auth handler
7982
auth = Auth({"username": <your username>, "password": <your password>}, no_prompt=True)
8083
blink.auth = auth
81-
await blink.start()
84+
try:
85+
await blink.start()
86+
except BlinkTwoFARequiredError:
87+
await blink.prompt_2fa()
8288
return blink
8389
8490
blink = asyncio.run(start())
@@ -108,7 +114,10 @@ Other use cases may involved loading credentials from a file. This file must be
108114
blink = Blink()
109115
auth = Auth(await json_load("<File Location>"))
110116
blink.auth = auth
111-
await blink.start()
117+
try:
118+
await blink.start()
119+
except BlinkTwoFARequiredError:
120+
await blink.prompt_2fa()
112121
return blink
113122
114123
blink = asyncio.run(start())

blinkapp/blinkapp.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
from datetime import datetime, timedelta
66
from aiohttp import ClientSession
77
from blinkpy.blinkpy import Blink
8-
from blinkpy.auth import Auth
8+
from blinkpy.auth import Auth, BlinkTwoFARequiredError
99
from blinkpy.helpers.util import json_load
1010

1111
CREDFILE = environ.get("CREDFILE")
12-
TIMEDELTA = timedelta(environ.get("TIMEDELTA", "1"))
12+
TIMEDELTA = timedelta(int(environ.get("TIMEDELTA", "1")))
1313

1414

1515
def get_date():
@@ -26,7 +26,10 @@ async def start(session: ClientSession):
2626
"""Startup blink app."""
2727
blink = Blink(session=session)
2828
blink.auth = Auth(await json_load(CREDFILE), session=session)
29-
await blink.start()
29+
try:
30+
await blink.start()
31+
except BlinkTwoFARequiredError:
32+
await blink.prompt_2fa()
3033
return blink
3134

3235

blinkpy/api.py

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
Throttle,
1010
local_storage_clip_url_template,
1111
)
12-
from blinkpy.helpers.constants import DEFAULT_URL, TIMEOUT, DEFAULT_USER_AGENT
12+
from blinkpy.helpers.constants import (
13+
TIMEOUT,
14+
DEFAULT_USER_AGENT,
15+
OAUTH_CLIENT_ID,
16+
OAUTH_GRANT_TYPE_PASSWORD,
17+
OAUTH_GRANT_TYPE_REFRESH_TOKEN,
18+
OAUTH_SCOPE,
19+
)
20+
from urllib.parse import urlencode
1321

1422
_LOGGER = logging.getLogger(__name__)
1523

@@ -22,32 +30,44 @@ async def request_login(
2230
auth,
2331
url,
2432
login_data,
33+
is_refresh=False,
2534
is_retry=False,
2635
):
2736
"""
28-
Login request.
37+
OAuth login request.
2938
3039
:param auth: Auth instance.
3140
:param url: Login url.
3241
:param login_data: Dictionary containing blink login data.
3342
:param is_retry:
43+
:param two_fa_code: 2FA code if required
3444
"""
45+
3546
headers = {
36-
"Host": DEFAULT_URL,
37-
"Content-Type": "application/json",
38-
"user-agent": DEFAULT_USER_AGENT,
47+
"Content-Type": "application/x-www-form-urlencoded",
48+
"User-Agent": DEFAULT_USER_AGENT,
49+
"hardware_id": login_data.get("device_id", "Blinkpy"),
3950
}
4051

41-
data = dumps(
42-
{
43-
"email": login_data["username"],
44-
"password": login_data["password"],
45-
"unique_id": login_data["uid"],
46-
"device_identifier": login_data["device_id"],
47-
"client_name": "Computer",
48-
"reauth": True,
49-
}
50-
)
52+
# Add 2FA code to headers if provided
53+
if "2fa_code" in login_data:
54+
headers["2fa-code"] = login_data["2fa_code"]
55+
56+
# Prepare form data for OAuth
57+
form_data = {
58+
"username": login_data["username"],
59+
"client_id": OAUTH_CLIENT_ID,
60+
"scope": OAUTH_SCOPE,
61+
}
62+
63+
if is_refresh:
64+
form_data["grant_type"] = OAUTH_GRANT_TYPE_REFRESH_TOKEN
65+
form_data["refresh_token"] = auth.refresh_token
66+
else:
67+
form_data["grant_type"] = OAUTH_GRANT_TYPE_PASSWORD
68+
form_data["password"] = login_data["password"]
69+
70+
data = urlencode(form_data)
5171

5272
return await auth.query(
5373
url=url,
@@ -56,23 +76,23 @@ async def request_login(
5676
json_resp=False,
5777
reqtype="post",
5878
is_retry=is_retry,
79+
skip_refresh_check=True,
5980
)
6081

6182

62-
async def request_verify(auth, blink, verify_key):
63-
"""Send verification key to blink servers."""
64-
url = (
65-
f"{blink.urls.base_url}/api/v5/accounts/{blink.account_id}"
66-
f"/users/{blink.auth.user_id}"
67-
f"/clients/{blink.client_id}/client_verification/pin/verify"
68-
)
69-
data = dumps({"pin": verify_key})
83+
async def request_tier(auth, url):
84+
"""Get account tier information from blink servers."""
85+
headers = {
86+
"Content-Type": "application/x-www-form-urlencoded",
87+
"User-Agent": DEFAULT_USER_AGENT,
88+
"Authorization": f"Bearer {auth.token}",
89+
}
90+
7091
return await auth.query(
7192
url=url,
72-
headers=auth.header,
73-
data=data,
74-
json_resp=False,
75-
reqtype="post",
93+
headers=headers,
94+
json_resp=True,
95+
reqtype="get",
7696
)
7797

7898

@@ -214,7 +234,7 @@ async def request_command_status(blink, network, command_id):
214234
async def request_homescreen(blink, **kwargs):
215235
"""Request homescreen info."""
216236
url = f"{blink.urls.base_url}/api/v3/accounts/{blink.account_id}/homescreen"
217-
return await http_get(blink, url)
237+
return await http_get(blink, url, json=False)
218238

219239

220240
@Throttle(seconds=MIN_THROTTLE_TIME)

0 commit comments

Comments
 (0)