Home Assistant Unofficial Reference 2024.12.1
services.py
Go to the documentation of this file.
1 """Services for the Plex integration."""
2 
3 import json
4 import logging
5 
6 from plexapi.exceptions import NotFound
7 import voluptuous as vol
8 from yarl import URL
9 
10 from homeassistant.core import HomeAssistant, ServiceCall
11 from homeassistant.exceptions import HomeAssistantError
12 from homeassistant.helpers.dispatcher import async_dispatcher_send
13 
14 from .const import (
15  DOMAIN,
16  PLEX_UPDATE_PLATFORMS_SIGNAL,
17  PLEX_URI_SCHEME,
18  SERVERS,
19  SERVICE_REFRESH_LIBRARY,
20  SERVICE_SCAN_CLIENTS,
21 )
22 from .errors import MediaNotFound
23 from .helpers import get_plex_data
24 from .models import PlexMediaSearchResult
25 from .server import PlexServer
26 
27 REFRESH_LIBRARY_SCHEMA = vol.Schema(
28  {vol.Optional("server_name"): str, vol.Required("library_name"): str}
29 )
30 
31 _LOGGER = logging.getLogger(__package__)
32 
33 
34 async def async_setup_services(hass: HomeAssistant) -> None:
35  """Set up services for the Plex component."""
36 
37  async def async_refresh_library_service(service_call: ServiceCall) -> None:
38  await hass.async_add_executor_job(refresh_library, hass, service_call)
39 
40  async def async_scan_clients_service(_: ServiceCall) -> None:
41  _LOGGER.warning(
42  "This service is deprecated in favor of the scan_clients button entity."
43  " Service calls will still work for now but the service will be removed in"
44  " a future release"
45  )
46  for server_id in get_plex_data(hass)[SERVERS]:
47  async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
48 
49  hass.services.async_register(
50  DOMAIN,
51  SERVICE_REFRESH_LIBRARY,
52  async_refresh_library_service,
53  schema=REFRESH_LIBRARY_SCHEMA,
54  )
55  hass.services.async_register(
56  DOMAIN, SERVICE_SCAN_CLIENTS, async_scan_clients_service
57  )
58 
59 
60 def refresh_library(hass: HomeAssistant, service_call: ServiceCall) -> None:
61  """Scan a Plex library for new and updated media."""
62  plex_server_name = service_call.data.get("server_name")
63  library_name = service_call.data["library_name"]
64 
65  plex_server = get_plex_server(hass, plex_server_name)
66 
67  try:
68  library = plex_server.library.section(title=library_name)
69  except NotFound:
70  _LOGGER.error(
71  "Library with name '%s' not found in %s",
72  library_name,
73  [x.title for x in plex_server.library.sections()],
74  )
75  return
76 
77  _LOGGER.debug("Scanning %s for new and updated media", library_name)
78  library.update()
79 
80 
82  hass: HomeAssistant,
83  plex_server_name: str | None = None,
84  plex_server_id: str | None = None,
85 ) -> PlexServer:
86  """Retrieve a configured Plex server by name."""
87  if DOMAIN not in hass.data:
88  raise HomeAssistantError("Plex integration not configured")
89  servers: dict[str, PlexServer] = get_plex_data(hass)[SERVERS]
90  if not servers:
91  raise HomeAssistantError("No Plex servers available")
92 
93  if plex_server_id:
94  return servers[plex_server_id]
95 
96  plex_servers = servers.values()
97  if plex_server_name:
98  plex_server = next(
99  (x for x in plex_servers if x.friendly_name == plex_server_name), None
100  )
101  if plex_server is not None:
102  return plex_server
103  friendly_names = [x.friendly_name for x in plex_servers]
104  raise HomeAssistantError(
105  f"Requested Plex server '{plex_server_name}' not found in {friendly_names}"
106  )
107 
108  if len(plex_servers) == 1:
109  return next(iter(plex_servers))
110 
111  friendly_names = [x.friendly_name for x in plex_servers]
112  raise HomeAssistantError(
113  "Multiple Plex servers configured, choose with 'plex_server' key:"
114  f" {friendly_names}"
115  )
116 
117 
119  hass: HomeAssistant,
120  content_type: str,
121  content_id: str,
122  default_plex_server: PlexServer | None = None,
123  supports_playqueues: bool = True,
124 ) -> PlexMediaSearchResult:
125  """Look up Plex media using media_player.play_media service payloads."""
126  plex_server = default_plex_server
127  extra_params = {}
128 
129  if content_id.startswith(PLEX_URI_SCHEME + "{"):
130  # Handle the special payload of 'plex://{<json>}'
131  content_id = content_id.removeprefix(PLEX_URI_SCHEME)
132  content = json.loads(content_id)
133  elif content_id.startswith(PLEX_URI_SCHEME):
134  # Handle standard media_browser payloads
135  plex_url = URL(content_id)
136  # https://github.com/pylint-dev/pylint/issues/3484
137  # pylint: disable-next=using-constant-test
138  if plex_url.name:
139  if len(plex_url.parts) == 2:
140  if plex_url.name == "search":
141  content = {}
142  else:
143  content = int(plex_url.name)
144  else:
145  # For "special" items like radio stations
146  content = plex_url.path
147  server_id = plex_url.host
148  plex_server = get_plex_server(hass, plex_server_id=server_id)
149  else: # noqa: PLR5501
150  # Handle legacy payloads without server_id in URL host position
151  if plex_url.host == "search":
152  content = {}
153  else:
154  content = int(plex_url.host) # type: ignore[arg-type]
155  extra_params = dict(plex_url.query)
156  else:
157  content = json.loads(content_id)
158 
159  if isinstance(content, dict):
160  if plex_server_name := content.pop("plex_server", None):
161  plex_server = get_plex_server(hass, plex_server_name)
162 
163  if not plex_server:
164  plex_server = get_plex_server(hass)
165 
166  if isinstance(content, dict):
167  if plex_user := content.pop("username", None):
168  _LOGGER.debug("Switching to Plex user: %s", plex_user)
169  plex_server = plex_server.switch_user(plex_user)
170 
171  if content_type == "station":
172  if not supports_playqueues:
173  raise HomeAssistantError("Plex stations are not supported on this device")
174  playqueue = plex_server.create_station_playqueue(content)
175  return PlexMediaSearchResult(playqueue)
176 
177  if isinstance(content, int):
178  content = {"plex_key": content}
179  content_type = DOMAIN
180 
181  content.update(extra_params)
182 
183  if playqueue_id := content.pop("playqueue_id", None):
184  if not supports_playqueues:
185  raise HomeAssistantError("Plex playqueues are not supported on this device")
186  try:
187  playqueue = plex_server.get_playqueue(playqueue_id)
188  except NotFound as err:
189  raise MediaNotFound(
190  f"PlayQueue '{playqueue_id}' could not be found"
191  ) from err
192  return PlexMediaSearchResult(playqueue, content)
193 
194  search_query = content.copy()
195  shuffle = search_query.pop("shuffle", 0)
196 
197  # Remove internal kwargs before passing copy to plexapi
198  for internal_key in ("resume", "offset"):
199  search_query.pop(internal_key, None)
200 
201  media = plex_server.lookup_media(content_type, **search_query)
202 
203  if supports_playqueues and (isinstance(media, list) or shuffle):
204  playqueue = plex_server.create_playqueue(
205  media, includeRelated=0, shuffle=shuffle
206  )
207  return PlexMediaSearchResult(playqueue, content)
208 
209  return PlexMediaSearchResult(media, content)
PlexData get_plex_data(HomeAssistant hass)
Definition: helpers.py:29
PlexServer get_plex_server(HomeAssistant hass, str|None plex_server_name=None, str|None plex_server_id=None)
Definition: services.py:85
None refresh_library(HomeAssistant hass, ServiceCall service_call)
Definition: services.py:60
PlexMediaSearchResult process_plex_payload(HomeAssistant hass, str content_type, str content_id, PlexServer|None default_plex_server=None, bool supports_playqueues=True)
Definition: services.py:124
None async_setup_services(HomeAssistant hass)
Definition: services.py:34
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193