mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +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] = [
|
PLATFORMS: list[Platform] = [
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
|
Platform.IMAGE,
|
||||||
Platform.MEDIA_PLAYER,
|
Platform.MEDIA_PLAYER,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
]
|
]
|
||||||
|
@ -43,11 +43,14 @@ class PlaystationNetworkData:
|
|||||||
registered_platforms: set[PlatformType] = field(default_factory=set)
|
registered_platforms: set[PlatformType] = field(default_factory=set)
|
||||||
trophy_summary: TrophySummary | None = None
|
trophy_summary: TrophySummary | None = None
|
||||||
profile: dict[str, Any] = field(default_factory=dict)
|
profile: dict[str, Any] = field(default_factory=dict)
|
||||||
|
shareable_profile_link: dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
class PlaystationNetwork:
|
class PlaystationNetwork:
|
||||||
"""Helper Class to return playstation network data in an easy to use structure."""
|
"""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:
|
def __init__(self, hass: HomeAssistant, npsso: str) -> None:
|
||||||
"""Initialize the class with the npsso token."""
|
"""Initialize the class with the npsso token."""
|
||||||
rate = Rate(300, Duration.MINUTE * 15)
|
rate = Rate(300, Duration.MINUTE * 15)
|
||||||
@ -63,6 +66,7 @@ class PlaystationNetwork:
|
|||||||
"""Setup PSN."""
|
"""Setup PSN."""
|
||||||
self.user = self.psn.user(online_id="me")
|
self.user = self.psn.user(online_id="me")
|
||||||
self.client = self.psn.me()
|
self.client = self.psn.me()
|
||||||
|
self.shareable_profile_link = self.client.get_shareable_profile_link()
|
||||||
self.trophy_titles = list(self.user.trophy_titles())
|
self.trophy_titles = list(self.user.trophy_titles())
|
||||||
|
|
||||||
async def async_setup(self) -> None:
|
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 = await self.hass.async_add_executor_job(self.retrieve_psn_data)
|
||||||
data.username = self.user.online_id
|
data.username = self.user.online_id
|
||||||
data.account_id = self.user.account_id
|
data.account_id = self.user.account_id
|
||||||
|
data.shareable_profile_link = self.shareable_profile_link
|
||||||
data.availability = data.presence["basicPresence"]["availability"]
|
data.availability = data.presence["basicPresence"]["availability"]
|
||||||
|
|
||||||
session = SessionData()
|
session = SessionData()
|
||||||
|
@ -43,6 +43,14 @@
|
|||||||
"offline": "mdi:account-off-outline"
|
"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"
|
"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
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,6 +71,9 @@
|
|||||||
'PS5',
|
'PS5',
|
||||||
'PSVITA',
|
'PSVITA',
|
||||||
]),
|
]),
|
||||||
|
'shareable_profile_link': dict({
|
||||||
|
'shareImageUrl': 'https://xxxxx.cloudfront.net/profile-testuser?Expires=1753304493',
|
||||||
|
}),
|
||||||
'trophy_summary': dict({
|
'trophy_summary': dict({
|
||||||
'account_id': '**REDACTED**',
|
'account_id': '**REDACTED**',
|
||||||
'earned_trophies': dict({
|
'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