Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e1e2178
Add Eve Online integration
ronaldvdmeer Mar 27, 2026
e867344
Add binary sensor platform to Eve Online integration
ronaldvdmeer Mar 27, 2026
f5a61e8
Upgrade Eve Online integration to platinum quality scale
ronaldvdmeer Mar 27, 2026
18b0066
Change quality scale to bronze for new integration
ronaldvdmeer Mar 27, 2026
9b77a3c
Catch JWT decoding failures in config flow
ronaldvdmeer Mar 27, 2026
2557a70
Use reconfigure-specific abort reason for account mismatch
ronaldvdmeer Mar 27, 2026
0b5f7c1
Redact character_id in diagnostics entry_data
ronaldvdmeer Mar 27, 2026
2dc2ed3
Catch aiohttp.ClientError in optional endpoint helpers
ronaldvdmeer Mar 27, 2026
65793f4
Use generator-based count instead of intermediate list
ronaldvdmeer Mar 27, 2026
a0796bb
Fix total_sp state class and re-raise auth errors in helpers
ronaldvdmeer Mar 27, 2026
98d8777
Handle OAuth2 token errors and add binascii.Error to JWT catch
ronaldvdmeer Mar 27, 2026
2ac10c5
Add comprehensive error path tests for config flow and coordinator
ronaldvdmeer Mar 27, 2026
c3547d4
Achieve 100% test coverage on all component files
ronaldvdmeer Mar 27, 2026
3331cb7
Address Copilot review: translation_placeholders and ConfigEntryAuthF…
ronaldvdmeer Mar 27, 2026
1f938ee
Address Copilot review: server entity dedup and stricter JWT validation
ronaldvdmeer Mar 27, 2026
2ab1597
Round wallet balance to 2 decimals to avoid floating-point artifacts …
ronaldvdmeer Mar 27, 2026
a688ec4
Move token extraction into try block, track server entity ownership i…
ronaldvdmeer Mar 27, 2026
b5cadff
Remove binary_sensor platform to comply with single-platform rule for…
ronaldvdmeer Mar 27, 2026
ba3e069
Remove diagnostics and reauth flow per reviewer request
ronaldvdmeer Mar 27, 2026
a680f3c
Fix unload ownership guard and add authentication_failed translation
ronaldvdmeer Mar 27, 2026
13149d7
Bump python-eveonline to 0.2.2
ronaldvdmeer Mar 27, 2026
2d0de0f
Bump python-eveonline to 0.4.0
ronaldvdmeer Mar 29, 2026
3b52d0d
Use token_refresh_failed translation key for OAuth token errors in ap…
ronaldvdmeer Mar 29, 2026
1d4180a
Add expires_at to mock OAuth tokens in test fixtures
ronaldvdmeer Mar 29, 2026
743c74b
Remove unused mock_eveonline_client parameter from _setup_integration…
ronaldvdmeer Mar 29, 2026
b04cc9e
Reload remaining entry to recreate server sensors when owning entry i…
ronaldvdmeer Mar 29, 2026
9a316c0
Use constants for character_id and character_name config entry data keys
ronaldvdmeer Mar 30, 2026
f9234b7
Remove reconfigure support — defer to a later PR
ronaldvdmeer Mar 30, 2026
813d198
Use translated units of measurement for custom sensor units
ronaldvdmeer Mar 30, 2026
a61c60e
Remove reconfigure tests that were left over after removing reconfigu…
ronaldvdmeer Mar 30, 2026
2afea39
Remove ownership mechanism and available_fn — simplify to HA native b…
ronaldvdmeer Mar 30, 2026
2a263d9
Add all quality scale rules with appropriate status
ronaldvdmeer Mar 30, 2026
79e6928
Remove DOMAIN prefix from unique_id
ronaldvdmeer Mar 30, 2026
c000714
Use PyJWT to decode Eve SSO JWT token
ronaldvdmeer Mar 30, 2026
513d8fa
Simplify: cleaner JWT decode, parametrized tests
ronaldvdmeer Mar 30, 2026
4a0a010
Fix quality_scale entries and token_type in tests
ronaldvdmeer Mar 30, 2026
0eab8d0
Remove server sensors to avoid unique_id collision across entries
ronaldvdmeer Mar 30, 2026
e673c02
Restore sell_orders in strings.json and icons.json
ronaldvdmeer Mar 30, 2026
9450b9e
Replace server_status connectivity check with character_online call
ronaldvdmeer Mar 30, 2026
7c766b5
Mark docs quality scale rules as done
ronaldvdmeer Mar 31, 2026
2b02f4b
Mark docs-supported-devices as exempt — cloud service, no physical de…
ronaldvdmeer Mar 31, 2026
f5bb950
Parametrize setup error tests in test_init.py
ronaldvdmeer Apr 4, 2026
85f2769
Improve code quality: integration_type, quality_scale, CONF_ constant…
ronaldvdmeer Apr 4, 2026
94c2651
Improve code quality based on self-review
ronaldvdmeer Apr 6, 2026
920f868
Fix UpdateFailed for transient auth errors, simplify sensor class hie…
ronaldvdmeer Apr 13, 2026
e415e90
Split coordinator by ESI cache interval and skip location/ship when o…
ronaldvdmeer Apr 13, 2026
35465c0
Address Copilot review: parallelism, cleanup, quality scale
ronaldvdmeer Apr 13, 2026
95d0320
Address Copilot review round 2: docstrings, offline caching, test qua…
ronaldvdmeer Apr 13, 2026
d503fcc
Fix quality scale, config flow syntax error, and stale comment
ronaldvdmeer Apr 13, 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.

74 changes: 74 additions & 0 deletions homeassistant/components/eveonline/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""The Eve Online integration."""

from __future__ import annotations

import asyncio

from eveonline import EveOnlineClient

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.config_entry_oauth2_flow import (
OAuth2Session,
async_get_config_entry_implementation,
)

from .api import AsyncConfigEntryAuth
from .const import CONF_CHARACTER_ID, CONF_CHARACTER_NAME
from .coordinator import (
EveOnlineConfigEntry,
EveOnlineCoordinator,
EveOnlineIndustryCoordinator,
EveOnlineMarketCoordinator,
EveOnlineRuntimeData,
EveOnlineSkillsCoordinator,
)

PLATFORMS: list[Platform] = [Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: EveOnlineConfigEntry) -> bool:
"""Set up Eve Online from a config entry."""
implementation = await async_get_config_entry_implementation(hass, entry)
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
)
industry_coordinator = EveOnlineIndustryCoordinator(
hass, entry, client, character_id, character_name
)
market_coordinator = EveOnlineMarketCoordinator(
hass, entry, client, character_id, character_name
)
skills_coordinator = EveOnlineSkillsCoordinator(
hass, entry, client, character_id, character_name
)

await asyncio.gather(
coordinator.async_config_entry_first_refresh(),
industry_coordinator.async_config_entry_first_refresh(),
market_coordinator.async_config_entry_first_refresh(),
skills_coordinator.async_config_entry_first_refresh(),
)

entry.runtime_data = EveOnlineRuntimeData(
coordinator=coordinator,
industry_coordinator=industry_coordinator,
market_coordinator=market_coordinator,
skills_coordinator=skills_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,
OAuth2TokenRequestReauthError,
OAuth2TokenRequestTransientError,
)
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
from homeassistant.helpers.update_coordinator import UpdateFailed

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