From dc3cf11dfc1b37d64fc3138f5e2a36e450a4587d Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Mon, 3 Nov 2025 10:50:17 -0500 Subject: [PATCH 1/8] Sort --- custom_components/mass_queue/const.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/custom_components/mass_queue/const.py b/custom_components/mass_queue/const.py index 7cd76c1..b8c3b1b 100644 --- a/custom_components/mass_queue/const.py +++ b/custom_components/mass_queue/const.py @@ -8,32 +8,33 @@ SERVICE_GET_QUEUE_ITEMS = "get_queue_items" SERVICE_GET_RECOMMENDATIONS = "get_recommendations" SERVICE_PLAY_QUEUE_ITEM = "play_queue_item" -SERVICE_REMOVE_QUEUE_ITEM = "remove_queue_item" -SERVICE_MOVE_QUEUE_ITEM_UP = "move_queue_item_up" SERVICE_MOVE_QUEUE_ITEM_DOWN = "move_queue_item_down" SERVICE_MOVE_QUEUE_ITEM_NEXT = "move_queue_item_next" +SERVICE_MOVE_QUEUE_ITEM_UP = "move_queue_item_up" +SERVICE_REMOVE_QUEUE_ITEM = "remove_queue_item" SERVICE_SEND_COMMAND = "send_command" SERVICE_SET_GROUP_VOLUME = "set_group_volume" SERVICE_UNFAVORITE_CURRENT_ITEM = "unfavorite_current_item" -ATTR_QUEUE_ID = "active_queue" + +ATTR_COMMAND = "command" ATTR_CONFIG_ENTRY_ID = "config_entry_id" -ATTR_LOCAL_IMAGE_ENCODED = "local_image_encoded" +ATTR_DATA = "data" +ATTR_FAVORITE = "favorite" ATTR_LIMIT = "limit" ATTR_LIMIT_AFTER = "limit_after" ATTR_LIMIT_BEFORE = "limit_before" -ATTR_QUEUE_ITEM_ID = "queue_item_id" -ATTR_MEDIA_TITLE = "media_title" +ATTR_LOCAL_IMAGE_ENCODED = "local_image_encoded" ATTR_MEDIA_ALBUM_NAME = "media_album_name" ATTR_MEDIA_ARTIST = "media_artist" ATTR_MEDIA_CONTENT_ID = "media_content_id" ATTR_MEDIA_IMAGE = "media_image" +ATTR_MEDIA_TITLE = "media_title" ATTR_OFFSET = "offset" ATTR_PLAYER_ENTITY = "entity" -ATTR_QUEUE_ITEMS = "queue_items" -ATTR_COMMAND = "command" -ATTR_DATA = "data" -ATTR_FAVORITE = "favorite" ATTR_PROVIDERS = "providers" +ATTR_QUEUE_ID = "active_queue" +ATTR_QUEUE_ITEM_ID = "queue_item_id" +ATTR_QUEUE_ITEMS = "queue_items" ATTR_VOLUME_LEVEL = "volume_level" CONF_DOWNLOAD_LOCAL = "download_local" From 906dd4ca6bae9ce8488143abfaa7369eac7f129c Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Mon, 3 Nov 2025 11:44:24 -0500 Subject: [PATCH 2/8] Remove excessive logging --- custom_components/mass_queue/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/custom_components/mass_queue/utils.py b/custom_components/mass_queue/utils.py index 5220081..d12d5df 100644 --- a/custom_components/mass_queue/utils.py +++ b/custom_components/mass_queue/utils.py @@ -180,8 +180,8 @@ def _get_recommendation_item_image_from_metadata(item: dict): accessible = [image for image in images if image["remotely_accessible"]] if accessible: return accessible[0]["path"] - except: # noqa: E722 - LOGGER.debug(f"Unable to get images for item {item} from metadata.") + except: # noqa: E722 S110 + pass return "" @@ -191,8 +191,8 @@ def _get_recommendation_item_image_from_image(item: dict): accessible = image_data["remotely_accessible"] if accessible: return image_data["path"] - except: # noqa: E722 - LOGGER.debug(f"Unable to get images for item {item} from image.") + except: # noqa: E722 S110 + pass return "" @@ -206,7 +206,6 @@ def _get_recommendation_item_image(item: dict): def process_recommendation_section_item(item: dict): """Process and reformat a single recommendation item.""" - LOGGER.debug(f"Got section item: {item}") return { "item_id": item["item_id"], "name": item["name"], From 8e67872f1b1327599b276a425c0def0cedbe7fe9 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Mon, 3 Nov 2025 11:46:34 -0500 Subject: [PATCH 3/8] Add Clear Queue From Here --- custom_components/mass_queue/const.py | 1 + custom_components/mass_queue/icons.json | 3 +- custom_components/mass_queue/schemas.py | 6 +++ custom_components/mass_queue/services.py | 39 +++++++++++++++++++ custom_components/mass_queue/services.yaml | 11 ++++++ custom_components/mass_queue/strings.json | 10 +++++ .../mass_queue/translations/en.json | 10 +++++ 7 files changed, 79 insertions(+), 1 deletion(-) diff --git a/custom_components/mass_queue/const.py b/custom_components/mass_queue/const.py index b8c3b1b..4a9051a 100644 --- a/custom_components/mass_queue/const.py +++ b/custom_components/mass_queue/const.py @@ -4,6 +4,7 @@ DOMAIN = "mass_queue" DEFAULT_NAME = "Music Assistant Queue Items" +SERVICE_CLEAR_QUEUE_FROM_HERE = "clear_queue_from_here" SERVICE_GET_GROUP_VOLUME = "get_group_volume" 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 3ae21ce..5f38299 100644 --- a/custom_components/mass_queue/icons.json +++ b/custom_components/mass_queue/icons.json @@ -10,6 +10,7 @@ "unfavorite_current_item": { "service": "mdi:heart-remove" }, "get_recommendations": { "service": "mdi:creation"}, "get_group_volume": { "service": "mdi:speaker-multiple"}, - "set_group_volume": { "service": "mdi:speaker-multiple"} + "set_group_volume": { "service": "mdi:speaker-multiple"}, + "clear_queue_from_here": { "service": "mdi:playlist-remove"} } } diff --git a/custom_components/mass_queue/schemas.py b/custom_components/mass_queue/schemas.py index 16b6a25..5185e4d 100644 --- a/custom_components/mass_queue/schemas.py +++ b/custom_components/mass_queue/schemas.py @@ -27,6 +27,12 @@ ATTR_VOLUME_LEVEL, ) +CLEAR_QUEUE_FROM_HERE_SERVICE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_PLAYER_ENTITY): str, + }, +) + GET_GROUP_VOLUME_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 bdf7aad..06770e8 100644 --- a/custom_components/mass_queue/services.py +++ b/custom_components/mass_queue/services.py @@ -11,7 +11,10 @@ from .const import ( ATTR_CONFIG_ENTRY_ID, ATTR_PLAYER_ENTITY, + ATTR_QUEUE_ITEM_ID, DOMAIN, + LOGGER, + SERVICE_CLEAR_QUEUE_FROM_HERE, SERVICE_GET_GROUP_VOLUME, SERVICE_GET_QUEUE_ITEMS, SERVICE_GET_RECOMMENDATIONS, @@ -25,6 +28,7 @@ SERVICE_UNFAVORITE_CURRENT_ITEM, ) from .schemas import ( + CLEAR_QUEUE_FROM_HERE_SERVICE_SCHEMA, GET_GROUP_VOLUME_SERVICE_SCHEMA, GET_RECOMMENDATIONS_SERVICE_SCHEMA, MOVE_QUEUE_ITEM_DOWN_SERVICE_SCHEMA, @@ -120,6 +124,13 @@ def register_actions(hass) -> None: schema=SET_GROUP_VOLUME_SERVICE_SCHEMA, supports_response=SupportsResponse.NONE, ) + hass.services.async_register( + DOMAIN, + SERVICE_CLEAR_QUEUE_FROM_HERE, + clear_queue_from_here, + schema=CLEAR_QUEUE_FROM_HERE_SERVICE_SCHEMA, + supports_response=SupportsResponse.NONE, + ) async def get_queue_items(call: ServiceCall): @@ -211,3 +222,31 @@ async def set_group_volume(call: ServiceCall): hass = call.hass actions = get_entity_actions_controller(hass, entity_id) await actions.set_group_volume(call) + +def filter_queue_after(queue, current_idx): + """Returns all items after the current active track.""" + if current_idx == len(queue): + return [] + return queue[current_idx + 1:] + +async def clear_queue_from_here(call: ServiceCall): + """Service wrapper to clear queue from point.""" + entity_id = call.data[ATTR_PLAYER_ENTITY] + hass = call.hass + actions = get_entity_actions_controller(hass, entity_id) + current_idx = await actions.get_queue_index(entity_id) + LOGGER.debug(f"Current Index: {current_idx}") + queue_id = actions.get_queue_id(entity_id) + LOGGER.debug(f"Queue ID: {queue_id}") + queue = actions._controller.queues.get(queue_id) + LOGGER.debug(f"Queue length: {len(queue)}") + client = actions._client + if len(queue) == current_idx: + return + items = queue[current_idx + 1:] + LOGGER.debug(f"Filtered length: {len(items)}") + LOGGER.debug(f"First item to remove {items[0]}") + LOGGER.debug(f"Last item to remove {items[-1]}") + for item in items: + queue_item_id = item[ATTR_QUEUE_ITEM_ID] + await client.player_queues.queue_command_delete(queue_id, queue_item_id) diff --git a/custom_components/mass_queue/services.yaml b/custom_components/mass_queue/services.yaml index d0de5b3..e9eb66b 100644 --- a/custom_components/mass_queue/services.yaml +++ b/custom_components/mass_queue/services.yaml @@ -206,3 +206,14 @@ set_group_volume: step: 1 unit_of_measurement: "%" mode: slider + +clear_queue_from_here: + fields: + entity: + name: Entity + description: Music Assistant Media Player Entity + required: true + selector: + entity: + domain: media_player + integration: music_assistant \ No newline at end of file diff --git a/custom_components/mass_queue/strings.json b/custom_components/mass_queue/strings.json index 109f9d0..593ae04 100644 --- a/custom_components/mass_queue/strings.json +++ b/custom_components/mass_queue/strings.json @@ -211,6 +211,16 @@ "description": "Volume level to set the player to." } } + }, + "clear_queue_from_here": { + "name": "Clear Queue From Here", + "description": "Clears all items before/after the current track in a queue.", + "fields": { + "entity": { + "name": "Entity", + "description": "Music Assistant Media Player Entity." + } + } } } } diff --git a/custom_components/mass_queue/translations/en.json b/custom_components/mass_queue/translations/en.json index e6cbc61..2b7e516 100644 --- a/custom_components/mass_queue/translations/en.json +++ b/custom_components/mass_queue/translations/en.json @@ -161,6 +161,16 @@ "description": "Music Assistant Media Player Entity." } } + }, + "clear_queue_from_here": { + "name": "Clear Queue From Here", + "description": "Clears all items before/after the current track in a queue.", + "fields": { + "entity": { + "name": "Entity", + "description": "Music Assistant Media Player Entity." + } + } } } } From 67d836ea2444fb81206f402fd96115d7101ab120 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Mon, 3 Nov 2025 11:49:12 -0500 Subject: [PATCH 4/8] Linting --- custom_components/mass_queue/services.py | 6 ++++-- custom_components/mass_queue/services.yaml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/custom_components/mass_queue/services.py b/custom_components/mass_queue/services.py index 06770e8..e7fa5d8 100644 --- a/custom_components/mass_queue/services.py +++ b/custom_components/mass_queue/services.py @@ -223,11 +223,13 @@ async def set_group_volume(call: ServiceCall): actions = get_entity_actions_controller(hass, entity_id) await actions.set_group_volume(call) + def filter_queue_after(queue, current_idx): """Returns all items after the current active track.""" if current_idx == len(queue): return [] - return queue[current_idx + 1:] + return queue[current_idx + 1 :] + async def clear_queue_from_here(call: ServiceCall): """Service wrapper to clear queue from point.""" @@ -243,7 +245,7 @@ async def clear_queue_from_here(call: ServiceCall): client = actions._client if len(queue) == current_idx: return - items = queue[current_idx + 1:] + items = queue[current_idx + 1 :] LOGGER.debug(f"Filtered length: {len(items)}") LOGGER.debug(f"First item to remove {items[0]}") LOGGER.debug(f"Last item to remove {items[-1]}") diff --git a/custom_components/mass_queue/services.yaml b/custom_components/mass_queue/services.yaml index e9eb66b..294914c 100644 --- a/custom_components/mass_queue/services.yaml +++ b/custom_components/mass_queue/services.yaml @@ -216,4 +216,4 @@ clear_queue_from_here: selector: entity: domain: media_player - integration: music_assistant \ No newline at end of file + integration: music_assistant From a8c68c5de309645735bde85b154d1c9240b9a561 Mon Sep 17 00:00:00 2001 From: MaximeNagel Date: Wed, 12 Nov 2025 13:13:14 +0100 Subject: [PATCH 5/8] Add French translation --- custom_components/mass_queue/services.yaml | 11 + .../mass_queue/translations/en.json | 47 ++++ .../mass_queue/translations/fr.json | 213 ++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 custom_components/mass_queue/translations/fr.json diff --git a/custom_components/mass_queue/services.yaml b/custom_components/mass_queue/services.yaml index d0de5b3..c29088f 100644 --- a/custom_components/mass_queue/services.yaml +++ b/custom_components/mass_queue/services.yaml @@ -1,4 +1,5 @@ get_queue_items: + translation_key: get_queue_items fields: limit: name: Limit @@ -52,6 +53,7 @@ get_queue_items: domain: media_player integration: music_assistant remove_queue_item: + translation_key: remove_queue_item fields: queue_item_id: name: Queue Item ID @@ -67,6 +69,7 @@ remove_queue_item: domain: media_player integration: music_assistant move_queue_item_up: + translation_key: move_queue_item_up fields: queue_item_id: name: Queue Item ID @@ -82,6 +85,7 @@ move_queue_item_up: domain: media_player integration: music_assistant move_queue_item_down: + translation_key: move_queue_item_down fields: queue_item_id: name: Queue Item ID @@ -97,6 +101,7 @@ move_queue_item_down: domain: media_player integration: music_assistant move_queue_item_next: + translation_key: move_queue_item_next fields: queue_item_id: name: Queue Item ID @@ -112,6 +117,7 @@ move_queue_item_next: domain: media_player integration: music_assistant play_queue_item: + translation_key: play_queue_item fields: queue_item_id: name: Queue Item ID @@ -127,6 +133,7 @@ play_queue_item: domain: media_player integration: music_assistant send_command: + translation_key: send_command fields: command: name: Command @@ -149,6 +156,7 @@ send_command: integration: mass_queue unfavorite_current_item: + translation_key: unfavorite_current_item fields: entity: name: Entity @@ -159,6 +167,7 @@ unfavorite_current_item: domain: media_player integration: music_assistant get_recommendations: + translation_key: get_recommendations fields: entity: name: Entity @@ -176,6 +185,7 @@ get_recommendations: text: multiple: true get_group_volume: + translation_key: get_group_volume fields: entity: name: Entity @@ -186,6 +196,7 @@ get_group_volume: domain: media_player integration: music_assistant set_group_volume: + translation_key: set_group_volume fields: entity: name: Entity diff --git a/custom_components/mass_queue/translations/en.json b/custom_components/mass_queue/translations/en.json index e6cbc61..34cb456 100644 --- a/custom_components/mass_queue/translations/en.json +++ b/custom_components/mass_queue/translations/en.json @@ -31,6 +31,15 @@ "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" } }, + "options": { + "step": { + "init": { + "data": { + "download_local": "Attempt fallback support for local media images. May cause performance issues. Only enable if you cannot see media images for your players." + } + } + } + }, "issues": { "invalid_server_version": { "title": "The Music Assistant server is not the correct version", @@ -161,6 +170,44 @@ "description": "Music Assistant Media Player Entity." } } + }, + "get_recommendations": { + "name": "Get Recommendations", + "description": "Get recommendations from your music providers.", + "fields": { + "entity": { + "name": "Entity", + "description": "Music Assistant Media Player Entity." + }, + "providers": { + "name": "Providers", + "description": "Limit recommendations to the specified providers." + } + } + }, + "get_group_volume": { + "name": "Get Group Volume", + "description": "Returns the volume for a player group.", + "fields": { + "entity": { + "name": "Entity", + "description": "Music Assistant Media Player Entity." + } + } + }, + "set_group_volume": { + "name": "Set Group Volume", + "description": "Sets the volume for a player group.", + "fields": { + "entity": { + "name": "Entity", + "description": "Music Assistant Media Player Entity." + }, + "volume_level": { + "name": "Volume Level", + "description": "Volume level to set the player to." + } + } } } } diff --git a/custom_components/mass_queue/translations/fr.json b/custom_components/mass_queue/translations/fr.json new file mode 100644 index 0000000..7f2209b --- /dev/null +++ b/custom_components/mass_queue/translations/fr.json @@ -0,0 +1,213 @@ +{ + "config": { + "step": { + "init": { + "data": { + "url": "URL du serveur Music Assistant" + } + }, + "manual": { + "title": "Ajouter manuellement le serveur Music Assistant", + "description": "Saisissez l’URL de votre serveur Music Assistant déjà en cours d’exécution. Si le serveur n’est pas encore installé, vous devez l’installer avant de poursuivre.", + "data": { + "url": "URL du serveur Music Assistant" + } + }, + "discovery_confirm": { + "description": "Souhaitez-vous ajouter le serveur Music Assistant « {url} » à Home Assistant ?", + "title": "Serveur Music Assistant découvert" + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_server_version": "La version du serveur Music Assistant n’est pas compatible", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "reconfiguration_successful": "Réconfiguration de l’intégration Music Assistant réussie.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "download_local": "Active une solution de secours pour télécharger les images locales des médias. Peut entraîner des problèmes de performances. À activer uniquement si aucune image n’apparaît pour vos lecteurs." + } + } + } + }, + "issues": { + "invalid_server_version": { + "title": "La version du serveur Music Assistant n’est pas compatible", + "description": "Vérifiez si des mises à jour sont disponibles pour le serveur et/ou l’intégration Music Assistant." + } + }, + "services": { + "get_queue_items": { + "name": "Obtenir les éléments de file d’attente", + "description": "Retourne la liste des éléments actuellement présents dans la file d’attente d’un lecteur.", + "fields": { + "limit": { + "name": "Limite", + "description": "Limite sur le nombre d’éléments de la file à renvoyer." + }, + "offset": { + "name": "Décalage", + "description": "Position de départ dans la file où zéro correspond au premier élément, et non à l’élément en cours." + }, + "limit_before": { + "name": "Limite avant", + "description": "Nombre d’éléments à récupérer avant l’élément actif actuel." + }, + "limit_after": { + "name": "Limite après", + "description": "Nombre d’éléments à récupérer après l’élément actif actuel." + }, + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + } + } + }, + "remove_queue_item": { + "name": "Supprimer un élément de file", + "description": "Supprime un élément de la file active d’un lecteur.", + "fields": { + "queue_item_id": { + "name": "ID d’élément", + "description": "Identifiant de l’élément à supprimer." + }, + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + } + } + }, + "move_queue_item_up": { + "name": "Monter un élément", + "description": "Monte un élément dans la file d’un lecteur.", + "fields": { + "queue_item_id": { + "name": "ID d’élément", + "description": "Identifiant de l’élément à monter." + }, + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + } + } + }, + "move_queue_item_down": { + "name": "Descendre un élément", + "description": "Descend un élément dans la file d’un lecteur.", + "fields": { + "queue_item_id": { + "name": "ID d’élément", + "description": "Identifiant de l’élément à descendre." + }, + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + } + } + }, + "move_queue_item_next": { + "name": "Lire ensuite", + "description": "Place un élément en lecture suivante dans la file d’un lecteur.", + "fields": { + "queue_item_id": { + "name": "ID d’élément", + "description": "Identifiant de l’élément à lire ensuite." + }, + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + } + } + }, + "play_queue_item": { + "name": "Lire un élément", + "description": "Lit un élément actuellement présent dans la file d’un lecteur.", + "fields": { + "queue_item_id": { + "name": "ID d’élément", + "description": "Identifiant de l’élément à lire." + }, + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + } + } + }, + "send_command": { + "name": "Envoyer une commande", + "description": "Envoie une commande à l’API Music Assistant.", + "fields": { + "command": { + "name": "Commande", + "description": "Commande à envoyer à Music Assistant." + }, + "data": { + "name": "Données", + "description": "Données à transmettre avec la commande." + }, + "config_entry_id": { + "name": "ID d’entrée de config", + "description": "Identifiant d’entrée de configuration pour l’instance Mass Queue Items." + } + } + }, + "unfavorite_current_item": { + "name": "Retirer des favoris", + "description": "Retire l’élément en cours de la liste des favoris.", + "fields": { + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + } + } + }, + "get_recommendations": { + "name": "Obtenir des recommandations", + "description": "Récupère des recommandations auprès de vos fournisseurs de musique.", + "fields": { + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + }, + "providers": { + "name": "Fournisseurs", + "description": "Limiter les recommandations aux fournisseurs indiqués." + } + } + }, + "get_group_volume": { + "name": "Obtenir le volume du groupe", + "description": "Retourne le volume d’un groupe de lecteurs.", + "fields": { + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + } + } + }, + "set_group_volume": { + "name": "Définir le volume du groupe", + "description": "Définit le volume d’un groupe de lecteurs.", + "fields": { + "entity": { + "name": "Entité", + "description": "Entité media_player Music Assistant." + }, + "volume_level": { + "name": "Niveau sonore", + "description": "Niveau sonore à appliquer au lecteur." + } + } + } + } +} From 6249809142c2295cc6092b691c621f4ff8ef1911 Mon Sep 17 00:00:00 2001 From: droans <49721649+droans@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:29:53 -0500 Subject: [PATCH 6/8] Remove `translation_key` --- custom_components/mass_queue/services.yaml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/custom_components/mass_queue/services.yaml b/custom_components/mass_queue/services.yaml index 7086f6b..294914c 100644 --- a/custom_components/mass_queue/services.yaml +++ b/custom_components/mass_queue/services.yaml @@ -1,5 +1,4 @@ get_queue_items: - translation_key: get_queue_items fields: limit: name: Limit @@ -53,7 +52,6 @@ get_queue_items: domain: media_player integration: music_assistant remove_queue_item: - translation_key: remove_queue_item fields: queue_item_id: name: Queue Item ID @@ -69,7 +67,6 @@ remove_queue_item: domain: media_player integration: music_assistant move_queue_item_up: - translation_key: move_queue_item_up fields: queue_item_id: name: Queue Item ID @@ -85,7 +82,6 @@ move_queue_item_up: domain: media_player integration: music_assistant move_queue_item_down: - translation_key: move_queue_item_down fields: queue_item_id: name: Queue Item ID @@ -101,7 +97,6 @@ move_queue_item_down: domain: media_player integration: music_assistant move_queue_item_next: - translation_key: move_queue_item_next fields: queue_item_id: name: Queue Item ID @@ -117,7 +112,6 @@ move_queue_item_next: domain: media_player integration: music_assistant play_queue_item: - translation_key: play_queue_item fields: queue_item_id: name: Queue Item ID @@ -133,7 +127,6 @@ play_queue_item: domain: media_player integration: music_assistant send_command: - translation_key: send_command fields: command: name: Command @@ -156,7 +149,6 @@ send_command: integration: mass_queue unfavorite_current_item: - translation_key: unfavorite_current_item fields: entity: name: Entity @@ -167,7 +159,6 @@ unfavorite_current_item: domain: media_player integration: music_assistant get_recommendations: - translation_key: get_recommendations fields: entity: name: Entity @@ -185,7 +176,6 @@ get_recommendations: text: multiple: true get_group_volume: - translation_key: get_group_volume fields: entity: name: Entity @@ -196,7 +186,6 @@ get_group_volume: domain: media_player integration: music_assistant set_group_volume: - translation_key: set_group_volume fields: entity: name: Entity From df491f503ec1531874eec1907a77d92705b6e3fe Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 14 Nov 2025 15:45:27 -0500 Subject: [PATCH 7/8] Move WS commands to separate file --- custom_components/mass_queue/__init__.py | 7 +- custom_components/mass_queue/utils.py | 57 +-------------- .../mass_queue/websocket_commands.py | 71 +++++++++++++++++++ 3 files changed, 77 insertions(+), 58 deletions(-) create mode 100644 custom_components/mass_queue/websocket_commands.py diff --git a/custom_components/mass_queue/__init__.py b/custom_components/mass_queue/__init__.py index 8234950..0846869 100644 --- a/custom_components/mass_queue/__init__.py +++ b/custom_components/mass_queue/__init__.py @@ -29,7 +29,10 @@ ) from .const import DOMAIN, LOGGER from .services import register_actions -from .utils import api_download_and_encode_image, download_images +from .websocket_commands import ( + api_download_and_encode_image, + api_download_images, +) if TYPE_CHECKING: from homeassistant.core import HomeAssistant @@ -121,7 +124,7 @@ async def on_hass_stop(event: Event) -> None: # noqa: ARG001 actions = await setup_controller_and_actions(hass, mass, entry) register_actions(hass) entry.runtime_data = MusicAssistantQueueEntryData(mass, actions, listen_task) - websocket_api.async_register_command(hass, download_images) + websocket_api.async_register_command(hass, api_download_images) websocket_api.async_register_command(hass, api_download_and_encode_image) # If the listen task is already failed, we need to raise ConfigEntryNotReady diff --git a/custom_components/mass_queue/utils.py b/custom_components/mass_queue/utils.py index d12d5df..4d7c502 100644 --- a/custom_components/mass_queue/utils.py +++ b/custom_components/mass_queue/utils.py @@ -6,8 +6,6 @@ import urllib.parse from typing import TYPE_CHECKING -import voluptuous as vol -from homeassistant.components import websocket_api from homeassistant.config_entries import ConfigEntryState from homeassistant.core import callback from homeassistant.exceptions import ServiceValidationError @@ -256,7 +254,7 @@ def generate_image_url_from_image_data(image_data: dict, client): return f"{base_url}/imageproxy?provider={provider}&size=256&format=png&path={img}" -async def _download_single_image_from_image_data( +async def download_single_image_from_image_data( image_data: dict, entity_id, hass, @@ -275,62 +273,9 @@ async def _download_single_image_from_image_data( return None -@websocket_api.websocket_command( - { - vol.Required("type"): "mass_queue/encode_images", - vol.Required("entity_id"): str, - vol.Required("images"): list, - }, -) -@websocket_api.async_response -async def download_images( - hass: HomeAssistant, - connection: websocket_api.ActiveConnection, - msg: dict, -) -> None: - """Download images and return them as b64 encoded.""" - 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: - img = await _download_single_image_from_image_data( - image, - entity_id, - hass, - session, - ) - image["encoded"] = img - result.append(image) - connection.send_result(msg["id"], result) - - async def download_and_encode_image(url: str, hass: HomeAssistant): """Downloads and encodes a single image from the given URL.""" session = aiohttp_client.async_get_clientsession(hass) req = await session.get(url) read = await req.content.read() return f"data:image;base64,{base64.b64encode(read).decode('utf-8')}" - - -@websocket_api.websocket_command( - { - vol.Required("type"): "mass_queue/download_and_encode_image", - vol.Required("url"): str, - }, -) -@websocket_api.async_response -async def api_download_and_encode_image( - hass: HomeAssistant, - connection: websocket_api.ActiveConnection, - msg: dict, -) -> None: - """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) diff --git a/custom_components/mass_queue/websocket_commands.py b/custom_components/mass_queue/websocket_commands.py new file mode 100644 index 0000000..cf08393 --- /dev/null +++ b/custom_components/mass_queue/websocket_commands.py @@ -0,0 +1,71 @@ +"""Music Assistant Queue Actions Websocket Commands.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import voluptuous as vol +from homeassistant.components import websocket_api +from homeassistant.helpers import aiohttp_client + +if TYPE_CHECKING: + from homeassistant.core import HomeAssistant + +from .const import LOGGER +from .utils import ( + download_and_encode_image, + download_single_image_from_image_data, +) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "mass_queue/download_and_encode_image", + vol.Required("url"): str, + }, +) +@websocket_api.async_response +async def api_download_and_encode_image( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict, +) -> None: + """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) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "mass_queue/encode_images", + vol.Required("entity_id"): str, + vol.Required("images"): list, + }, +) +@websocket_api.async_response +async def api_download_images( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict, +) -> None: + """Download images and return them as b64 encoded.""" + 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: + img = await download_single_image_from_image_data( + image, + entity_id, + hass, + session, + ) + image["encoded"] = img + result.append(image) + connection.send_result(msg["id"], result) From 43be63993679527ec392d9181b5739ee6216d3d1 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 14 Nov 2025 15:50:35 -0500 Subject: [PATCH 8/8] Add `get_entity_info` --- custom_components/mass_queue/__init__.py | 2 + custom_components/mass_queue/utils.py | 82 ++++++++++++++++++- .../mass_queue/websocket_commands.py | 21 +++++ 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/custom_components/mass_queue/__init__.py b/custom_components/mass_queue/__init__.py index 0846869..6650c2e 100644 --- a/custom_components/mass_queue/__init__.py +++ b/custom_components/mass_queue/__init__.py @@ -32,6 +32,7 @@ from .websocket_commands import ( api_download_and_encode_image, api_download_images, + api_get_entity_info, ) if TYPE_CHECKING: @@ -126,6 +127,7 @@ async def on_hass_stop(event: Event) -> None: # noqa: ARG001 entry.runtime_data = MusicAssistantQueueEntryData(mass, actions, listen_task) websocket_api.async_register_command(hass, api_download_images) websocket_api.async_register_command(hass, api_download_and_encode_image) + websocket_api.async_register_command(hass, api_get_entity_info) # If the listen task is already failed, we need to raise ConfigEntryNotReady if listen_task.done() and (listen_error := listen_task.exception()) is not None: diff --git a/custom_components/mass_queue/utils.py b/custom_components/mass_queue/utils.py index 4d7c502..e0dbea5 100644 --- a/custom_components/mass_queue/utils.py +++ b/custom_components/mass_queue/utils.py @@ -10,7 +10,9 @@ from homeassistant.core import callback from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import aiohttp_client +from homeassistant.helpers import device_registry as dr from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.template import device_id if TYPE_CHECKING: from homeassistant.core import HomeAssistant @@ -18,7 +20,7 @@ from . import MassQueueEntryData -from .const import LOGGER +from .const import ATTR_QUEUE_ID, LOGGER @callback @@ -37,14 +39,25 @@ def _get_config_entry( return entry -def get_entity_actions_controller(hass, entity_id): +def get_mass_queue_entry(hass, entity_id): """Gets the actions for the selected entity.""" mass_entry = get_mass_entry(hass, entity_id) mass = mass_entry.runtime_data.mass.connection.ws_server_url - mass_queue_entry = find_mass_queue_entry(hass, mass) + return find_mass_queue_entry(hass, mass) + + +def get_entity_actions_controller(hass, entity_id): + """Gets the actions for the selected entity.""" + mass_queue_entry = get_mass_queue_entry(hass, entity_id) return mass_queue_entry.runtime_data.actions +def get_mass_client(hass, entity_id): + """Gets the actions for the selected entity.""" + mass_queue_entry = get_mass_queue_entry(hass, entity_id) + return mass_queue_entry.runtime_data.mass + + def get_mass_entry(hass, entity_id): """Helper function to pull MA Config Entry.""" config_id = _get_mass_entity_config_entry_id(hass, entity_id) @@ -204,6 +217,7 @@ def _get_recommendation_item_image(item: dict): def process_recommendation_section_item(item: dict): """Process and reformat a single recommendation item.""" + LOGGER.debug(f"Got section item: {item}") return { "item_id": item["item_id"], "name": item["name"], @@ -279,3 +293,65 @@ async def download_and_encode_image(url: str, hass: HomeAssistant): req = await session.get(url) read = await req.content.read() return f"data:image;base64,{base64.b64encode(read).decode('utf-8')}" + + +def get_entity_info(hass: HomeAssistant, entity_id: str): + """Gets the server and client info for a given player.""" + client = get_mass_client(hass, entity_id) + state = hass.states.get(entity_id) + registry = dr.async_get(hass) + dev_id = device_id(hass, entity_id) + dev = registry.async_get(dev_id) + identifiers = dev.identifiers + + player_id = [_id[1] for _id in identifiers if _id[0] == "music_assistant"][0] + player = client.players.get(player_id) + + mass_entry_id = _get_mass_entity_config_entry_id(hass, entity_id) + mass_queue_id = get_mass_queue_entry(hass, entity_id).entry_id + + queue_id = state.attributes.get(ATTR_QUEUE_ID) + + server_url = client.server_info.base_url + ws_url = client.connection.ws_server_url + + config_url = dev.configuration_url + manufacturer = dev.manufacturer + model = dev.model + + available = player.available + can_group_with = player.can_group_with + ip_address = player.device_info.ip_address + features = list(player.supported_features) + name = player.name + provider = player.provider + synced_to = player.synced_to + player_type = player.type + + return { + "available": available, + "can_group_with": can_group_with, + "connection": { + "configuration_url": config_url, + "url": ip_address, + }, + "entries": { + "music_assistant": mass_entry_id, + "mass_queue": mass_queue_id, + }, + "features": features, + "manufacturer": manufacturer, + "model": model, + "name": name, + "player_id": player_id, + "provider": provider, + "queue_id": queue_id, + "server": { + "connection": { + "url": server_url, + "websocket": ws_url, + }, + }, + "synced_to": synced_to, + "type": player_type, + } diff --git a/custom_components/mass_queue/websocket_commands.py b/custom_components/mass_queue/websocket_commands.py index cf08393..83e7f15 100644 --- a/custom_components/mass_queue/websocket_commands.py +++ b/custom_components/mass_queue/websocket_commands.py @@ -15,9 +15,30 @@ from .utils import ( download_and_encode_image, download_single_image_from_image_data, + get_entity_info, ) +@websocket_api.websocket_command( + { + vol.Required("type"): "mass_queue/get_info", + vol.Required("entity_id"): str, + }, +) +@websocket_api.async_response +def api_get_entity_info( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict, +) -> None: + """Returns Music Assistant player information on a given player.""" + LOGGER.debug(f"Got message: {msg}") + entity_id = msg["entity_id"] + result = get_entity_info(hass, entity_id) + LOGGER.debug(f"Sending result {result}") + connection.send_result(msg["id"], result) + + @websocket_api.websocket_command( { vol.Required("type"): "mass_queue/download_and_encode_image",