mirror of
https://github.com/USA-RedDragon/badnest.git
synced 2025-01-31 10:40:26 +00:00
Added Code for Google Auth Login
This commit is contained in:
parent
c686190a24
commit
5d74119c38
1
.gitignore
vendored
1
.gitignore
vendored
@ -90,3 +90,4 @@ ENV/
|
|||||||
.ropeproject
|
.ropeproject
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
@ -3,14 +3,19 @@ import voluptuous as vol
|
|||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, CONF_ISSUE_TOKEN, CONF_COOKIE, CONF_APIKEY
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.All(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_EMAIL): cv.string,
|
vol.Required(CONF_EMAIL, default=""): cv.string,
|
||||||
vol.Required(CONF_PASSWORD): cv.string,
|
vol.Required(CONF_PASSWORD, default=""): cv.string,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vol.Required(CONF_ISSUE_TOKEN, default=""): cv.string,
|
||||||
|
vol.Required(CONF_COOKIE, default=""): cv.string,
|
||||||
|
vol.Required(CONF_APIKEY, default=""): cv.string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -23,10 +28,22 @@ def setup(hass, config):
|
|||||||
if config.get(DOMAIN) is not None:
|
if config.get(DOMAIN) is not None:
|
||||||
email = config[DOMAIN].get(CONF_EMAIL)
|
email = config[DOMAIN].get(CONF_EMAIL)
|
||||||
password = config[DOMAIN].get(CONF_PASSWORD)
|
password = config[DOMAIN].get(CONF_PASSWORD)
|
||||||
|
issue_token = config[DOMAIN].get(CONF_ISSUE_TOKEN)
|
||||||
|
cookie = config[DOMAIN].get(CONF_COOKIE)
|
||||||
|
api_key = config[DOMAIN].get(CONF_APIKEY)
|
||||||
else:
|
else:
|
||||||
email = None
|
email = None
|
||||||
password = None
|
password = None
|
||||||
|
issue_token = None
|
||||||
|
cookie = None
|
||||||
|
api_key = None
|
||||||
|
|
||||||
hass.data[DOMAIN] = {CONF_EMAIL: email, CONF_PASSWORD: password}
|
hass.data[DOMAIN] = {
|
||||||
|
CONF_EMAIL: email,
|
||||||
|
CONF_PASSWORD: password,
|
||||||
|
CONF_ISSUE_TOKEN: issue_token,
|
||||||
|
CONF_COOKIE: cookie,
|
||||||
|
CONF_APIKEY: api_key
|
||||||
|
}
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -3,32 +3,71 @@ import requests
|
|||||||
API_URL = "https://home.nest.com"
|
API_URL = "https://home.nest.com"
|
||||||
CAMERA_WEBAPI_BASE = "https://webapi.camera.home.nest.com"
|
CAMERA_WEBAPI_BASE = "https://webapi.camera.home.nest.com"
|
||||||
CAMERA_URL = "https://nexusapi-us1.camera.home.nest.com"
|
CAMERA_URL = "https://nexusapi-us1.camera.home.nest.com"
|
||||||
|
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) " \
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) " \
|
||||||
|
"Chrome/75.0.3770.100 Safari/537.36"
|
||||||
|
URL_JWT = "https://nestauthproxyservice-pa.googleapis.com/v1/issue_jwt"
|
||||||
|
|
||||||
|
|
||||||
class NestAPI:
|
class NestAPI:
|
||||||
def __init__(self, email, password):
|
def __init__(self, email, password, issue_token, cookie, api_key):
|
||||||
self._user_id = None
|
self._user_id = None
|
||||||
self._access_token = None
|
self._access_token = None
|
||||||
self._session = requests.Session()
|
self._session = requests.Session()
|
||||||
self._session.headers.update({"Referer": "https://home.nest.com/"})
|
self._session.headers.update({"Referer": "https://home.nest.com/"})
|
||||||
self._device_id = None
|
self._device_id = None
|
||||||
self._login(email, password)
|
if email is not None and password is not None:
|
||||||
|
self._login_nest(email, password)
|
||||||
|
else:
|
||||||
|
self._login_google(issue_token, cookie, api_key)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def _login(self, email, password):
|
def _login_nest(self, email, password):
|
||||||
r = self._session.post(
|
r = self._session.post(
|
||||||
f"{API_URL}/session", json={"email": email, "password": password}
|
f"{API_URL}/session", json={"email": email, "password": password}
|
||||||
)
|
)
|
||||||
self._user_id = r.json()["userid"]
|
self._user_id = r.json()["userid"]
|
||||||
self._access_token = r.json()["access_token"]
|
self._access_token = r.json()["access_token"]
|
||||||
|
|
||||||
|
def _login_google(self, issue_token, cookie, api_key):
|
||||||
|
headers = {
|
||||||
|
'Sec-Fetch-Mode': 'cors',
|
||||||
|
'User-Agent': USER_AGENT,
|
||||||
|
'X-Requested-With': 'XmlHttpRequest',
|
||||||
|
'Referer': 'https://accounts.google.com/o/oauth2/iframe',
|
||||||
|
'cookie': cookie
|
||||||
|
}
|
||||||
|
r = requests.get(url=issue_token, headers=headers)
|
||||||
|
access_token = r.json()['access_token']
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + access_token,
|
||||||
|
'User-Agent': USER_AGENT,
|
||||||
|
'x-goog-api-key': api_key,
|
||||||
|
'Referer': 'https://home.nest.com'
|
||||||
|
}
|
||||||
|
params = {
|
||||||
|
"embed_google_oauth_access_token": True,
|
||||||
|
"expire_after": '3600s',
|
||||||
|
"google_oauth_access_token": access_token,
|
||||||
|
"policy_id": 'authproxy-oauth-policy'
|
||||||
|
}
|
||||||
|
r = requests.post(url=URL_JWT, headers=headers, params=params)
|
||||||
|
self._user_id = r.json()['claims']['subject']['nestId']['id']
|
||||||
|
self._access_token = r.json()['jwt']
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class NestThermostatAPI(NestAPI):
|
class NestThermostatAPI(NestAPI):
|
||||||
def __init__(self, email, password):
|
def __init__(self, email, password, issue_token, cookie, api_key):
|
||||||
super(NestThermostatAPI, self).__init__(email, password)
|
super(NestThermostatAPI, self).__init__(
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
issue_token,
|
||||||
|
cookie,
|
||||||
|
api_key)
|
||||||
self._shared_id = None
|
self._shared_id = None
|
||||||
self._czfe_url = None
|
self._czfe_url = None
|
||||||
self._compressor_lockout_enabled = None
|
self._compressor_lockout_enabled = None
|
||||||
@ -182,8 +221,13 @@ class NestThermostatAPI(NestAPI):
|
|||||||
|
|
||||||
|
|
||||||
class NestCameraAPI(NestAPI):
|
class NestCameraAPI(NestAPI):
|
||||||
def __init__(self, email, password):
|
def __init__(self, email, password, issue_token, cookie, api_key):
|
||||||
super(NestCameraAPI, self).__init__(email, password)
|
super(NestCameraAPI, self).__init__(
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
issue_token,
|
||||||
|
cookie,
|
||||||
|
api_key)
|
||||||
# log into dropcam
|
# log into dropcam
|
||||||
self._session.post(
|
self._session.post(
|
||||||
f"{API_URL}/dropcam/api/login",
|
f"{API_URL}/dropcam/api/login",
|
||||||
|
@ -7,7 +7,7 @@ from homeassistant.components.camera import Camera, SUPPORT_ON_OFF
|
|||||||
|
|
||||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||||
from .api import NestCameraAPI
|
from .api import NestCameraAPI
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, CONF_ISSUE_TOKEN, CONF_COOKIE, CONF_APIKEY
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -25,7 +25,10 @@ async def async_setup_platform(hass,
|
|||||||
|
|
||||||
api = NestCameraAPI(
|
api = NestCameraAPI(
|
||||||
hass.data[DOMAIN][CONF_EMAIL],
|
hass.data[DOMAIN][CONF_EMAIL],
|
||||||
hass.data[DOMAIN][CONF_PASSWORD]
|
hass.data[DOMAIN][CONF_PASSWORD],
|
||||||
|
hass.data[DOMAIN][CONF_ISSUE_TOKEN],
|
||||||
|
hass.data[DOMAIN][CONF_COOKIE],
|
||||||
|
hass.data[DOMAIN][CONF_APIKEY]
|
||||||
)
|
)
|
||||||
|
|
||||||
# cameras = await hass.async_add_executor_job(nest.get_cameras())
|
# cameras = await hass.async_add_executor_job(nest.get_cameras())
|
||||||
|
@ -29,7 +29,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .api import NestThermostatAPI
|
from .api import NestThermostatAPI
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, CONF_ISSUE_TOKEN, CONF_COOKIE, CONF_APIKEY
|
||||||
|
|
||||||
NEST_MODE_HEAT_COOL = "range"
|
NEST_MODE_HEAT_COOL = "range"
|
||||||
NEST_MODE_ECO = "eco"
|
NEST_MODE_ECO = "eco"
|
||||||
@ -64,7 +64,10 @@ async def async_setup_platform(hass,
|
|||||||
"""Set up the Nest climate device."""
|
"""Set up the Nest climate device."""
|
||||||
nest = NestThermostatAPI(
|
nest = NestThermostatAPI(
|
||||||
hass.data[DOMAIN][CONF_EMAIL],
|
hass.data[DOMAIN][CONF_EMAIL],
|
||||||
hass.data[DOMAIN][CONF_PASSWORD]
|
hass.data[DOMAIN][CONF_PASSWORD],
|
||||||
|
hass.data[DOMAIN][CONF_ISSUE_TOKEN],
|
||||||
|
hass.data[DOMAIN][CONF_COOKIE],
|
||||||
|
hass.data[DOMAIN][CONF_APIKEY]
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities([NestClimate(nest)])
|
async_add_entities([NestClimate(nest)])
|
||||||
|
@ -1 +1,4 @@
|
|||||||
DOMAIN = 'badnest'
|
DOMAIN = 'badnest'
|
||||||
|
CONF_ISSUE_TOKEN = 'issue_token'
|
||||||
|
CONF_COOKIE = 'cookie'
|
||||||
|
CONF_APIKEY = 'api_key'
|
||||||
|
37
info.md
37
info.md
@ -13,20 +13,53 @@ This isn't an advertised or public API, it's still better than web scraping, but
|
|||||||
- Tested with a single thermostat, I have no other devices to test with
|
- Tested with a single thermostat, I have no other devices to test with
|
||||||
- Camera integration is untested by me
|
- Camera integration is untested by me
|
||||||
- Nest could change their webapp api at any time, making this defunct
|
- Nest could change their webapp api at any time, making this defunct
|
||||||
- Won't work with Google-linked accounts
|
|
||||||
- Presets don't work (Eco, Away)
|
- Presets don't work (Eco, Away)
|
||||||
|
|
||||||
## Example configuration.yaml
|
## Example configuration.yaml - When you're not using the Google Auth Login
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
badnest:
|
badnest:
|
||||||
email: email@domain.com
|
email: email@domain.com
|
||||||
password: !secret nest_password
|
password: !secret nest_password
|
||||||
|
|
||||||
|
climate:
|
||||||
|
- platform: badnest
|
||||||
|
scan_interval: 10
|
||||||
|
|
||||||
camera:
|
camera:
|
||||||
- platform: badnest
|
- platform: badnest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example configuration.yaml - When you are using the Google Auth Login
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
badnest:
|
||||||
|
issue_token: "https://accounts.google.com/o/oauth2/iframerpc....."
|
||||||
|
cookie: "OCAK=......"
|
||||||
|
api_key : "#YOURAPIKEYHERE#"
|
||||||
|
|
||||||
climate:
|
climate:
|
||||||
- platform: badnest
|
- platform: badnest
|
||||||
scan_interval: 10
|
scan_interval: 10
|
||||||
|
|
||||||
|
camera:
|
||||||
|
- platform: badnest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Google Login support added with many thanks to: chrisjshull from <https://github.com/chrisjshull/homebridge-nest/>
|
||||||
|
|
||||||
|
The values of `"issue_token"`, `"cookie"` and `"api_key"` are specific to your Google Account. To get them, follow these steps (only needs to be done once, as long as you stay logged into your Google Account).
|
||||||
|
|
||||||
|
1. Open a Chrome browser tab in Incognito Mode (or clear your cache).
|
||||||
|
2. Open Developer Tools (View/Developer/Developer Tools).
|
||||||
|
3. Click on 'Network' tab. Make sure 'Preserve Log' is checked.
|
||||||
|
4. In the 'Filter' box, enter `issueToken`
|
||||||
|
5. Go to `home.nest.com`, and click 'Sign in with Google'. Log into your account.
|
||||||
|
6. One network call (beginning with `iframerpc`) will appear in the Dev Tools window. Click on it.
|
||||||
|
7. In the Headers tab, under General, copy the entire `Request URL` (beginning with `https://accounts.google.com`, ending with `nest.com`). This is your `"issue_token"` in `configuration.yaml`.
|
||||||
|
8. In the 'Filter' box, enter `oauth2/iframe`
|
||||||
|
9. Several network calls will appear in the Dev Tools window. Click on the last `iframe` call.
|
||||||
|
10. In the Headers tab, under Request Headers, copy the entire `cookie` (beginning `OCAK=...` - **include the whole string which is several lines long and has many field/value pairs** - do not include the `cookie:` name). This is your `"cookie"` in `configuration.yaml`.
|
||||||
|
11. In the 'Filter' box, enter `issue_jwt`
|
||||||
|
12. Click on the last `issue_jwt` call.
|
||||||
|
13. In the Headers tab, under Request Headers, copy the entire `x-goog-api-key` (do not include the `x-goog-api-key:` name). This is your `"api_key"` in `configuration.yaml`.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user