mirror of
https://github.com/home-assistant/core.git
synced 2025-11-28 03:58:01 +00:00
300 lines
9.4 KiB
Python
300 lines
9.4 KiB
Python
"""Custom actions (previously known as services) for the Music Assistant integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from music_assistant_models.enums import MediaType, QueueOption
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.media_player import (
|
|
ATTR_MEDIA_ENQUEUE,
|
|
DOMAIN as MEDIA_PLAYER_DOMAIN,
|
|
)
|
|
from homeassistant.const import ATTR_CONFIG_ENTRY_ID
|
|
from homeassistant.core import (
|
|
HomeAssistant,
|
|
ServiceCall,
|
|
ServiceResponse,
|
|
SupportsResponse,
|
|
callback,
|
|
)
|
|
from homeassistant.exceptions import ServiceValidationError
|
|
from homeassistant.helpers import config_validation as cv, service
|
|
|
|
from .const import (
|
|
ATTR_ALBUM,
|
|
ATTR_ALBUM_ARTISTS_ONLY,
|
|
ATTR_ALBUM_TYPE,
|
|
ATTR_ALBUMS,
|
|
ATTR_ANNOUNCE_VOLUME,
|
|
ATTR_ARTIST,
|
|
ATTR_ARTISTS,
|
|
ATTR_AUDIOBOOKS,
|
|
ATTR_AUTO_PLAY,
|
|
ATTR_FAVORITE,
|
|
ATTR_ITEMS,
|
|
ATTR_LIBRARY_ONLY,
|
|
ATTR_LIMIT,
|
|
ATTR_MEDIA_ID,
|
|
ATTR_MEDIA_TYPE,
|
|
ATTR_OFFSET,
|
|
ATTR_ORDER_BY,
|
|
ATTR_PLAYLISTS,
|
|
ATTR_PODCASTS,
|
|
ATTR_RADIO,
|
|
ATTR_RADIO_MODE,
|
|
ATTR_SEARCH,
|
|
ATTR_SEARCH_ALBUM,
|
|
ATTR_SEARCH_ARTIST,
|
|
ATTR_SEARCH_NAME,
|
|
ATTR_SOURCE_PLAYER,
|
|
ATTR_TRACKS,
|
|
ATTR_URL,
|
|
ATTR_USE_PRE_ANNOUNCE,
|
|
DOMAIN,
|
|
)
|
|
from .helpers import get_music_assistant_client
|
|
from .schemas import (
|
|
LIBRARY_RESULTS_SCHEMA,
|
|
SEARCH_RESULT_SCHEMA,
|
|
media_item_dict_from_mass_item,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from music_assistant_models.media_items import (
|
|
Album,
|
|
Artist,
|
|
Audiobook,
|
|
Playlist,
|
|
Podcast,
|
|
Radio,
|
|
Track,
|
|
)
|
|
|
|
SERVICE_SEARCH = "search"
|
|
SERVICE_GET_LIBRARY = "get_library"
|
|
SERVICE_PLAY_MEDIA_ADVANCED = "play_media"
|
|
SERVICE_PLAY_ANNOUNCEMENT = "play_announcement"
|
|
SERVICE_TRANSFER_QUEUE = "transfer_queue"
|
|
SERVICE_GET_QUEUE = "get_queue"
|
|
|
|
DEFAULT_OFFSET = 0
|
|
DEFAULT_LIMIT = 25
|
|
DEFAULT_SORT_ORDER = "name"
|
|
|
|
|
|
@callback
|
|
def register_actions(hass: HomeAssistant) -> None:
|
|
"""Register custom actions."""
|
|
hass.services.async_register(
|
|
DOMAIN,
|
|
SERVICE_SEARCH,
|
|
handle_search,
|
|
schema=vol.Schema(
|
|
{
|
|
vol.Required(ATTR_CONFIG_ENTRY_ID): str,
|
|
vol.Required(ATTR_SEARCH_NAME): cv.string,
|
|
vol.Optional(ATTR_MEDIA_TYPE): vol.All(
|
|
cv.ensure_list, [vol.Coerce(MediaType)]
|
|
),
|
|
vol.Optional(ATTR_SEARCH_ARTIST): cv.string,
|
|
vol.Optional(ATTR_SEARCH_ALBUM): cv.string,
|
|
vol.Optional(ATTR_LIMIT, default=5): vol.Coerce(int),
|
|
vol.Optional(ATTR_LIBRARY_ONLY, default=False): cv.boolean,
|
|
}
|
|
),
|
|
supports_response=SupportsResponse.ONLY,
|
|
)
|
|
hass.services.async_register(
|
|
DOMAIN,
|
|
SERVICE_GET_LIBRARY,
|
|
handle_get_library,
|
|
schema=vol.Schema(
|
|
{
|
|
vol.Required(ATTR_CONFIG_ENTRY_ID): str,
|
|
vol.Required(ATTR_MEDIA_TYPE): vol.Coerce(MediaType),
|
|
vol.Optional(ATTR_FAVORITE): cv.boolean,
|
|
vol.Optional(ATTR_SEARCH): cv.string,
|
|
vol.Optional(ATTR_LIMIT): cv.positive_int,
|
|
vol.Optional(ATTR_OFFSET): int,
|
|
vol.Optional(ATTR_ORDER_BY): cv.string,
|
|
vol.Optional(ATTR_ALBUM_TYPE): list[MediaType],
|
|
vol.Optional(ATTR_ALBUM_ARTISTS_ONLY): cv.boolean,
|
|
}
|
|
),
|
|
supports_response=SupportsResponse.ONLY,
|
|
)
|
|
|
|
# Platform entity services
|
|
service.async_register_platform_entity_service(
|
|
hass,
|
|
DOMAIN,
|
|
SERVICE_PLAY_MEDIA_ADVANCED,
|
|
entity_domain=MEDIA_PLAYER_DOMAIN,
|
|
schema={
|
|
vol.Required(ATTR_MEDIA_ID): vol.All(cv.ensure_list, [cv.string]),
|
|
vol.Optional(ATTR_MEDIA_TYPE): vol.Coerce(MediaType),
|
|
vol.Optional(ATTR_MEDIA_ENQUEUE): vol.Coerce(QueueOption),
|
|
vol.Optional(ATTR_ARTIST): cv.string,
|
|
vol.Optional(ATTR_ALBUM): cv.string,
|
|
vol.Optional(ATTR_RADIO_MODE): vol.Coerce(bool),
|
|
},
|
|
func="_async_handle_play_media",
|
|
)
|
|
service.async_register_platform_entity_service(
|
|
hass,
|
|
DOMAIN,
|
|
SERVICE_PLAY_ANNOUNCEMENT,
|
|
entity_domain=MEDIA_PLAYER_DOMAIN,
|
|
schema={
|
|
vol.Required(ATTR_URL): cv.string,
|
|
vol.Optional(ATTR_USE_PRE_ANNOUNCE): vol.Coerce(bool),
|
|
vol.Optional(ATTR_ANNOUNCE_VOLUME): vol.Coerce(int),
|
|
},
|
|
func="_async_handle_play_announcement",
|
|
)
|
|
service.async_register_platform_entity_service(
|
|
hass,
|
|
DOMAIN,
|
|
SERVICE_TRANSFER_QUEUE,
|
|
entity_domain=MEDIA_PLAYER_DOMAIN,
|
|
schema={
|
|
vol.Optional(ATTR_SOURCE_PLAYER): cv.entity_id,
|
|
vol.Optional(ATTR_AUTO_PLAY): vol.Coerce(bool),
|
|
},
|
|
func="_async_handle_transfer_queue",
|
|
)
|
|
service.async_register_platform_entity_service(
|
|
hass,
|
|
DOMAIN,
|
|
SERVICE_GET_QUEUE,
|
|
entity_domain=MEDIA_PLAYER_DOMAIN,
|
|
schema=None,
|
|
func="_async_handle_get_queue",
|
|
supports_response=SupportsResponse.ONLY,
|
|
)
|
|
|
|
|
|
async def handle_search(call: ServiceCall) -> ServiceResponse:
|
|
"""Handle queue_command action."""
|
|
mass = get_music_assistant_client(call.hass, call.data[ATTR_CONFIG_ENTRY_ID])
|
|
search_name = call.data[ATTR_SEARCH_NAME]
|
|
search_artist = call.data.get(ATTR_SEARCH_ARTIST)
|
|
search_album = call.data.get(ATTR_SEARCH_ALBUM)
|
|
if search_album and search_artist:
|
|
search_name = f"{search_artist} - {search_album} - {search_name}"
|
|
elif search_album:
|
|
search_name = f"{search_album} - {search_name}"
|
|
elif search_artist:
|
|
search_name = f"{search_artist} - {search_name}"
|
|
search_results = await mass.music.search(
|
|
search_query=search_name,
|
|
media_types=call.data.get(ATTR_MEDIA_TYPE, MediaType.ALL),
|
|
limit=call.data[ATTR_LIMIT],
|
|
library_only=call.data[ATTR_LIBRARY_ONLY],
|
|
)
|
|
response: ServiceResponse = SEARCH_RESULT_SCHEMA(
|
|
{
|
|
ATTR_ARTISTS: [
|
|
media_item_dict_from_mass_item(mass, item)
|
|
for item in search_results.artists
|
|
],
|
|
ATTR_ALBUMS: [
|
|
media_item_dict_from_mass_item(mass, item)
|
|
for item in search_results.albums
|
|
],
|
|
ATTR_TRACKS: [
|
|
media_item_dict_from_mass_item(mass, item)
|
|
for item in search_results.tracks
|
|
],
|
|
ATTR_PLAYLISTS: [
|
|
media_item_dict_from_mass_item(mass, item)
|
|
for item in search_results.playlists
|
|
],
|
|
ATTR_RADIO: [
|
|
media_item_dict_from_mass_item(mass, item)
|
|
for item in search_results.radio
|
|
],
|
|
ATTR_AUDIOBOOKS: [
|
|
media_item_dict_from_mass_item(mass, item)
|
|
for item in search_results.audiobooks
|
|
],
|
|
ATTR_PODCASTS: [
|
|
media_item_dict_from_mass_item(mass, item)
|
|
for item in search_results.podcasts
|
|
],
|
|
}
|
|
)
|
|
return response
|
|
|
|
|
|
async def handle_get_library(call: ServiceCall) -> ServiceResponse:
|
|
"""Handle get_library action."""
|
|
mass = get_music_assistant_client(call.hass, call.data[ATTR_CONFIG_ENTRY_ID])
|
|
media_type = call.data[ATTR_MEDIA_TYPE]
|
|
limit = call.data.get(ATTR_LIMIT, DEFAULT_LIMIT)
|
|
offset = call.data.get(ATTR_OFFSET, DEFAULT_OFFSET)
|
|
order_by = call.data.get(ATTR_ORDER_BY, DEFAULT_SORT_ORDER)
|
|
base_params = {
|
|
"favorite": call.data.get(ATTR_FAVORITE),
|
|
"search": call.data.get(ATTR_SEARCH),
|
|
"limit": limit,
|
|
"offset": offset,
|
|
"order_by": order_by,
|
|
}
|
|
library_result: (
|
|
list[Album]
|
|
| list[Artist]
|
|
| list[Track]
|
|
| list[Radio]
|
|
| list[Playlist]
|
|
| list[Audiobook]
|
|
| list[Podcast]
|
|
)
|
|
if media_type == MediaType.ALBUM:
|
|
library_result = await mass.music.get_library_albums(
|
|
**base_params,
|
|
album_types=call.data.get(ATTR_ALBUM_TYPE),
|
|
)
|
|
elif media_type == MediaType.ARTIST:
|
|
library_result = await mass.music.get_library_artists(
|
|
**base_params,
|
|
album_artists_only=bool(call.data.get(ATTR_ALBUM_ARTISTS_ONLY)),
|
|
)
|
|
elif media_type == MediaType.TRACK:
|
|
library_result = await mass.music.get_library_tracks(
|
|
**base_params,
|
|
)
|
|
elif media_type == MediaType.RADIO:
|
|
library_result = await mass.music.get_library_radios(
|
|
**base_params,
|
|
)
|
|
elif media_type == MediaType.PLAYLIST:
|
|
library_result = await mass.music.get_library_playlists(
|
|
**base_params,
|
|
)
|
|
elif media_type == MediaType.AUDIOBOOK:
|
|
library_result = await mass.music.get_library_audiobooks(
|
|
**base_params,
|
|
)
|
|
elif media_type == MediaType.PODCAST:
|
|
library_result = await mass.music.get_library_podcasts(
|
|
**base_params,
|
|
)
|
|
else:
|
|
raise ServiceValidationError(f"Unsupported media type {media_type}")
|
|
|
|
response: ServiceResponse = LIBRARY_RESULTS_SCHEMA(
|
|
{
|
|
ATTR_ITEMS: [
|
|
media_item_dict_from_mass_item(mass, item) for item in library_result
|
|
],
|
|
ATTR_LIMIT: limit,
|
|
ATTR_OFFSET: offset,
|
|
ATTR_ORDER_BY: order_by,
|
|
ATTR_MEDIA_TYPE: media_type,
|
|
}
|
|
)
|
|
return response
|