-
Notifications
You must be signed in to change notification settings - Fork 7
feat: Add INTL region support for Hello Smart International app #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a7a4507
25d1a3f
884a0a0
a6136c5
ae14017
bbbf07b
1fd8c5e
5af89f8
5bbcfc9
5b19b62
379e42f
ddfaf5a
9ce1f33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,3 +74,6 @@ venv.bak/ | |
|
|
||
| # Generated response files | ||
| response-*.json | ||
|
|
||
| .copilot* | ||
| external | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -7,10 +7,10 @@ | |||||
| from typing import Optional | ||||||
|
|
||||||
| from pysmarthashtag.api import utils | ||||||
| from pysmarthashtag.api.authentication import SmartAuthentication | ||||||
| from pysmarthashtag.api.authentication import SmartAuthentication, SmartAuthenticationINTL | ||||||
| from pysmarthashtag.api.client import SmartClient, SmartClientConfiguration | ||||||
| from pysmarthashtag.api.log_sanitizer import sanitize_log_data | ||||||
| from pysmarthashtag.const import API_CARS_URL, API_SELECT_CAR_URL, EndpointUrls | ||||||
| from pysmarthashtag.const import API_CARS_URL, API_SELECT_CAR_URL, EndpointUrls, SmartRegion | ||||||
| from pysmarthashtag.models import SmartAuthError, SmartHumanCarConnectionError, SmartTokenRefreshNecessary | ||||||
| from pysmarthashtag.vehicle.vehicle import SmartVehicle | ||||||
|
|
||||||
|
|
@@ -38,21 +38,72 @@ class SmartAccount: | |||||
| endpoint_urls: Optional[EndpointUrls] = None | ||||||
| """Optional. Custom endpoint URLs for international API support.""" | ||||||
|
|
||||||
| region: Optional[SmartRegion] = None | ||||||
| """Optional. Region preset (EU or INTL). If set, overrides endpoint_urls.""" | ||||||
|
|
||||||
| vehicles: dict[str, SmartVehicle] = field(default_factory=dict, init=False) | ||||||
| """Vehicles associated with the account.""" | ||||||
|
|
||||||
| def __post_init__(self, password, log_responses): | ||||||
| """Initialize the account.""" | ||||||
| # Ensure endpoint_urls is set | ||||||
| if self.endpoint_urls is None: | ||||||
| # Import get_endpoint_urls_for_region here to avoid circular imports | ||||||
| from pysmarthashtag.const import get_endpoint_urls_for_region | ||||||
|
|
||||||
| # If region is specified, use it to get endpoint URLs | ||||||
| if self.region is not None: | ||||||
| self.endpoint_urls = get_endpoint_urls_for_region(self.region) | ||||||
| elif self.endpoint_urls is None: | ||||||
| self.endpoint_urls = EndpointUrls() | ||||||
|
|
||||||
| # Choose authentication class based on region | ||||||
| if self.region == SmartRegion.INTL: | ||||||
| _LOGGER.info("Using INTL (International) authentication for region: %s", self.region) | ||||||
| auth = SmartAuthenticationINTL(self.username, password, endpoint_urls=self.endpoint_urls) | ||||||
| else: | ||||||
| _LOGGER.info("Using EU authentication (default)") | ||||||
| auth = SmartAuthentication(self.username, password, endpoint_urls=self.endpoint_urls) | ||||||
|
|
||||||
| if self.config is None: | ||||||
| self.config = SmartClientConfiguration( | ||||||
| SmartAuthentication(self.username, password, endpoint_urls=self.endpoint_urls), | ||||||
| auth, | ||||||
| log_responses=log_responses, | ||||||
| ) | ||||||
|
|
||||||
| def _is_intl_region(self) -> bool: | ||||||
| """Check if using INTL (International) region.""" | ||||||
| return self.region == SmartRegion.INTL | ||||||
|
|
||||||
| def _generate_api_headers(self, params: dict, method: str, url: str, body=None) -> dict[str, str]: | ||||||
| """Generate API headers based on region (EU or INTL). | ||||||
|
|
||||||
| This method automatically selects the correct header generator | ||||||
| based on whether the account is configured for INTL region. | ||||||
| """ | ||||||
| auth = self.config.authentication | ||||||
|
|
||||||
| if self._is_intl_region(): | ||||||
| # INTL region uses different headers and signing | ||||||
| client_id = getattr(auth, "api_client_id", None) | ||||||
| return utils.generate_intl_header( | ||||||
| device_id=auth.device_id, | ||||||
|
||||||
| device_id=auth.device_id, | |
| device_id=getattr(auth, "device_identifier", auth.device_id), |
Copilot
AI
Feb 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The request URL is built with URL-encoded params (url_params), but the signature/header generation is passed the unencoded params dict. For EU requests, _create_sign() does not URL-encode params, so the signature payload can diverge from the actual request URL (notably target=basic%2Cmore). Use a single canonical params representation for both the URL and signature (e.g., pass url_params into header generation too, or centralize encoding inside _create_sign() for all regions).
| params=params, | |
| params=url_params, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a per-file Ruff ignore for
C901onbattery.pyhides an increasing complexity issue in parsing logic. Prefer refactoring_parse_vehicle_data()into smaller helpers (e.g., per feature group like V2L/charge lid/DCDC) so complexity stays within the configured limit and remains maintainable.