Skip to content
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3f806a8
Add Eve Online integration
ronaldvdmeer Mar 27, 2026
9990a2f
Add binary sensor platform to Eve Online integration
ronaldvdmeer Mar 27, 2026
be2d290
Upgrade Eve Online integration to platinum quality scale
ronaldvdmeer Mar 27, 2026
5923049
Change quality scale to bronze for new integration
ronaldvdmeer Mar 27, 2026
c77c214
Catch JWT decoding failures in config flow
ronaldvdmeer Mar 27, 2026
044f4e0
Use reconfigure-specific abort reason for account mismatch
ronaldvdmeer Mar 27, 2026
d92f837
Redact character_id in diagnostics entry_data
ronaldvdmeer Mar 27, 2026
bb7eabe
Catch aiohttp.ClientError in optional endpoint helpers
ronaldvdmeer Mar 27, 2026
0302e0e
Use generator-based count instead of intermediate list
ronaldvdmeer Mar 27, 2026
0cb441d
Fix total_sp state class and re-raise auth errors in helpers
ronaldvdmeer Mar 27, 2026
94b108d
Merge branch 'dev' into add-eveonline-integration
ronaldvdmeer Mar 27, 2026
57dfb17
Handle OAuth2 token errors and add binascii.Error to JWT catch
ronaldvdmeer Mar 27, 2026
0a3b4c3
Add comprehensive error path tests for config flow and coordinator
ronaldvdmeer Mar 27, 2026
5082b29
Achieve 100% test coverage on all component files
ronaldvdmeer Mar 27, 2026
1bf7724
Address Copilot review: translation_placeholders and ConfigEntryAuthF…
ronaldvdmeer Mar 27, 2026
ed1d625
Address Copilot review: server entity dedup and stricter JWT validation
ronaldvdmeer Mar 27, 2026
4188cf6
Round wallet balance to 2 decimals to avoid floating-point artifacts …
ronaldvdmeer Mar 27, 2026
f1aed99
Move token extraction into try block, track server entity ownership i…
ronaldvdmeer Mar 27, 2026
a3df086
Merge branch 'dev' into add-eveonline-integration
ronaldvdmeer Mar 27, 2026
32e69dd
Remove binary_sensor platform to comply with single-platform rule for…
ronaldvdmeer Mar 27, 2026
f7baa66
Remove diagnostics and reauth flow per reviewer request
ronaldvdmeer Mar 27, 2026
9aac320
Merge remote-tracking branch 'upstream/dev' into add-eveonline-integr…
ronaldvdmeer Mar 27, 2026
eeeb68b
Fix unload ownership guard and add authentication_failed translation
ronaldvdmeer Mar 27, 2026
4a9a146
Bump python-eveonline to 0.2.2
ronaldvdmeer Mar 27, 2026
cc6403b
Bump python-eveonline to 0.4.0
ronaldvdmeer Mar 29, 2026
a8c1512
Use token_refresh_failed translation key for OAuth token errors in ap…
ronaldvdmeer Mar 29, 2026
a834c53
Add expires_at to mock OAuth tokens in test fixtures
ronaldvdmeer Mar 29, 2026
d8ce117
Remove unused mock_eveonline_client parameter from _setup_integration…
ronaldvdmeer Mar 29, 2026
de42a3c
Reload remaining entry to recreate server sensors when owning entry i…
ronaldvdmeer Mar 29, 2026
599a757
Use constants for character_id and character_name config entry data keys
ronaldvdmeer Mar 30, 2026
9d83bbf
Remove reconfigure support — defer to a later PR
ronaldvdmeer Mar 30, 2026
607d8b2
Use translated units of measurement for custom sensor units
ronaldvdmeer Mar 30, 2026
c76ad37
Remove reconfigure tests that were left over after removing reconfigu…
ronaldvdmeer Mar 30, 2026
6653095
Remove ownership mechanism and available_fn — simplify to HA native b…
ronaldvdmeer Mar 30, 2026
8394ad3
Add all quality scale rules with appropriate status
ronaldvdmeer Mar 30, 2026
37497f9
Remove DOMAIN prefix from unique_id
ronaldvdmeer Mar 30, 2026
6364483
Use PyJWT to decode Eve SSO JWT token
ronaldvdmeer Mar 30, 2026
53de7e3
Simplify: cleaner JWT decode, parametrized tests
ronaldvdmeer Mar 30, 2026
d4cf0e0
Fix quality_scale entries and token_type in tests
ronaldvdmeer Mar 30, 2026
62ed0e2
Remove server sensors to avoid unique_id collision across entries
ronaldvdmeer Mar 30, 2026
7551bea
Restore sell_orders in strings.json and icons.json
ronaldvdmeer Mar 30, 2026
f5aace9
Replace server_status connectivity check with character_online call
ronaldvdmeer Mar 30, 2026
4a8a876
Mark docs quality scale rules as done
ronaldvdmeer Mar 31, 2026
6d04046
Mark docs-supported-devices as exempt — cloud service, no physical de…
ronaldvdmeer Mar 31, 2026
be1a1e5
Merge branch 'dev' into add-eveonline-integration
ronaldvdmeer Apr 2, 2026
6e2fe39
Parametrize setup error tests in test_init.py
ronaldvdmeer Apr 4, 2026
31b234d
Improve code quality: integration_type, quality_scale, CONF_ constant…
ronaldvdmeer Apr 4, 2026
a1095c6
Improve code quality based on self-review
ronaldvdmeer Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ homeassistant.components.enphase_envoy.*
homeassistant.components.eq3btsmart.*
homeassistant.components.esphome.*
homeassistant.components.event.*
homeassistant.components.eveonline.*
homeassistant.components.evil_genius_labs.*
homeassistant.components.evohome.*
homeassistant.components.faa_delays.*
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions homeassistant/components/eveonline/__init__.py
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)
48 changes: 48 additions & 0 deletions homeassistant/components/eveonline/api.py
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
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"])
14 changes: 14 additions & 0 deletions homeassistant/components/eveonline/application_credentials.py
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,
)
74 changes: 74 additions & 0 deletions homeassistant/components/eveonline/config_flow.py
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]:
"""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:
return self.async_abort(reason="oauth_error")

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()

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"),
}
24 changes: 24 additions & 0 deletions homeassistant/components/eveonline/const.py
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",
]
Loading
Loading