diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 0b39238f..28b54e70 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -23,4 +23,5 @@ If you're looking to add functionality to Home Assistant you will need to do the - https://www.teslaapi.io/ - https://tesla-api.timdorr.com/ 2. Build a proper abstraction inheriting from the [vehicle.py](teslajsonpy/vehicle.py). Check out [lock.py](teslajsonpy/lock.py). +3. Add abstraction to the controller [_add_components](https://github.com/zabuldon/teslajsonpy/blob/dev/teslajsonpy/controller.py#L530) so it will be discoverable. 3. Add changes to Home Assistant to access your abstraction and submit a PR per HA guidelines. diff --git a/README.md b/README.md index 33bfa62b..33ea077a 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ Also thanks to [Tim Dorr](https://tesla-api.timdorr.com/) for documenting the AP 11. Add yourself to `AUTHORS.md`. 12. Submit a [pull request](https://github.com/zabuldon/teslajsonpy/pulls)! +# Documentation +[API docs](https://teslajsonpy.readthedocs.io/en/latest/). + # License [Apache-2.0](LICENSE). By providing a contribution, you agree the contribution is licensed under Apache-2.0. diff --git a/poetry.lock b/poetry.lock index 06a29942..7476ec95 100644 --- a/poetry.lock +++ b/poetry.lock @@ -79,7 +79,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "authcaptureproxy" -version = "0.7.1" +version = "0.8.1" description = "A Python project to create a proxy to capture authentication information from a webpage. This is useful to capture oauth login details without access to a third-party oauth." category = "main" optional = false @@ -902,7 +902,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6.1" -content-hash = "513bb0ac499b37fc74ec071ab684b51ae9bf923ff75a665d7ce9386c3abc4bc0" +content-hash = "a68982256fa1f745e820452050efde37c3c2c3f3e26f597abfd9a8a05afe6b44" [metadata.files] aiohttp = [ @@ -969,8 +969,8 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] authcaptureproxy = [ - {file = "authcaptureproxy-0.7.1-py3-none-any.whl", hash = "sha256:eac7b62249e30596a931b4a5ebac7ac2cbc34027e3aba51344768181bf90a5fb"}, - {file = "authcaptureproxy-0.7.1.tar.gz", hash = "sha256:252de42cf864eb35aac5d3b20c12ad79c3f5c329e52b70b101eaceeb55b91e8b"}, + {file = "authcaptureproxy-0.8.1-py3-none-any.whl", hash = "sha256:49c049cd19652fb67011ea84b20721748ddbd0621a7cbe1fb17c90a9f7e095a6"}, + {file = "authcaptureproxy-0.8.1.tar.gz", hash = "sha256:7326ff1bf7382447d897731d991f7f03d84c3a7307fe941bfcea0b2d536ac21e"}, ] autoapi = [ {file = "autoapi-2.0.1.tar.gz", hash = "sha256:4003b17599020652d0738dc1c426d0abf2f58f8a1821f5500816043210b3d1d6"}, diff --git a/pyproject.toml b/pyproject.toml index cf762cef..099002d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,25 @@ description = "A library to work with Tesla API." authors = ["Sergey Isachenko "] license = "Apache-2.0" repository = "https://github.com/zabuldon/teslajsonpy" +readme = "README.md" +homepage = "https://github.com/zabuldon/teslajsonpy" +documentation = "https://teslajsonpy.readthedocs.io" +classifiers = [ + "Development Status :: 3 - Alpha", + "Natural Language :: English", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Environment :: Console", + "Topic :: Software Development :: Libraries :: Python Modules" +] +include = [ + "README.md", + "LICENSE", + "CHANGELOG.md" +] [tool.poetry.dependencies] python = "^3.6.1" @@ -12,7 +31,7 @@ aiohttp = "^3.7.4" backoff = "^1.10.0" beautifulsoup4 = "^4.9.3" wrapt = "^1.12.1" -authcaptureproxy = "~=0.7.1" +authcaptureproxy = "~=0.8.1" [tool.poetry.dev-dependencies] flake8 = "^3.9.0" @@ -35,3 +54,7 @@ pylint = "^2.7.3" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[metadata] +description-file = "README.md" + diff --git a/teslajsonpy/const.py b/teslajsonpy/const.py index 6fdb9445..f1915bb0 100644 --- a/teslajsonpy/const.py +++ b/teslajsonpy/const.py @@ -11,6 +11,6 @@ DRIVING_INTERVAL = 60 # interval when driving detected WEBSOCKET_TIMEOUT = 11 # time for websocket to timeout RELEASE_NOTES_URL = "https://teslascope.com/teslapedia/software/" -AUTH_DOMAIN = "https://auth.tesla.cn" +AUTH_DOMAIN = "https://auth.tesla.com" API_URL = "https://owner-api.teslamotors.com" WS_URL = "wss://streaming.vn.teslamotors.com/streaming" diff --git a/teslajsonpy/controller.py b/teslajsonpy/controller.py index 42bd296e..fde21e75 100644 --- a/teslajsonpy/controller.py +++ b/teslajsonpy/controller.py @@ -36,10 +36,10 @@ from teslajsonpy.homeassistant.charger import ChargerSwitch, ChargingSensor, RangeSwitch from teslajsonpy.homeassistant.climate import Climate, TempSensor from teslajsonpy.homeassistant.gps import GPS, Odometer +from teslajsonpy.homeassistant.heated_seats import HeatedSeatSwitch from teslajsonpy.homeassistant.lock import ChargerLock, Lock from teslajsonpy.homeassistant.sentry_mode import SentryModeSwitch from teslajsonpy.homeassistant.trunk import FrunkLock, TrunkLock -from teslajsonpy.homeassistant.heated_seats import HeatedSeatSwitch _LOGGER = logging.getLogger(__name__) @@ -546,11 +546,11 @@ def _add_components(self, car): self.__components.append(TrunkLock(car, self)) self.__components.append(FrunkLock(car, self)) self.__components.append(UpdateSensor(car, self)) - self.__components.append(HeatedSeatSwitch(car, self, 'left')) - self.__components.append(HeatedSeatSwitch(car, self, 'right')) - self.__components.append(HeatedSeatSwitch(car, self, 'rear_left')) - self.__components.append(HeatedSeatSwitch(car, self, 'rear_center')) - self.__components.append(HeatedSeatSwitch(car, self, 'rear_right')) + for seat in ['left', 'right', 'rear_left', 'rear_center', 'rear_right']: + try: + self.__components.append(HeatedSeatSwitch(car, self, seat)) + except KeyError: + _LOGGER.debug("Seat warmer %s not detected", seat) async def _wake_up(self, car_id): car_vin = self._id_to_vin(car_id) diff --git a/teslajsonpy/teslaproxy.py b/teslajsonpy/teslaproxy.py index 74cbf189..94e86142 100644 --- a/teslajsonpy/teslaproxy.py +++ b/teslajsonpy/teslaproxy.py @@ -10,15 +10,16 @@ """ from functools import partial import logging -from typing import Any, Dict, Text - import random +from typing import Any, Dict, Optional + from aiohttp import ClientResponse, web from authcaptureproxy import AuthCaptureProxy, return_timer_countdown_refresh_html +from authcaptureproxy.const import SKIP_AUTO_HEADERS from authcaptureproxy.examples.modifiers import find_regex_urls from authcaptureproxy.helper import prepend_url -from yarl import URL import multidict +from yarl import URL _LOGGER = logging.getLogger(__name__) @@ -35,11 +36,11 @@ def __init__(self, proxy_url: URL, host_url: URL) -> None: """ super().__init__(URL(proxy_url), URL(host_url)) - self._config_flow_id = None - self._callback_url = None - self.waf_retry = 0 - self.waf_limit = 30 - self.tests = {"test_url": self.test_url} + self._config_flow_id: Optional[str] = None + self._callback_url: Optional[str] = None + self.waf_retry: int = 0 + self.waf_limit: int = 30 + self.tests: Dict[str, str] = {"test_url": self.test_url} self.headers = { "x-tesla-user-agent": "TeslaApp/3.10.9-433/adff2e065/android/10", @@ -58,12 +59,13 @@ def __init__(self, proxy_url: URL, host_url: URL) -> None: }, } ) + self.redirect_filters = {"url": ["^.*/static/404.html$"]} async def test_url( self, resp: ClientResponse, - data: Dict[Text, Any], - query: Dict[Text, Any], # pylint: disable=unused-argument + data: Dict[str, Any], + query: Dict[str, Any], # pylint: disable=unused-argument ): """Test for a successful Tesla URL. @@ -71,14 +73,14 @@ async def test_url( Args: resp (ClientResponse): The aiohttp response. - data (Dict[Text, Any]): Dictionary of all post data captured through proxy with overwrites for duplicate keys. - query (Dict[Text, Any]): Dictionary of all query data with overwrites for duplicate keys. + data (Dict[str, Any]): Dictionary of all post data captured through proxy with overwrites for duplicate keys. + query (Dict[str, Any]): Dictionary of all query data with overwrites for duplicate keys. Returns - Optional[Union[URL, Text]]: URL for a http 302 redirect or Text to display on success. None indicates test did not pass. + Optional[Union[URL, str]]: URL for a http 302 redirect or str to display on success. None indicates test did not pass. """ - code: Text = "" + code: str = "" if resp.url.path == "/void/callback": code = resp.url.query.get("code") if resp.url.path == "/static/404.html": @@ -109,17 +111,17 @@ async def test_url( text = await resp.json() _LOGGER.debug("Json response: %s", text) - async def prepend_relative_urls(self, base_url: URL, html: Text) -> Text: + async def prepend_relative_urls(self, base_url: URL, html: str) -> str: """Prepend relative urls with url host. This is intended to be used for to place the proxy_url in front of relative urls in src="/ Args: base_url (URL): Base URL to prepend - html (Text): Text to replace + html (str): text to replace Returns - Text: Replaced text + str: Replaced text """ if not base_url: @@ -142,17 +144,17 @@ async def reset_data(self) -> None: self.waf_retry = 0 await super().reset_data() - async def prepend_i18n_path(self, base_url: URL, html: Text) -> Text: + async def prepend_i18n_path(self, base_url: URL, html: str) -> str: """Prepend path for i18n loadPath so it'll reach the proxy. This is intended to be used for to place the proxy_url path in front of relative urls for loadPath in i18next. Args: base_url (URL): Base URL to prepend - html (Text): Text to replace + html (str): text to replace Returns - Text: Replaced text + str: Replaced text """ if not base_url: @@ -161,9 +163,7 @@ async def prepend_i18n_path(self, base_url: URL, html: Text) -> Text: return await find_regex_urls( partial(prepend_url, base_url, encoded=True), - { - "method_func": r"""(?:loadPath:)\s*?["']([^"']*)[\"\']""", - }, + {"method_func": r"""(?:loadPath:)\s*?["']([^"']*)[\"\']"""}, html=html, ) @@ -172,6 +172,11 @@ async def modify_headers( ) -> multidict.MultiDict: """Modify headers. + Return modified headers based on site and request. To disable auto header generation, + pass in a key const.SKIP_AUTO_HEADERS with a list of keys to not generate. + + For example, to prevent User-Agent generation: {SKIP_AUTO_HEADERS : ["User-Agent"]} + Args: site (URL): URL of the next host request. request (web.Request): Proxy directed request. This will need to be changed for the actual host request. @@ -182,6 +187,7 @@ async def modify_headers( """ result = await super().modify_headers(site, request) method = request.method + result.update({SKIP_AUTO_HEADERS: ["User-Agent"]}) if ( str(site.path) == "/oauth2/v3/authorize/mfa/verify" and method == "POST"