-
-
Notifications
You must be signed in to change notification settings - Fork 37.2k
Add eveonline integration #166674
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
Open
ronaldvdmeer
wants to merge
48
commits into
home-assistant:dev
Choose a base branch
from
ronaldvdmeer:add-eveonline-integration
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add eveonline integration #166674
Changes from 43 commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
3f806a8
Add Eve Online integration
ronaldvdmeer 9990a2f
Add binary sensor platform to Eve Online integration
ronaldvdmeer be2d290
Upgrade Eve Online integration to platinum quality scale
ronaldvdmeer 5923049
Change quality scale to bronze for new integration
ronaldvdmeer c77c214
Catch JWT decoding failures in config flow
ronaldvdmeer 044f4e0
Use reconfigure-specific abort reason for account mismatch
ronaldvdmeer d92f837
Redact character_id in diagnostics entry_data
ronaldvdmeer bb7eabe
Catch aiohttp.ClientError in optional endpoint helpers
ronaldvdmeer 0302e0e
Use generator-based count instead of intermediate list
ronaldvdmeer 0cb441d
Fix total_sp state class and re-raise auth errors in helpers
ronaldvdmeer 94b108d
Merge branch 'dev' into add-eveonline-integration
ronaldvdmeer 57dfb17
Handle OAuth2 token errors and add binascii.Error to JWT catch
ronaldvdmeer 0a3b4c3
Add comprehensive error path tests for config flow and coordinator
ronaldvdmeer 5082b29
Achieve 100% test coverage on all component files
ronaldvdmeer 1bf7724
Address Copilot review: translation_placeholders and ConfigEntryAuthF…
ronaldvdmeer ed1d625
Address Copilot review: server entity dedup and stricter JWT validation
ronaldvdmeer 4188cf6
Round wallet balance to 2 decimals to avoid floating-point artifacts …
ronaldvdmeer f1aed99
Move token extraction into try block, track server entity ownership i…
ronaldvdmeer a3df086
Merge branch 'dev' into add-eveonline-integration
ronaldvdmeer 32e69dd
Remove binary_sensor platform to comply with single-platform rule for…
ronaldvdmeer f7baa66
Remove diagnostics and reauth flow per reviewer request
ronaldvdmeer 9aac320
Merge remote-tracking branch 'upstream/dev' into add-eveonline-integr…
ronaldvdmeer eeeb68b
Fix unload ownership guard and add authentication_failed translation
ronaldvdmeer 4a9a146
Bump python-eveonline to 0.2.2
ronaldvdmeer cc6403b
Bump python-eveonline to 0.4.0
ronaldvdmeer a8c1512
Use token_refresh_failed translation key for OAuth token errors in ap…
ronaldvdmeer a834c53
Add expires_at to mock OAuth tokens in test fixtures
ronaldvdmeer d8ce117
Remove unused mock_eveonline_client parameter from _setup_integration…
ronaldvdmeer de42a3c
Reload remaining entry to recreate server sensors when owning entry i…
ronaldvdmeer 599a757
Use constants for character_id and character_name config entry data keys
ronaldvdmeer 9d83bbf
Remove reconfigure support — defer to a later PR
ronaldvdmeer 607d8b2
Use translated units of measurement for custom sensor units
ronaldvdmeer c76ad37
Remove reconfigure tests that were left over after removing reconfigu…
ronaldvdmeer 6653095
Remove ownership mechanism and available_fn — simplify to HA native b…
ronaldvdmeer 8394ad3
Add all quality scale rules with appropriate status
ronaldvdmeer 37497f9
Remove DOMAIN prefix from unique_id
ronaldvdmeer 6364483
Use PyJWT to decode Eve SSO JWT token
ronaldvdmeer 53de7e3
Simplify: cleaner JWT decode, parametrized tests
ronaldvdmeer d4cf0e0
Fix quality_scale entries and token_type in tests
ronaldvdmeer 62ed0e2
Remove server sensors to avoid unique_id collision across entries
ronaldvdmeer 7551bea
Restore sell_orders in strings.json and icons.json
ronaldvdmeer f5aace9
Replace server_status connectivity check with character_online call
ronaldvdmeer 4a8a876
Mark docs quality scale rules as done
ronaldvdmeer 6d04046
Mark docs-supported-devices as exempt — cloud service, no physical de…
ronaldvdmeer be1a1e5
Merge branch 'dev' into add-eveonline-integration
ronaldvdmeer 6e2fe39
Parametrize setup error tests in test_init.py
ronaldvdmeer 31b234d
Improve code quality: integration_type, quality_scale, CONF_ constant…
ronaldvdmeer a1095c6
Improve code quality based on self-review
ronaldvdmeer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| """The Eve Online integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from eveonline import EveOnlineClient | ||
|
|
||
| from homeassistant.const import Platform | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import ConfigEntryNotReady | ||
| from homeassistant.helpers import aiohttp_client | ||
| from homeassistant.helpers.config_entry_oauth2_flow import ( | ||
| ImplementationUnavailableError, | ||
| OAuth2Session, | ||
| async_get_config_entry_implementation, | ||
| ) | ||
|
|
||
| from .api import AsyncConfigEntryAuth | ||
| from .const import CONF_CHARACTER_ID, CONF_CHARACTER_NAME, DOMAIN | ||
| from .coordinator import EveOnlineConfigEntry, EveOnlineCoordinator | ||
|
|
||
| PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
|
||
|
|
||
| async def async_setup_entry(hass: HomeAssistant, entry: EveOnlineConfigEntry) -> bool: | ||
| """Set up Eve Online from a config entry.""" | ||
| try: | ||
| implementation = await async_get_config_entry_implementation(hass, entry) | ||
| except ImplementationUnavailableError as err: | ||
| raise ConfigEntryNotReady( | ||
| translation_domain=DOMAIN, | ||
| translation_key="oauth2_implementation_unavailable", | ||
| ) from err | ||
|
|
||
| session = OAuth2Session(hass, entry, implementation) | ||
|
|
||
| auth = AsyncConfigEntryAuth(aiohttp_client.async_get_clientsession(hass), session) | ||
| client = EveOnlineClient(auth=auth) | ||
|
|
||
| character_id: int = entry.data[CONF_CHARACTER_ID] | ||
| character_name: str = entry.data[CONF_CHARACTER_NAME] | ||
|
|
||
| coordinator = EveOnlineCoordinator( | ||
| hass, entry, client, character_id, character_name | ||
| ) | ||
| await coordinator.async_config_entry_first_refresh() | ||
|
|
||
| entry.runtime_data = coordinator | ||
|
|
||
| await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
| return True | ||
|
|
||
|
|
||
| async def async_unload_entry(hass: HomeAssistant, entry: EveOnlineConfigEntry) -> bool: | ||
| """Unload a config entry.""" | ||
| return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| """API helpers for the Eve Online integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import cast | ||
|
|
||
| from aiohttp import ClientError, ClientSession | ||
| from eveonline.auth import AbstractAuth | ||
|
|
||
| from homeassistant.exceptions import ( | ||
| ConfigEntryAuthFailed, | ||
| ConfigEntryNotReady, | ||
| OAuth2TokenRequestReauthError, | ||
| OAuth2TokenRequestTransientError, | ||
| ) | ||
| from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session | ||
|
|
||
| from .const import DOMAIN | ||
|
|
||
|
|
||
| class AsyncConfigEntryAuth(AbstractAuth): | ||
| """Provide Eve Online authentication tied to an OAuth2 based config entry.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| websession: ClientSession, | ||
| oauth_session: OAuth2Session, | ||
| ) -> None: | ||
| """Initialize Eve Online auth.""" | ||
| super().__init__(websession) | ||
| self._oauth_session = oauth_session | ||
|
|
||
| async def async_get_access_token(self) -> str: | ||
| """Return a valid access token.""" | ||
| try: | ||
| await self._oauth_session.async_ensure_token_valid() | ||
| except OAuth2TokenRequestReauthError as err: | ||
| raise ConfigEntryAuthFailed( | ||
| translation_domain=DOMAIN, | ||
| translation_key="authentication_failed", | ||
| ) from err | ||
ronaldvdmeer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| except (OAuth2TokenRequestTransientError, ClientError) as err: | ||
| raise ConfigEntryNotReady( | ||
| translation_domain=DOMAIN, | ||
| translation_key="token_refresh_failed", | ||
| translation_placeholders={"error": str(err)}, | ||
| ) from err | ||
| return cast(str, self._oauth_session.token["access_token"]) | ||
ronaldvdmeer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
14 changes: 14 additions & 0 deletions
14
homeassistant/components/eveonline/application_credentials.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| """Application credentials for the Eve Online integration.""" | ||
|
|
||
| from homeassistant.components.application_credentials import AuthorizationServer | ||
| from homeassistant.core import HomeAssistant | ||
|
|
||
| from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN | ||
|
|
||
|
|
||
| async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: | ||
| """Return the Eve Online authorization server.""" | ||
| return AuthorizationServer( | ||
| authorize_url=OAUTH2_AUTHORIZE, | ||
| token_url=OAUTH2_TOKEN, | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| """Config flow for the Eve Online integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import Any | ||
|
|
||
| import jwt | ||
|
|
||
| from homeassistant.config_entries import ConfigFlowResult | ||
| from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler | ||
|
|
||
| from .const import CONF_CHARACTER_ID, CONF_CHARACTER_NAME, DOMAIN, SCOPES | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class OAuth2FlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): | ||
| """Handle OAuth2 config flow for Eve Online. | ||
|
|
||
| Each config entry represents one authenticated character. | ||
| Multiple characters can be added as separate entries. | ||
| """ | ||
|
|
||
| DOMAIN = DOMAIN | ||
|
|
||
| @property | ||
| def logger(self) -> logging.Logger: | ||
| """Return logger.""" | ||
| return _LOGGER | ||
|
|
||
| @property | ||
| def extra_authorize_data(self) -> dict[str, Any]: | ||
ronaldvdmeer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """Extra data to include in the authorize URL.""" | ||
| return {"scope": " ".join(SCOPES)} | ||
|
|
||
| async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: | ||
| """Create an entry for the flow. | ||
|
|
||
| Decode the Eve SSO JWT access token to extract character_id and | ||
| character_name, then create a config entry for that character. | ||
| """ | ||
| try: | ||
| token = data["token"]["access_token"] | ||
| character_info = _decode_eve_jwt(token) | ||
| except ValueError, KeyError, jwt.DecodeError: | ||
ronaldvdmeer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return self.async_abort(reason="oauth_error") | ||
ronaldvdmeer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| character_id = character_info[CONF_CHARACTER_ID] | ||
| character_name = character_info[CONF_CHARACTER_NAME] | ||
|
|
||
| await self.async_set_unique_id(str(character_id)) | ||
| self._abort_if_unique_id_configured() | ||
|
|
||
ronaldvdmeer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| data[CONF_CHARACTER_ID] = character_id | ||
| data[CONF_CHARACTER_NAME] = character_name | ||
|
|
||
| return self.async_create_entry( | ||
| title=character_name, | ||
| data=data, | ||
| ) | ||
|
|
||
|
|
||
| def _decode_eve_jwt(token: str) -> dict[str, Any]: | ||
| """Decode an Eve SSO JWT to extract character info.""" | ||
| decoded = jwt.decode(token, options={"verify_signature": False}) | ||
| sub = decoded.get("sub", "") | ||
| sub_parts = sub.split(":") | ||
| if len(sub_parts) != 3 or sub_parts[0] != "CHARACTER" or sub_parts[1] != "EVE": | ||
| raise ValueError(sub) | ||
| return { | ||
| CONF_CHARACTER_ID: int(sub_parts[2]), | ||
| CONF_CHARACTER_NAME: decoded.get("name", "Unknown"), | ||
| } | ||
ronaldvdmeer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| """Constants for the Eve Online integration.""" | ||
|
|
||
| from typing import Final | ||
|
|
||
| DOMAIN: Final = "eveonline" | ||
|
|
||
| CONF_CHARACTER_ID: Final = "character_id" | ||
| CONF_CHARACTER_NAME: Final = "character_name" | ||
|
|
||
| OAUTH2_AUTHORIZE: Final = "https://login.eveonline.com/v2/oauth/authorize" | ||
| OAUTH2_TOKEN: Final = "https://login.eveonline.com/v2/oauth/token" | ||
|
|
||
| SCOPES: Final[list[str]] = [ | ||
| "esi-characters.read_fatigue.v1", | ||
| "esi-industry.read_character_jobs.v1", | ||
| "esi-location.read_location.v1", | ||
| "esi-location.read_online.v1", | ||
| "esi-location.read_ship_type.v1", | ||
| "esi-mail.read_mail.v1", | ||
| "esi-markets.read_character_orders.v1", | ||
| "esi-skills.read_skillqueue.v1", | ||
| "esi-skills.read_skills.v1", | ||
| "esi-wallet.read_character_wallet.v1", | ||
| ] |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.