From f73ad6da6d6263cab2d8f4911bbdc721ffa82bac Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 2 Jan 2026 08:54:21 -0500 Subject: [PATCH 1/8] Remove excessive logging --- custom_components/mass_queue/actions.py | 10 +--------- custom_components/mass_queue/controller.py | 8 ++++++-- custom_components/mass_queue/websocket_commands.py | 3 --- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/custom_components/mass_queue/actions.py b/custom_components/mass_queue/actions.py index 9fc3f23..994d6ae 100644 --- a/custom_components/mass_queue/actions.py +++ b/custom_components/mass_queue/actions.py @@ -196,7 +196,6 @@ async def get_active_queue(self, entity_id: str): async def _format_queue_item(self, queue_item: dict) -> dict: """Format list of queue items for response.""" - LOGGER.debug(f"Got queue item with keys {queue_item.keys()}") media = queue_item["media_item"] queue_item_id = queue_item["queue_item_id"] @@ -224,7 +223,6 @@ async def _format_queue_item(self, queue_item: dict) -> dict: ) if local_image_encoded: response[ATTR_LOCAL_IMAGE_ENCODED] = local_image_encoded - LOGGER.debug(f"Sending back response with keys {response.keys()}") return response async def send_command(self, call: ServiceCall) -> ServiceResponse: @@ -353,13 +351,7 @@ async def get_playlist_items(self, playlist_uri: str): f"Getting playlist items for provider {provider}, item_id {item_id}", ) resp = await self._client.music.get_playlist_tracks(item_id, provider) - LOGGER.debug(f"Got response with {len(resp) if resp else 0} items") - result = [self.format_playlist_item(item.to_dict()) for item in resp] - msg = f"Got response {result[0]}" - if len(msg) > 200: - msg = f"{msg[180]}..." + "}" - LOGGER.debug(msg) - return result + return [self.format_playlist_item(item.to_dict()) for item in resp] def format_playlist_item(self, playlist_item: dict) -> dict: """Processes the individual items in a playlist.""" diff --git a/custom_components/mass_queue/controller.py b/custom_components/mass_queue/controller.py index f4250b4..276c126 100644 --- a/custom_components/mass_queue/controller.py +++ b/custom_components/mass_queue/controller.py @@ -304,7 +304,9 @@ def update(self, player_id: str, queue_id: str): def send_ha_event(self, event_data): """Send event to Home Assistant.""" - LOGGER.debug(f"Sending event type {MASS_QUEUE_EVENT_DOMAIN}, data {event_data}") + LOGGER.debug( + f"Sending event type {MASS_QUEUE_EVENT_DOMAIN}, data {event_data}", + ) self._hass.bus.async_fire(MASS_QUEUE_EVENT_DOMAIN, event_data) @@ -358,7 +360,9 @@ def remove(self, queue_id): def send_ha_event(self, event_data): """Send event to Home Assistant.""" - LOGGER.debug(f"Sending event type {MASS_QUEUE_EVENT_DOMAIN}, data {event_data}") + LOGGER.debug( + f"Sending event type {MASS_QUEUE_EVENT_DOMAIN}, data {event_data}", + ) self._hass.bus.async_fire(MASS_QUEUE_EVENT_DOMAIN, event_data) async def process_image_single_item(self, queue_item: dict): diff --git a/custom_components/mass_queue/websocket_commands.py b/custom_components/mass_queue/websocket_commands.py index 33e1eb1..4b10dd0 100644 --- a/custom_components/mass_queue/websocket_commands.py +++ b/custom_components/mass_queue/websocket_commands.py @@ -54,7 +54,6 @@ async def api_download_and_encode_image( """Download images and return them as b64 encoded.""" LOGGER.debug(f"Got message: {msg}") url = msg["url"] - LOGGER.debug(f"URL: {url}") result = await download_and_encode_image(url, hass) connection.send_result(msg["id"], result) @@ -76,8 +75,6 @@ async def api_download_images( LOGGER.debug(f"Received message: {msg}") session = aiohttp_client.async_get_clientsession(hass) images = msg["images"] - LOGGER.debug("Pulled images from message") - LOGGER.debug(images) result = [] entity_id = msg["entity_id"] for image in images: From 20e575e45cf1e908610c316e340bd4aa0aea815d Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 2 Jan 2026 08:55:05 -0500 Subject: [PATCH 2/8] Allow setting page for get playlist tracks --- custom_components/mass_queue/actions.py | 8 ++++++-- custom_components/mass_queue/const.py | 1 + custom_components/mass_queue/schemas.py | 2 ++ custom_components/mass_queue/services.py | 2 +- custom_components/mass_queue/services.yaml | 9 +++++++++ custom_components/mass_queue/strings.json | 4 ++++ custom_components/mass_queue/translations/en.json | 4 ++++ 7 files changed, 27 insertions(+), 3 deletions(-) diff --git a/custom_components/mass_queue/actions.py b/custom_components/mass_queue/actions.py index 994d6ae..a8e12a6 100644 --- a/custom_components/mass_queue/actions.py +++ b/custom_components/mass_queue/actions.py @@ -344,13 +344,17 @@ async def unfavorite_item(self, call: ServiceCall) -> ServiceResponse: library_item_id=item_id, ) - async def get_playlist_items(self, playlist_uri: str): + async def get_playlist_tracks(self, playlist_uri: str, page: int | None = None): """Retrieves all playlist items.""" provider, item_id = parse_uri(playlist_uri) LOGGER.debug( f"Getting playlist items for provider {provider}, item_id {item_id}", ) - resp = await self._client.music.get_playlist_tracks(item_id, provider) + resp = ( + await self._client.music.get_playlist_tracks(item_id, provider) + if not page + else await self._client.music.get_playlist_tracks(item_id, provider, page) + ) return [self.format_playlist_item(item.to_dict()) for item in resp] def format_playlist_item(self, playlist_item: dict) -> dict: diff --git a/custom_components/mass_queue/const.py b/custom_components/mass_queue/const.py index bb33dbf..235154d 100644 --- a/custom_components/mass_queue/const.py +++ b/custom_components/mass_queue/const.py @@ -37,6 +37,7 @@ ATTR_MEDIA_IMAGE = "media_image" ATTR_MEDIA_TITLE = "media_title" ATTR_OFFSET = "offset" +ATTR_PAGE = "page" ATTR_PLAYER_ENTITY = "entity" ATTR_URI = "uri" ATTR_PROVIDERS = "providers" diff --git a/custom_components/mass_queue/schemas.py b/custom_components/mass_queue/schemas.py index ee456f4..97a0deb 100644 --- a/custom_components/mass_queue/schemas.py +++ b/custom_components/mass_queue/schemas.py @@ -20,6 +20,7 @@ ATTR_MEDIA_IMAGE, ATTR_MEDIA_TITLE, ATTR_OFFSET, + ATTR_PAGE, ATTR_PLAYER_ENTITY, ATTR_PROVIDERS, ATTR_QUEUE_ITEM_ID, @@ -126,6 +127,7 @@ { vol.Required(ATTR_CONFIG_ENTRY_ID): str, vol.Required(ATTR_URI): str, + vol.Required(ATTR_PAGE): int, }, ) diff --git a/custom_components/mass_queue/services.py b/custom_components/mass_queue/services.py index 5546030..ad43b5c 100644 --- a/custom_components/mass_queue/services.py +++ b/custom_components/mass_queue/services.py @@ -272,5 +272,5 @@ async def get_playlist_tracks(call: ServiceCall): entry = hass.config_entries.async_get_entry(config_entry) actions = entry.runtime_data.actions return { - "tracks": await actions.get_playlist_items(uri), + "tracks": await actions.get_playlist_tracks(uri), } diff --git a/custom_components/mass_queue/services.yaml b/custom_components/mass_queue/services.yaml index 438b9ef..dce76d9 100644 --- a/custom_components/mass_queue/services.yaml +++ b/custom_components/mass_queue/services.yaml @@ -232,3 +232,12 @@ get_playlist_tracks: text: example: "library://playlist/12" required: true + page: + selector: + number: + min: 0 + max: 1000 + step: 1 + mode: box + required: false + example: 0 diff --git a/custom_components/mass_queue/strings.json b/custom_components/mass_queue/strings.json index 7914a32..361bffe 100644 --- a/custom_components/mass_queue/strings.json +++ b/custom_components/mass_queue/strings.json @@ -254,6 +254,10 @@ "uri": { "name": "Playlist URI", "description": "URI for the playlist." + }, + "page": { + "name": "Page", + "description": "Page of results to return. If not provided, returns all." } } } diff --git a/custom_components/mass_queue/translations/en.json b/custom_components/mass_queue/translations/en.json index efb1b55..022e73c 100644 --- a/custom_components/mass_queue/translations/en.json +++ b/custom_components/mass_queue/translations/en.json @@ -230,6 +230,10 @@ "uri": { "name": "Playlist URI", "description": "URI for the playlist." + }, + "page": { + "name": "Page", + "description": "Page of results to return. If not provided, returns all." } } } From 377bde9dac9bf3b97361d431f8119f9bb72aa396 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 2 Jan 2026 09:17:26 -0500 Subject: [PATCH 3/8] Add support for getting artist, album tracks --- custom_components/mass_queue/actions.py | 61 +++++++++++++++++-- custom_components/mass_queue/const.py | 2 + custom_components/mass_queue/icons.json | 2 + custom_components/mass_queue/schemas.py | 4 +- custom_components/mass_queue/services.py | 44 ++++++++++++- custom_components/mass_queue/services.yaml | 48 +++++++++++++++ custom_components/mass_queue/strings.json | 36 +++++++++++ .../mass_queue/translations/en.json | 36 +++++++++++ 8 files changed, 225 insertions(+), 8 deletions(-) diff --git a/custom_components/mass_queue/actions.py b/custom_components/mass_queue/actions.py index a8e12a6..a3492bd 100644 --- a/custom_components/mass_queue/actions.py +++ b/custom_components/mass_queue/actions.py @@ -17,6 +17,7 @@ from music_assistant_models.errors import ( InvalidCommand, MediaNotFoundError, + ProviderUnavailableError, ) from .const import ( @@ -62,12 +63,12 @@ MOVE_QUEUE_ITEM_NEXT_SERVICE_SCHEMA, MOVE_QUEUE_ITEM_UP_SERVICE_SCHEMA, PLAY_QUEUE_ITEM_SERVICE_SCHEMA, - PLAYLIST_ITEM_SCHEMA, QUEUE_ITEM_SCHEMA, QUEUE_ITEMS_SERVICE_SCHEMA, REMOVE_QUEUE_ITEM_SERVICE_SCHEMA, SEND_COMMAND_SERVICE_SCHEMA, SET_GROUP_VOLUME_SERVICE_SCHEMA, + TRACK_ITEM_SCHEMA, ) from .utils import ( find_image, @@ -344,6 +345,58 @@ async def unfavorite_item(self, call: ServiceCall) -> ServiceResponse: library_item_id=item_id, ) + async def get_artist_details(self, artist_uri): + """Retrieves the details for an artist.""" + provider, item_id = parse_uri(artist_uri) + LOGGER.debug(f"Getting artist details for provider {provider}") + return await self._client.music.get_album(item_id, provider) + + async def get_album_details(self, album_uri): + """Retrieves the details for an album.""" + provider, item_id = parse_uri(album_uri) + LOGGER.debug(f"Getting album details for provider {provider}") + return await self._client.music.get_album(item_id, provider) + + async def get_playlist_details(self, playlist_uri): + """Retrieves the details for a playlist.""" + provider, item_id = parse_uri(playlist_uri) + LOGGER.debug(f"Getting album details for provider {provider}") + return await self._client.music.get_album(item_id, provider) + + async def get_artist_tracks(self, artist_uri: str, page: int | None = None): + """Retrieves a limited number of tracks from an artist.""" + details = await self.get_artist_details(artist_uri) + mappings = list(details.provider_mappings) + if not len(mappings) > 0: + msg = f"URI {artist_uri} returned no results!" + raise ProviderUnavailableError(msg) + mapping = mappings[0] + item_id = mapping.item_id + provider = mapping.provider_domain + resp = ( + await self._client.music.get_artist_tracks(item_id, provider) + if not page + else await self._client.music.get_artist_tracks(item_id, provider, page) + ) + return [self.format_track_item(item.to_dict()) for item in resp] + + async def get_album_tracks(self, album_uri: str, page: int | None = None): + """Retrieves all tracks from an album.""" + details = await self.get_album_details(album_uri) + mappings = list(details.provider_mappings) + if not len(mappings) > 0: + msg = f"URI {album_uri} returned no results!" + raise ProviderUnavailableError(msg) + mapping = mappings[0] + item_id = mapping.item_id + provider = mapping.provider_domain + resp = ( + await self._client.music.get_album_tracks(item_id, provider) + if not page + else await self._client.music.get_album_tracks(item_id, provider, page) + ) + return [self.format_track_item(item.to_dict()) for item in resp] + async def get_playlist_tracks(self, playlist_uri: str, page: int | None = None): """Retrieves all playlist items.""" provider, item_id = parse_uri(playlist_uri) @@ -355,9 +408,9 @@ async def get_playlist_tracks(self, playlist_uri: str, page: int | None = None): if not page else await self._client.music.get_playlist_tracks(item_id, provider, page) ) - return [self.format_playlist_item(item.to_dict()) for item in resp] + return [self.format_track_item(item.to_dict()) for item in resp] - def format_playlist_item(self, playlist_item: dict) -> dict: + def format_track_item(self, playlist_item: dict) -> dict: """Processes the individual items in a playlist.""" media_title = playlist_item.get("name") or "N/A" media_album = playlist_item.get("album") or "N/A" @@ -370,7 +423,7 @@ def format_playlist_item(self, playlist_item: dict) -> dict: artists = playlist_item["artists"] artist_names = [artist["name"] for artist in artists] media_artist = ", ".join(artist_names) - response: ServiceResponse = PLAYLIST_ITEM_SCHEMA( + response: ServiceResponse = TRACK_ITEM_SCHEMA( { ATTR_MEDIA_TITLE: media_title, ATTR_MEDIA_ALBUM_NAME: media_album_name, diff --git a/custom_components/mass_queue/const.py b/custom_components/mass_queue/const.py index 235154d..dfe1c06 100644 --- a/custom_components/mass_queue/const.py +++ b/custom_components/mass_queue/const.py @@ -11,6 +11,8 @@ DEFAULT_NAME = "Music Assistant Queue Items" SERVICE_CLEAR_QUEUE_FROM_HERE = "clear_queue_from_here" SERVICE_GET_GROUP_VOLUME = "get_group_volume" +SERVICE_GET_ALBUM_TRACKS = "get_album_tracks" +SERVICE_GET_ARTIST_TRACKS = "get_artist_tracks" SERVICE_GET_PLAYLIST_TRACKS = "get_playlist_tracks" SERVICE_GET_QUEUE_ITEMS = "get_queue_items" SERVICE_GET_RECOMMENDATIONS = "get_recommendations" diff --git a/custom_components/mass_queue/icons.json b/custom_components/mass_queue/icons.json index 49f5f47..16dfcac 100644 --- a/custom_components/mass_queue/icons.json +++ b/custom_components/mass_queue/icons.json @@ -1,5 +1,7 @@ { "services": { + "get_album_tracks": { "service": "mdi:album"}, + "get_artist_tracks": { "service": "mdi:account-music"}, "get_playlist_tracks": { "service": "mdi:playlist-music"}, "get_queue_items": { "service": "mdi:playlist-music" }, "move_queue_item_down": { "service": "mdi:arrow-down" }, diff --git a/custom_components/mass_queue/schemas.py b/custom_components/mass_queue/schemas.py index 97a0deb..1614b3b 100644 --- a/custom_components/mass_queue/schemas.py +++ b/custom_components/mass_queue/schemas.py @@ -54,7 +54,7 @@ }, ) -PLAYLIST_ITEM_SCHEMA = vol.Schema( +TRACK_ITEM_SCHEMA = vol.Schema( { vol.Required(ATTR_MEDIA_TITLE): str, vol.Required(ATTR_MEDIA_ALBUM_NAME): str, @@ -123,7 +123,7 @@ }, ) -GET_PLAYLIST_TRACKS_SERVICE_SCHEMA = vol.Schema( +GET_TRACKS_SERVICE_SCHEMA = vol.Schema( { vol.Required(ATTR_CONFIG_ENTRY_ID): str, vol.Required(ATTR_URI): str, diff --git a/custom_components/mass_queue/services.py b/custom_components/mass_queue/services.py index ad43b5c..fac23a3 100644 --- a/custom_components/mass_queue/services.py +++ b/custom_components/mass_queue/services.py @@ -16,6 +16,8 @@ DOMAIN, LOGGER, SERVICE_CLEAR_QUEUE_FROM_HERE, + SERVICE_GET_ALBUM_TRACKS, + SERVICE_GET_ARTIST_TRACKS, SERVICE_GET_GROUP_VOLUME, SERVICE_GET_PLAYLIST_TRACKS, SERVICE_GET_QUEUE_ITEMS, @@ -32,8 +34,8 @@ from .schemas import ( CLEAR_QUEUE_FROM_HERE_SERVICE_SCHEMA, GET_GROUP_VOLUME_SERVICE_SCHEMA, - GET_PLAYLIST_TRACKS_SERVICE_SCHEMA, GET_RECOMMENDATIONS_SERVICE_SCHEMA, + GET_TRACKS_SERVICE_SCHEMA, MOVE_QUEUE_ITEM_DOWN_SERVICE_SCHEMA, MOVE_QUEUE_ITEM_NEXT_SERVICE_SCHEMA, MOVE_QUEUE_ITEM_UP_SERVICE_SCHEMA, @@ -138,7 +140,21 @@ def register_actions(hass) -> None: DOMAIN, SERVICE_GET_PLAYLIST_TRACKS, get_playlist_tracks, - schema=GET_PLAYLIST_TRACKS_SERVICE_SCHEMA, + schema=GET_TRACKS_SERVICE_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) + hass.services.async_register( + DOMAIN, + SERVICE_GET_ALBUM_TRACKS, + get_album_tracks, + schema=GET_TRACKS_SERVICE_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) + hass.services.async_register( + DOMAIN, + SERVICE_GET_ARTIST_TRACKS, + get_artist_tracks, + schema=GET_TRACKS_SERVICE_SCHEMA, supports_response=SupportsResponse.ONLY, ) @@ -264,6 +280,30 @@ async def clear_queue_from_here(call: ServiceCall): await client.player_queues.queue_command_delete(queue_id, queue_item_id) +async def get_album_tracks(call: ServiceCall): + """Gets all tracks in an album.""" + config_entry = call.data[ATTR_CONFIG_ENTRY_ID] + uri = call.data[ATTR_URI] + hass = call.hass + entry = hass.config_entries.async_get_entry(config_entry) + actions = entry.runtime_data.actions + return { + "tracks": await actions.get_album_tracks(uri), + } + + +async def get_artist_tracks(call: ServiceCall): + """Gets all tracks for an artist.""" + config_entry = call.data[ATTR_CONFIG_ENTRY_ID] + uri = call.data[ATTR_URI] + hass = call.hass + entry = hass.config_entries.async_get_entry(config_entry) + actions = entry.runtime_data.actions + return { + "tracks": await actions.get_artist_tracks(uri), + } + + async def get_playlist_tracks(call: ServiceCall): """Gets all tracks in a playlist.""" config_entry = call.data[ATTR_CONFIG_ENTRY_ID] diff --git a/custom_components/mass_queue/services.yaml b/custom_components/mass_queue/services.yaml index dce76d9..014510a 100644 --- a/custom_components/mass_queue/services.yaml +++ b/custom_components/mass_queue/services.yaml @@ -241,3 +241,51 @@ get_playlist_tracks: mode: box required: false example: 0 +get_album_tracks: + fields: + config_entry_id: + name: Config Entry ID + required: true + selector: + config_entry: + integration: mass_queue + uri: + name: Album URI + description: URI for the album + selector: + text: + example: "library://album/12" + required: true + page: + selector: + number: + min: 0 + max: 1000 + step: 1 + mode: box + required: false + example: 0 +get_artist_tracks: + fields: + config_entry_id: + name: Config Entry ID + required: true + selector: + config_entry: + integration: mass_queue + uri: + name: Artist URI + description: URI for the artist + selector: + text: + example: "library://artist/12" + required: true + page: + selector: + number: + min: 0 + max: 1000 + step: 1 + mode: box + required: false + example: 0 diff --git a/custom_components/mass_queue/strings.json b/custom_components/mass_queue/strings.json index 361bffe..8e6f235 100644 --- a/custom_components/mass_queue/strings.json +++ b/custom_components/mass_queue/strings.json @@ -260,6 +260,42 @@ "description": "Page of results to return. If not provided, returns all." } } + }, + "get_album_tracks": { + "name": "Get Album Tracks", + "description": "Returns all tracks for the album given.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Album URI", + "description": "URI for the album." + }, + "page": { + "name": "Page", + "description": "Page of results to return. If not provided, returns all." + } + } + }, + "get_artist_tracks": { + "name": "Get Artist Tracks", + "description": "Returns all tracks for the artist given.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Artist URI", + "description": "URI for the artist." + }, + "page": { + "name": "Page", + "description": "Page of results to return. If not provided, returns all." + } + } } } } diff --git a/custom_components/mass_queue/translations/en.json b/custom_components/mass_queue/translations/en.json index 022e73c..d8008ac 100644 --- a/custom_components/mass_queue/translations/en.json +++ b/custom_components/mass_queue/translations/en.json @@ -236,6 +236,42 @@ "description": "Page of results to return. If not provided, returns all." } } + }, + "get_album_tracks": { + "name": "Get Album Tracks", + "description": "Returns all tracks for the album given.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Album URI", + "description": "URI for the album." + }, + "page": { + "name": "Page", + "description": "Page of results to return. If not provided, returns all." + } + } + }, + "get_artist_tracks": { + "name": "Get Artist Tracks", + "description": "Returns all tracks for the artist given.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Artist URI", + "description": "URI for the artist." + }, + "page": { + "name": "Page", + "description": "Page of results to return. If not provided, returns all." + } + } } } } From 5ce5f4e73f79b2e0a376efa2856028909f3f7b2d Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 2 Jan 2026 09:39:09 -0500 Subject: [PATCH 4/8] Add services to get details for albums, artists, and playlists --- custom_components/mass_queue/const.py | 3 + custom_components/mass_queue/icons.json | 3 + custom_components/mass_queue/schemas.py | 7 +++ custom_components/mass_queue/services.py | 55 +++++++++++++++++++ custom_components/mass_queue/services.yaml | 45 +++++++++++++++ custom_components/mass_queue/strings.json | 42 ++++++++++++++ .../mass_queue/translations/en.json | 42 ++++++++++++++ 7 files changed, 197 insertions(+) diff --git a/custom_components/mass_queue/const.py b/custom_components/mass_queue/const.py index dfe1c06..73d1094 100644 --- a/custom_components/mass_queue/const.py +++ b/custom_components/mass_queue/const.py @@ -11,8 +11,11 @@ DEFAULT_NAME = "Music Assistant Queue Items" SERVICE_CLEAR_QUEUE_FROM_HERE = "clear_queue_from_here" SERVICE_GET_GROUP_VOLUME = "get_group_volume" +SERVICE_GET_ALBUM = "get_album_" SERVICE_GET_ALBUM_TRACKS = "get_album_tracks" +SERVICE_GET_ARTIST = "get_artist" SERVICE_GET_ARTIST_TRACKS = "get_artist_tracks" +SERVICE_GET_PLAYLIST = "get_playlist" SERVICE_GET_PLAYLIST_TRACKS = "get_playlist_tracks" SERVICE_GET_QUEUE_ITEMS = "get_queue_items" SERVICE_GET_RECOMMENDATIONS = "get_recommendations" diff --git a/custom_components/mass_queue/icons.json b/custom_components/mass_queue/icons.json index 16dfcac..da0e38b 100644 --- a/custom_components/mass_queue/icons.json +++ b/custom_components/mass_queue/icons.json @@ -1,7 +1,10 @@ { "services": { + "get_album": { "service": "mdi:album"}, "get_album_tracks": { "service": "mdi:album"}, + "get_artist": { "service": "mdi:account-music"}, "get_artist_tracks": { "service": "mdi:account-music"}, + "get_playlist": { "service": "mdi:playlist-music"}, "get_playlist_tracks": { "service": "mdi:playlist-music"}, "get_queue_items": { "service": "mdi:playlist-music" }, "move_queue_item_down": { "service": "mdi:arrow-down" }, diff --git a/custom_components/mass_queue/schemas.py b/custom_components/mass_queue/schemas.py index 1614b3b..ecd6c99 100644 --- a/custom_components/mass_queue/schemas.py +++ b/custom_components/mass_queue/schemas.py @@ -131,6 +131,13 @@ }, ) +GET_DATA_SERVICE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_CONFIG_ENTRY_ID): str, + vol.Required(ATTR_URI): str, + }, +) + GET_RECOMMENDATIONS_SERVICE_SCHEMA = vol.Schema( { vol.Required(ATTR_PLAYER_ENTITY): str, diff --git a/custom_components/mass_queue/services.py b/custom_components/mass_queue/services.py index fac23a3..8489d61 100644 --- a/custom_components/mass_queue/services.py +++ b/custom_components/mass_queue/services.py @@ -16,9 +16,12 @@ DOMAIN, LOGGER, SERVICE_CLEAR_QUEUE_FROM_HERE, + SERVICE_GET_ALBUM, SERVICE_GET_ALBUM_TRACKS, + SERVICE_GET_ARTIST, SERVICE_GET_ARTIST_TRACKS, SERVICE_GET_GROUP_VOLUME, + SERVICE_GET_PLAYLIST, SERVICE_GET_PLAYLIST_TRACKS, SERVICE_GET_QUEUE_ITEMS, SERVICE_GET_RECOMMENDATIONS, @@ -33,6 +36,7 @@ ) from .schemas import ( CLEAR_QUEUE_FROM_HERE_SERVICE_SCHEMA, + GET_DATA_SERVICE_SCHEMA, GET_GROUP_VOLUME_SERVICE_SCHEMA, GET_RECOMMENDATIONS_SERVICE_SCHEMA, GET_TRACKS_SERVICE_SCHEMA, @@ -157,6 +161,27 @@ def register_actions(hass) -> None: schema=GET_TRACKS_SERVICE_SCHEMA, supports_response=SupportsResponse.ONLY, ) + hass.services.async_register( + DOMAIN, + SERVICE_GET_ALBUM, + get_album, + schema=GET_DATA_SERVICE_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) + hass.services.async_register( + DOMAIN, + SERVICE_GET_ARTIST, + get_artist, + schema=GET_DATA_SERVICE_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) + hass.services.async_register( + DOMAIN, + SERVICE_GET_PLAYLIST, + get_playlist, + schema=GET_DATA_SERVICE_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) async def get_queue_items(call: ServiceCall): @@ -314,3 +339,33 @@ async def get_playlist_tracks(call: ServiceCall): return { "tracks": await actions.get_playlist_tracks(uri), } + + +async def get_album(call: ServiceCall): + """Returns the details about an album from the server.""" + config_entry = call.data[ATTR_CONFIG_ENTRY_ID] + uri = call.data[ATTR_URI] + hass = call.hass + entry = hass.config_entries.async_get_entry(config_entry) + actions = entry.runtime_data.actions + return (await actions.get_album_details(uri)).to_dict() + + +async def get_artist(call: ServiceCall): + """Returns the details about an artist from the server.""" + config_entry = call.data[ATTR_CONFIG_ENTRY_ID] + uri = call.data[ATTR_URI] + hass = call.hass + entry = hass.config_entries.async_get_entry(config_entry) + actions = entry.runtime_data.actions + return (await actions.get_artist_details(uri)).to_dict() + + +async def get_playlist(call: ServiceCall): + """Returns the details about a playlist from the server.""" + config_entry = call.data[ATTR_CONFIG_ENTRY_ID] + uri = call.data[ATTR_URI] + hass = call.hass + entry = hass.config_entries.async_get_entry(config_entry) + actions = entry.runtime_data.actions + return (await actions.get_playlist_details(uri)).to_dict() diff --git a/custom_components/mass_queue/services.yaml b/custom_components/mass_queue/services.yaml index 014510a..86c39dd 100644 --- a/custom_components/mass_queue/services.yaml +++ b/custom_components/mass_queue/services.yaml @@ -289,3 +289,48 @@ get_artist_tracks: mode: box required: false example: 0 +get_album: + fields: + config_entry_id: + name: Config Entry ID + required: true + selector: + config_entry: + integration: mass_queue + uri: + name: Album URI + description: URI for the Album + selector: + text: + example: "library://album/12" + required: true +get_artist: + fields: + config_entry_id: + name: Config Entry ID + required: true + selector: + config_entry: + integration: mass_queue + uri: + name: Artist URI + description: URI for the artist + selector: + text: + example: "library://artist/12" + required: true +get_playlist: + fields: + config_entry_id: + name: Config Entry ID + required: true + selector: + config_entry: + integration: mass_queue + uri: + name: Playlist URI + description: URI for the playlist + selector: + text: + example: "library://playlist/12" + required: true diff --git a/custom_components/mass_queue/strings.json b/custom_components/mass_queue/strings.json index 8e6f235..8ac262f 100644 --- a/custom_components/mass_queue/strings.json +++ b/custom_components/mass_queue/strings.json @@ -296,6 +296,48 @@ "description": "Page of results to return. If not provided, returns all." } } + }, + "get_playlist": { + "name": "Get Playlist", + "description": "Returns information about a playlist from the server.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Playlist URI", + "description": "URI for the playlist." + } + } + }, + "get_album": { + "name": "Get Album", + "description": "Returns information about an album from the server.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Album URI", + "description": "URI for the album." + } + } + }, + "get_artist": { + "name": "Get Artist", + "description": "Returns information about an artist from the server.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Artist URI", + "description": "URI for the artist." + } + } } } } diff --git a/custom_components/mass_queue/translations/en.json b/custom_components/mass_queue/translations/en.json index d8008ac..21f103c 100644 --- a/custom_components/mass_queue/translations/en.json +++ b/custom_components/mass_queue/translations/en.json @@ -272,6 +272,48 @@ "description": "Page of results to return. If not provided, returns all." } } + }, + "get_playlist": { + "name": "Get Playlist", + "description": "Returns information about a playlist from the server.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Playlist URI", + "description": "URI for the playlist." + } + } + }, + "get_album": { + "name": "Get Album", + "description": "Returns information about an album from the server.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Album URI", + "description": "URI for the album." + } + } + }, + "get_artist": { + "name": "Get Artist", + "description": "Returns information about an artist from the server.", + "fields": { + "config_entry_id": { + "name": "Config Entry ID", + "description": "Config Entry ID for the Music Assistant Queue Items instance." + }, + "uri": { + "name": "Artist URI", + "description": "URI for the artist." + } + } } } } From 66c4c555e949c20e1553d4868a6041ef750fa419 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 2 Jan 2026 10:04:45 -0500 Subject: [PATCH 5/8] Pull correct details --- custom_components/mass_queue/actions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mass_queue/actions.py b/custom_components/mass_queue/actions.py index a3492bd..ac6ca61 100644 --- a/custom_components/mass_queue/actions.py +++ b/custom_components/mass_queue/actions.py @@ -349,7 +349,7 @@ async def get_artist_details(self, artist_uri): """Retrieves the details for an artist.""" provider, item_id = parse_uri(artist_uri) LOGGER.debug(f"Getting artist details for provider {provider}") - return await self._client.music.get_album(item_id, provider) + return await self._client.music.get_artist(item_id, provider) async def get_album_details(self, album_uri): """Retrieves the details for an album.""" @@ -361,7 +361,7 @@ async def get_playlist_details(self, playlist_uri): """Retrieves the details for a playlist.""" provider, item_id = parse_uri(playlist_uri) LOGGER.debug(f"Getting album details for provider {provider}") - return await self._client.music.get_album(item_id, provider) + return await self._client.music.get_playlist(item_id, provider) async def get_artist_tracks(self, artist_uri: str, page: int | None = None): """Retrieves a limited number of tracks from an artist.""" From 766775f0b2d885b0418f1664bbaf75647cfe64b9 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 2 Jan 2026 10:21:50 -0500 Subject: [PATCH 6/8] Set page to optional --- custom_components/mass_queue/schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mass_queue/schemas.py b/custom_components/mass_queue/schemas.py index ecd6c99..ba5f82c 100644 --- a/custom_components/mass_queue/schemas.py +++ b/custom_components/mass_queue/schemas.py @@ -127,7 +127,7 @@ { vol.Required(ATTR_CONFIG_ENTRY_ID): str, vol.Required(ATTR_URI): str, - vol.Required(ATTR_PAGE): int, + vol.Optional(ATTR_PAGE): int, }, ) From a255bc6759078314456b285128d9f59fa49e5810 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 2 Jan 2026 10:30:15 -0500 Subject: [PATCH 7/8] Fix: Service name --- custom_components/mass_queue/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mass_queue/const.py b/custom_components/mass_queue/const.py index 73d1094..447332e 100644 --- a/custom_components/mass_queue/const.py +++ b/custom_components/mass_queue/const.py @@ -11,7 +11,7 @@ DEFAULT_NAME = "Music Assistant Queue Items" SERVICE_CLEAR_QUEUE_FROM_HERE = "clear_queue_from_here" SERVICE_GET_GROUP_VOLUME = "get_group_volume" -SERVICE_GET_ALBUM = "get_album_" +SERVICE_GET_ALBUM = "get_album" SERVICE_GET_ALBUM_TRACKS = "get_album_tracks" SERVICE_GET_ARTIST = "get_artist" SERVICE_GET_ARTIST_TRACKS = "get_artist_tracks" From 20327c26033120bcc3fe8c7f9527dd002cb204e4 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Sat, 3 Jan 2026 22:11:26 -0500 Subject: [PATCH 8/8] Report duration --- custom_components/mass_queue/actions.py | 5 ++++- custom_components/mass_queue/const.py | 1 + custom_components/mass_queue/schemas.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/custom_components/mass_queue/actions.py b/custom_components/mass_queue/actions.py index ac6ca61..6840521 100644 --- a/custom_components/mass_queue/actions.py +++ b/custom_components/mass_queue/actions.py @@ -23,6 +23,7 @@ from .const import ( ATTR_COMMAND, ATTR_DATA, + ATTR_DURATION, ATTR_FAVORITE, ATTR_LIMIT, ATTR_LIMIT_AFTER, @@ -410,7 +411,7 @@ async def get_playlist_tracks(self, playlist_uri: str, page: int | None = None): ) return [self.format_track_item(item.to_dict()) for item in resp] - def format_track_item(self, playlist_item: dict) -> dict: + def format_track_item(self, playlist_item: dict) -> TRACK_ITEM_SCHEMA: """Processes the individual items in a playlist.""" media_title = playlist_item.get("name") or "N/A" media_album = playlist_item.get("album") or "N/A" @@ -419,6 +420,7 @@ def format_track_item(self, playlist_item: dict) -> dict: media_image = find_image(playlist_item) or "" local_image_encoded = playlist_item.get(ATTR_LOCAL_IMAGE_ENCODED) favorite = playlist_item["favorite"] + duration = playlist_item["duration"] or 0 artists = playlist_item["artists"] artist_names = [artist["name"] for artist in artists] @@ -429,6 +431,7 @@ def format_track_item(self, playlist_item: dict) -> dict: ATTR_MEDIA_ALBUM_NAME: media_album_name, ATTR_MEDIA_ARTIST: media_artist, ATTR_MEDIA_CONTENT_ID: media_content_id, + ATTR_DURATION: duration, ATTR_MEDIA_IMAGE: media_image, ATTR_FAVORITE: favorite, }, diff --git a/custom_components/mass_queue/const.py b/custom_components/mass_queue/const.py index 447332e..1d0ae11 100644 --- a/custom_components/mass_queue/const.py +++ b/custom_components/mass_queue/const.py @@ -31,6 +31,7 @@ ATTR_COMMAND = "command" ATTR_CONFIG_ENTRY_ID = "config_entry_id" ATTR_DATA = "data" +ATTR_DURATION = "duration" ATTR_FAVORITE = "favorite" ATTR_LIMIT = "limit" ATTR_LIMIT_AFTER = "limit_after" diff --git a/custom_components/mass_queue/schemas.py b/custom_components/mass_queue/schemas.py index ba5f82c..815e8d5 100644 --- a/custom_components/mass_queue/schemas.py +++ b/custom_components/mass_queue/schemas.py @@ -9,6 +9,7 @@ ATTR_COMMAND, ATTR_CONFIG_ENTRY_ID, ATTR_DATA, + ATTR_DURATION, ATTR_FAVORITE, ATTR_LIMIT, ATTR_LIMIT_AFTER, @@ -62,6 +63,7 @@ vol.Required(ATTR_MEDIA_CONTENT_ID): str, vol.Required(ATTR_MEDIA_IMAGE): str, vol.Required(ATTR_FAVORITE): bool, + vol.Required(ATTR_DURATION): vol.Any(int, None), vol.Optional(ATTR_LOCAL_IMAGE_ENCODED): str, }, )