mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 23:27:37 +00:00
Add image platform to PlayStation Network (#148928)
This commit is contained in:
parent
50688bbd69
commit
414057d455
@ -16,6 +16,7 @@ from .helpers import PlaystationNetwork
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.IMAGE,
|
||||
Platform.MEDIA_PLAYER,
|
||||
Platform.SENSOR,
|
||||
]
|
||||
|
@ -43,11 +43,14 @@ class PlaystationNetworkData:
|
||||
registered_platforms: set[PlatformType] = field(default_factory=set)
|
||||
trophy_summary: TrophySummary | None = None
|
||||
profile: dict[str, Any] = field(default_factory=dict)
|
||||
shareable_profile_link: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
class PlaystationNetwork:
|
||||
"""Helper Class to return playstation network data in an easy to use structure."""
|
||||
|
||||
shareable_profile_link: dict[str, str]
|
||||
|
||||
def __init__(self, hass: HomeAssistant, npsso: str) -> None:
|
||||
"""Initialize the class with the npsso token."""
|
||||
rate = Rate(300, Duration.MINUTE * 15)
|
||||
@ -63,6 +66,7 @@ class PlaystationNetwork:
|
||||
"""Setup PSN."""
|
||||
self.user = self.psn.user(online_id="me")
|
||||
self.client = self.psn.me()
|
||||
self.shareable_profile_link = self.client.get_shareable_profile_link()
|
||||
self.trophy_titles = list(self.user.trophy_titles())
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
@ -100,7 +104,7 @@ class PlaystationNetwork:
|
||||
data = await self.hass.async_add_executor_job(self.retrieve_psn_data)
|
||||
data.username = self.user.online_id
|
||||
data.account_id = self.user.account_id
|
||||
|
||||
data.shareable_profile_link = self.shareable_profile_link
|
||||
data.availability = data.presence["basicPresence"]["availability"]
|
||||
|
||||
session = SessionData()
|
||||
|
@ -43,6 +43,14 @@
|
||||
"offline": "mdi:account-off-outline"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"share_profile": {
|
||||
"default": "mdi:share-variant"
|
||||
},
|
||||
"avatar": {
|
||||
"default": "mdi:account-circle"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
105
homeassistant/components/playstation_network/image.py
Normal file
105
homeassistant/components/playstation_network/image.py
Normal file
@ -0,0 +1,105 @@
|
||||
"""Image platform for PlayStation Network."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
|
||||
from homeassistant.components.image import ImageEntity, ImageEntityDescription
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .coordinator import (
|
||||
PlaystationNetworkConfigEntry,
|
||||
PlaystationNetworkData,
|
||||
PlaystationNetworkUserDataCoordinator,
|
||||
)
|
||||
from .entity import PlaystationNetworkServiceEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
class PlaystationNetworkImage(StrEnum):
|
||||
"""PlayStation Network images."""
|
||||
|
||||
AVATAR = "avatar"
|
||||
SHARE_PROFILE = "share_profile"
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class PlaystationNetworkImageEntityDescription(ImageEntityDescription):
|
||||
"""Image entity description."""
|
||||
|
||||
image_url_fn: Callable[[PlaystationNetworkData], str | None]
|
||||
|
||||
|
||||
IMAGE_DESCRIPTIONS: tuple[PlaystationNetworkImageEntityDescription, ...] = (
|
||||
PlaystationNetworkImageEntityDescription(
|
||||
key=PlaystationNetworkImage.SHARE_PROFILE,
|
||||
translation_key=PlaystationNetworkImage.SHARE_PROFILE,
|
||||
image_url_fn=lambda data: data.shareable_profile_link["shareImageUrl"],
|
||||
),
|
||||
PlaystationNetworkImageEntityDescription(
|
||||
key=PlaystationNetworkImage.AVATAR,
|
||||
translation_key=PlaystationNetworkImage.AVATAR,
|
||||
image_url_fn=(
|
||||
lambda data: next(
|
||||
(
|
||||
pic.get("url")
|
||||
for pic in data.profile["avatars"]
|
||||
if pic.get("size") == "xl"
|
||||
),
|
||||
None,
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: PlaystationNetworkConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up image platform."""
|
||||
|
||||
coordinator = config_entry.runtime_data.user_data
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
PlaystationNetworkImageEntity(hass, coordinator, description)
|
||||
for description in IMAGE_DESCRIPTIONS
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class PlaystationNetworkImageEntity(PlaystationNetworkServiceEntity, ImageEntity):
|
||||
"""An image entity."""
|
||||
|
||||
entity_description: PlaystationNetworkImageEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
coordinator: PlaystationNetworkUserDataCoordinator,
|
||||
entity_description: PlaystationNetworkImageEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the image entity."""
|
||||
super().__init__(coordinator, entity_description)
|
||||
ImageEntity.__init__(self, hass)
|
||||
|
||||
self._attr_image_url = self.entity_description.image_url_fn(coordinator.data)
|
||||
self._attr_image_last_updated = dt_util.utcnow()
|
||||
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
url = self.entity_description.image_url_fn(self.coordinator.data)
|
||||
|
||||
if url != self._attr_image_url:
|
||||
self._attr_image_url = url
|
||||
self._cached_image = None
|
||||
self._attr_image_last_updated = dt_util.utcnow()
|
||||
|
||||
super()._handle_coordinator_update()
|
@ -96,6 +96,14 @@
|
||||
"busy": "Away"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"share_profile": {
|
||||
"name": "Share profile"
|
||||
},
|
||||
"avatar": {
|
||||
"name": "Avatar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,9 @@ def mock_psnawpapi(mock_user: MagicMock) -> Generator[MagicMock]:
|
||||
]
|
||||
}
|
||||
}
|
||||
client.me.return_value.get_shareable_profile_link.return_value = {
|
||||
"shareImageUrl": "https://xxxxx.cloudfront.net/profile-testuser?Expires=1753304493"
|
||||
}
|
||||
yield client
|
||||
|
||||
|
||||
|
@ -71,6 +71,9 @@
|
||||
'PS5',
|
||||
'PSVITA',
|
||||
]),
|
||||
'shareable_profile_link': dict({
|
||||
'shareImageUrl': 'https://xxxxx.cloudfront.net/profile-testuser?Expires=1753304493',
|
||||
}),
|
||||
'trophy_summary': dict({
|
||||
'account_id': '**REDACTED**',
|
||||
'earned_trophies': dict({
|
||||
|
96
tests/components/playstation_network/test_image.py
Normal file
96
tests/components/playstation_network/test_image.py
Normal file
@ -0,0 +1,96 @@
|
||||
"""Test the PlayStation Network image platform."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from datetime import timedelta
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def image_only() -> Generator[None]:
|
||||
"""Enable only the image platform."""
|
||||
with patch(
|
||||
"homeassistant.components.playstation_network.PLATFORMS",
|
||||
[Platform.IMAGE],
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.usefixtures("mock_psnawpapi")
|
||||
async def test_image_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
hass_client: ClientSessionGenerator,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_psnawpapi: MagicMock,
|
||||
) -> None:
|
||||
"""Test image platform."""
|
||||
freezer.move_to("2025-06-16T00:00:00-00:00")
|
||||
|
||||
respx.get(
|
||||
"http://static-resource.np.community.playstation.net/avatar_xl/WWS_A/UP90001312L24_DD96EB6A4FF5FE883C09_XL.png"
|
||||
).respond(status_code=HTTPStatus.OK, content_type="image/png", content=b"Test")
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
assert (state := hass.states.get("image.testuser_avatar"))
|
||||
assert state.state == "2025-06-16T00:00:00+00:00"
|
||||
|
||||
access_token = state.attributes["access_token"]
|
||||
assert (
|
||||
state.attributes["entity_picture"]
|
||||
== f"/api/image_proxy/image.testuser_avatar?token={access_token}"
|
||||
)
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.get(state.attributes["entity_picture"])
|
||||
assert resp.status == HTTPStatus.OK
|
||||
body = await resp.read()
|
||||
assert body == b"Test"
|
||||
assert resp.content_type == "image/png"
|
||||
assert resp.content_length == 4
|
||||
|
||||
ava = "https://static-resource.np.community.playstation.net/avatar_m/WWS_E/E0011_m.png"
|
||||
profile = mock_psnawpapi.user.return_value.profile.return_value
|
||||
profile["avatars"] = [{"size": "xl", "url": ava}]
|
||||
mock_psnawpapi.user.return_value.profile.return_value = profile
|
||||
respx.get(ava).respond(
|
||||
status_code=HTTPStatus.OK, content_type="image/png", content=b"Test2"
|
||||
)
|
||||
|
||||
freezer.tick(timedelta(seconds=30))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (state := hass.states.get("image.testuser_avatar"))
|
||||
assert state.state == "2025-06-16T00:00:30+00:00"
|
||||
|
||||
access_token = state.attributes["access_token"]
|
||||
assert (
|
||||
state.attributes["entity_picture"]
|
||||
== f"/api/image_proxy/image.testuser_avatar?token={access_token}"
|
||||
)
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.get(state.attributes["entity_picture"])
|
||||
assert resp.status == HTTPStatus.OK
|
||||
body = await resp.read()
|
||||
assert body == b"Test2"
|
||||
assert resp.content_type == "image/png"
|
||||
assert resp.content_length == 5
|
Loading…
x
Reference in New Issue
Block a user