Repository: alandtse/alexa_media_player
Pull request #2875: feat: add get_history_records service call
Author: login: Joe91
Created: 2025-03-25T22:05:18Z | Merged: 2025-03-27T02:03:05Z

added a method to get some of the history-records. Need this for one of mine automations, maybe someone else can use this too.
I don't know your requirements or target structure, so just give me feedback.
If this is not a desired behavior I'm also fine with this.

Thanks a lot for this amazing component!
Joe


Diff:
diff --git a/custom_components/alexa_media/const.py b/custom_components/alexa_media/const.py
index 16d6ea6f6..55115513f 100644
--- a/custom_components/alexa_media/const.py
+++ b/custom_components/alexa_media/const.py
@@ -68,6 +68,7 @@
 
 SERVICE_UPDATE_LAST_CALLED = "update_last_called"
 SERVICE_RESTORE_VOLUME = "restore_volume"
+SERVICE_GET_HISTORY_RECORDS = "get_history_records"
 SERVICE_FORCE_LOGOUT = "force_logout"
 
 RECURRING_PATTERN = {
diff --git a/custom_components/alexa_media/media_player.py b/custom_components/alexa_media/media_player.py
index 1febd96b9..08d52646f 100644
--- a/custom_components/alexa_media/media_player.py
+++ b/custom_components/alexa_media/media_player.py
@@ -260,6 +260,7 @@ def __init__(self, device, login, second_account_index=0):
         self._source_list = []
         self._connected_bluetooth = None
         self._bluetooth_list = []
+        self._history_records = []
         self._shuffle = None
         self._repeat = None
         self._playing_parent = None
@@ -1734,6 +1735,7 @@ def extra_state_attributes(self):
             "last_called_summary": self._last_called_summary,
             "connected_bluetooth": self._connected_bluetooth,
             "bluetooth_list": self._bluetooth_list,
+            "history_records": self._history_records,
             "previous_volume": self._previous_volume,
         }
         return attr
diff --git a/custom_components/alexa_media/services.py b/custom_components/alexa_media/services.py
index 726827090..8f832b414 100644
--- a/custom_components/alexa_media/services.py
+++ b/custom_components/alexa_media/services.py
@@ -22,6 +22,7 @@
     DATA_ALEXAMEDIA,
     DOMAIN,
     SERVICE_FORCE_LOGOUT,
+    SERVICE_GET_HISTORY_RECORDS,
     SERVICE_RESTORE_VOLUME,
     SERVICE_UPDATE_LAST_CALLED,
 )
@@ -38,6 +39,13 @@
 )
 RESTORE_VOLUME_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.entity_id})
 
+GET_HISTORY_RECORDS_SCHEMA = vol.Schema(
+    {
+        vol.Required(ATTR_ENTITY_ID): cv.entity_id,
+        vol.Optional(ATTR_NUM_ENTRIES, default=5): cv.positive_int,
+    }
+)
+
 
 class AlexaMediaServices:
     """Class that holds our services that should be published to hass."""
@@ -64,6 +72,12 @@ async def register(self):
             self.restore_volume,
             schema=RESTORE_VOLUME_SCHEMA,
         )
+        self.hass.services.async_register(
+            DOMAIN,
+            SERVICE_GET_HISTORY_RECORDS,
+            self.get_history_records,
+            schema=GET_HISTORY_RECORDS_SCHEMA,
+        )
 
     async def unregister(self):
         """Deregister services from hass."""
@@ -76,6 +90,10 @@ async def unregister(self):
             DOMAIN,
             SERVICE_RESTORE_VOLUME,
         )
+        self.hass.services.async_remove(
+            DOMAIN,
+            SERVICE_GET_HISTORY_RECORDS,
+        )
 
     @_catch_login_errors
     async def force_logout(self, call) -> bool:
@@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
+                    login_obj, None, None
+                )
+                if history_data:
+                    for item in history_data:
+                        summary = item.get("description", {}).get("summary", "")
+                        device_serial_number = item.get("deviceSerialNumber")
+                        if (
+                            not summary
+                            or summary == ","
+                            or device_serial_number != target_serial_number
+                        ):
+                            continue
+                        entry = {
+                            "timestamp": item.get("creationTimestamp"),
+                            "summary": summary,
+                            "response": item.get("alexaResponse", ""),
+                        }
+                        history_data_total.append(entry)
+            except Exception as e:
+                _LOGGER.error(
+                    "Error retrieving history for %s: %s", hide_email(email), str(e)
+                )
+
+        # Sort and limit entries
+        history_data_total.sort(key=lambda x: x["timestamp"], reverse=True)
+        history_data_total = history_data_total[:number_of_entries]
+
+        # Update the entity's attributes
+        state = self.hass.states.get(entity_id)
+        if state and self.hass.states.async_set:
+            new_attributes = dict(state.attributes)
+            new_attributes["history_records"] = history_data_total
+            self.hass.states.async_set(entity_id, state.state, new_attributes)
+        else:
+            _LOGGER.error("Entity %s state not found", entity_id)
+            return False
+
+        return True
diff --git a/custom_components/alexa_media/services.yaml b/custom_components/alexa_media/services.yaml
index 899bb6fa6..3663506cf 100644
--- a/custom_components/alexa_media/services.yaml
+++ b/custom_components/alexa_media/services.yaml
@@ -23,6 +23,24 @@ restore_volume:
           domain: media_player
           integration: alexa_media
 
+get_history_records:
+  description: Returns the last entries of all the customer history.
+  fields:
+    entity_id:
+      name: Entity
+      description: Alexa Media Player device to get history from.
+      required: true
+      selector:
+        entity:
+          domain: media_player
+          integration: alexa_media
+    entries:
+      name: Entries
+      description: Number of records to return.
+      required: false
+      default: 5
+      example: 5
+
 update_last_called:
   # Description of the service
   description: Forces update of last_called echo device for each Alexa account.
diff --git a/custom_components/alexa_media/strings.json b/custom_components/alexa_media/strings.json
index 80a853e38..f293142bc 100644
--- a/custom_components/alexa_media/strings.json
+++ b/custom_components/alexa_media/strings.json
@@ -88,6 +88,20 @@
         }
       }
     },
+    "get_history_records": {
+      "name": "Get History Records",
+      "description": "Parses the history records for the specified device",
+      "fields": {
+        "entity_id": {
+          "name": "Select media player:",
+          "description": "Entity to get the history for"
+        },
+        "entries": {
+          "name": "Number of entries",
+          "description": "Number of entries to get"
+        }
+      }
+    },
     "update_last_called": {
       "name": "Update Last Called Sensor",
       "description": "Forces update of last_called echo device for each Alexa account.",
diff --git a/custom_components/alexa_media/translations/en.json b/custom_components/alexa_media/translations/en.json
index 6b3c18fae..f8fba741a 100644
--- a/custom_components/alexa_media/translations/en.json
+++ b/custom_components/alexa_media/translations/en.json
@@ -94,6 +94,20 @@
       },
       "name": "Restore Previous Volume"
     },
+    "get_history_records": {
+      "description": "Parses the history records for the specified device",
+      "fields": {
+        "entity_id": {
+          "description": "Entity to get the history for",
+          "name": "Select media player:"
+        },
+        "entries": {
+          "description": "Number of entries to get",
+          "name": "Number of entries"
+        }
+      },
+      "name": "Get History Records"
+    },
     "update_last_called": {
       "description": "Forces update of last_called echo device for each Alexa account.",
       "fields": {


Reviews:
user: login: alandtse
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-26T07:11:02Z
user: login: Joe91
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:17:08Z
user: login: danielbrunt57
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-26T08:28:59Z
user: login: Joe91
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:32:37Z
user: login: danielbrunt57
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-26T09:09:51Z
user: login: Joe91
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T09:34:12Z
user: login: danielbrunt57
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-26T15:32:05Z
user: login: Joe91
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T15:35:58Z
user: login: danielbrunt57
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-26T18:44:14Z
user: login: Joe91
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T18:50:34Z
user: login: danielbrunt57
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T11:30:21Z

Review comments:
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: alandtse
body: Just to confirm, alexapy already has this? Or do we need an alexapy bump?
created_at: 2025-03-26T07:11:02Z
author_association: OWNER
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: Joe91
body: Yeah, it's already there.
Found it when I thought I had to write it on my own. 
Quite a powerful feature in my opinion...
created_at: 2025-03-26T08:17:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: danielbrunt57
body: Yes, `alexaapi.py` already has this and the `update_last_called` service uses it via:
```
    async def get_last_device_serial(
        login: AlexaLogin, items: int = 10
    ) -> Optional[dict[str, Any]]:
        """Identify the last device's serial number and last summary.

        This will search the [last items] activity records and find the latest
        entry where Echo successfully responded.
        """
        response = await AlexaAPI.get_customer_history_records(
            login, max_record_size=items
        )
```
created_at: 2025-03-26T08:28:59Z
author_association: COLLABORATOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: Joe91
body: ah okay. But it's not already used in the media_player. Or is it?
created_at: 2025-03-26T08:32:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: danielbrunt57
body: I can see the possible merit in doing what you're doing.  I'm just not quite seeing how or where you're going to use the returned data.
There's a fundamental underlying current issue with the HTTP "GET" history records though and that is when using the original HTTP path `customer_history_records`, it almost always has to retry the GET (up to 5 times) and often fails with "Too Many Requests".  That HTTP access point is what the now defunct web interface to Alexa used to use.  Apollon77's `alexa-remote` (javascript) now uses `customer_history_records_v2` and can almost always GET records repeatedly without the dreaded "429" error.  I tried rewriting `alexapy` to use it but Apollon surrounds it with a different authentication scheme that I've not been able to unravel yet and convert to Python and I always get "403 Unauthorized".

created_at: 2025-03-26T09:09:51Z
author_association: COLLABORATOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: Joe91
body: That returned data is stored in the new attribute "history_records".
I can then access this with templates or in the Python-Scripts.

I have one device that is only controllable with Alexa and this way I can evaluate the state of the device and control it depending on the state. 

What is your suggestion based on that issue? 
Since I'm already happy with this state for my usecase I'm totally fine to not merge that at all or to wait for your rework first. 

created_at: 2025-03-26T09:34:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: danielbrunt57
body: You could test modifying the existing service call to see if it meets your needs and has no downside.
created_at: 2025-03-26T15:32:05Z
author_association: COLLABORATOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: Joe91
body: What do you mean with the existing service-call? I can't see any other option to access the history-records then using the api-call provieded.
Do you mean to improt the alexa-remote?
created_at: 2025-03-26T15:35:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: danielbrunt57
body: > ah okay. But it's not already used in the media_player. Or is it?

> What do you mean with the existing service-call? I can't see any other option to access the history-records then using the api-call provided.

AMP calls `update_last_called` when AMP initializes at startup and then every time it polls Amazon, unless automatic polling is disabled by the user. That function calls `get_last_device_serial` which calls `get_customer_history_records`.
Your enhancement could be added inside `get_last_device_serial` to automatically update the new fields when polling occurs or the service `update_last_called` is manually called.  Having a new clearly defined service that updates the history records might be clearer for the user when making service calls manually but, increasing or duplicating automatic calls to `get_customer_history_records` will only add to its current "429 Too Many Requests" errors so we need to bear that in mind.

> Do you mean to import the alexa-remote?

No.  alexa-remote is javascript code so that's not possible.
created_at: 2025-03-26T18:44:14Z
author_association: COLLABORATOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: Joe91
body: Ah, so you recommend to change this in alexapy module (to sore and provide it at the same moment) and afterwards acces it with alexa_media_player at this exact situation?
Edit: so it might already be enough to insert some more suff there and use this later up in this module:
https://gitlab.com/keatontaylor/alexapy/-/blob/dev/alexapy/alexaapi.py?ref_type=heads#L1491

Is that how you would solve this?
created_at: 2025-03-26T18:50:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -190,3 +208,66 @@ async def restore_volume(self, call) -> bool:
 
         _LOGGER.debug("Volume restored to %s for entity %s", previous_volume, entity_id)
         return True
+
+    async def get_history_records(self, call):
+        """Handle request to get history records and store them on the entity."""
+        entity_id = call.data.get(ATTR_ENTITY_ID)
+        number_of_entries = call.data.get(ATTR_NUM_ENTRIES)
+        _LOGGER.debug(
+            "Service get_history_records for: %s with %d entries",
+            entity_id,
+            number_of_entries,
+        )
+
+        # Validate the target entity
+        entity_registry = er.async_get(self.hass)
+        entity_entry = entity_registry.async_get(entity_id)
+        if not entity_entry or entity_entry.platform != DOMAIN:
+            _LOGGER.error("Entity %s not found or not part of %s", entity_id, DOMAIN)
+            return False
+        target_serial_number = entity_entry.unique_id
+
+        history_data_total = []
+        for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+            login_obj = account_dict["login_obj"]
+            try:
+                # Get the history records. Input: Time from, Time to,
+                history_data = await AlexaAPI.get_customer_history_records(
path: custom_components/alexa_media/services.py
user: login: danielbrunt57
body: No, it should be done in the AMP function that calls alexapy. alexapy can't update AMP config entry components. It simply returns x number of history records and it's up to the calling function to process the data it retrieved.  In AMP, function 1 calls function 2 which calls alexapy so it should be handled by either function 1 or 2 and from memory, I think function 2 is where I would add handling the data processing for the new feature.
created_at: 2025-03-27T11:30:21Z
author_association: COLLABORATOR

PR comments:
user: login: coveralls
created_at: 2025-03-25T22:08:56Z
body: ## Pull Request Test Coverage Report for [Build 14090949062](https://coveralls.io/builds/72941974)


### Details

* **3** of **40**   **(7.5%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.02%**) to **9.254%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [custom_components/alexa_media/media_player.py](https://coveralls.io/builds/72941974/source?filename=custom_components%2Falexa_media%2Fmedia_player.py#L263) | 0 | 1 | 0.0%
| [custom_components/alexa_media/services.py](https://coveralls.io/builds/72941974/source?filename=custom_components%2Falexa_media%2Fservices.py#L75) | 2 | 38 | 5.26%
<!-- | **Total:** | **3** | **40** | **7.5%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/72941974/badge)](https://coveralls.io/builds/72941974) |
| :-- | --: |
| Change from base [Build 14013889667](https://coveralls.io/builds/72871227): |  -0.02% |
| Covered Lines: | 331 |
| Relevant Lines: | 3577 |

---
##### 💛  - [Coveralls](https://coveralls.io)

author_association: NONE
user: login: Joe91
created_at: 2025-03-26T15:04:01Z
body: stupid question: if this shall become a merge, what am I missing to fix this validation? Is there any mechanism for automatic translation into the different languages or what is required to fix this check?
author_association: CONTRIBUTOR
user: login: alandtse
created_at: 2025-03-26T16:24:45Z
body: En.json should be populated I believe. Other languages are handled by lokalise. 
author_association: OWNER
user: login: danielbrunt57
created_at: 2025-03-26T18:30:13Z
body: Correct.  See my [PR #2776](https://github.com/alandtse/alexa_media_player/pull/2776) from December where I had the same issue.
author_association: COLLABORATOR
user: login: danielbrunt57
created_at: 2025-03-26T19:31:52Z
body: After further fleshing the scenarios out in my head, we could update the description for the `update_last_called` to indicate it also updates history records when it's called. Thus the new service call becomes unnecessary as the existing service call is already retrieving customer history records and updating both last_called and the new history records.
author_association: COLLABORATOR
user: login: danielbrunt57
created_at: 2025-03-26T19:37:53Z
body: I would modify `get_last_device_serial` to update the new history records at the same time it's updating last_called since it's already retrieved the last 10 records.
author_association: COLLABORATOR
user: login: Joe91
created_at: 2025-03-26T19:41:39Z
body: I'm completely fine with this proposal :-).
For my automation I need especally the response of the last (better the last few) records. Since there is a lot more in these entries I would recommend to filter them and only use the entries that might be useful.

If they are already available (because of the exact same call being performed cyclic much deeper) the proposal of mine is stupid and will only cause additional traffic. I just didn't see / know that when I started.
Will you or someone else have a look at this? I'm also fine with if you say that this is not the direction you want this module to be, then I'll just keept this as a solution for myself.

Anyway this module is really amazing. Thanks for maintaining and supporting this!

Shall I close this PR directly?
author_association: CONTRIBUTOR
user: login: alandtse
created_at: 2025-03-26T21:04:44Z
body: As this is a service call, I'm ok with just adding the service. Most users will not be impacted   An optimization to auto populate can come later. 
author_association: OWNER
user: login: alandtse
created_at: 2025-03-27T02:09:40Z
body: Thanks. I'll probably do a release this weekend. This should give a few days for translators to add the keys.
author_association: OWNER
user: login: Joe91
created_at: 2025-03-27T05:53:48Z
body: Great, thanks. 
I did a lot of testing and even though this works really great for me and the automation, I discovered an issue, that either need good documentation or a fix to make this usable for the default user.

- since the data doesn't seem to arrive in the media_player class in the internal attribute (don't know why or how to fix this) the history will disappear after a while again, haven't found out when exactly yet.

Really sorry for discovering this issues after this was merged. Maybe someone can give me some hint in how to fix them or do it correctly?  I will provide a fix as soon as possible, but we can also revert this for now.


author_association: CONTRIBUTOR
user: login: alandtse
created_at: 2025-03-27T06:48:23Z
body: The wiki is used for documentation. Please document there. Attributes are refreshed based on events. That refresh could be clearing the data. 
author_association: OWNER
user: login: danielbrunt57
created_at: 2025-03-27T11:39:10Z
body: @alandtse Have you not read any of my comments?  I think your merging this as is was premature as I believe no consideration has been given to the already existing problem of 429 Too Many Requests when trying to update last called. If users try to also use this, the 429's are most likely going to increase.
author_association: COLLABORATOR
user: login: danielbrunt57
created_at: 2025-03-27T12:12:07Z
body: @alandtse So just refactor it down the road then. Okay. Agreed, it's not likely to get widespread use initially. I still want to dig deep into converting to V2 history records as that will impact everyone greatly. It'll be a challenge but one I'd like to conquer. Might take 6 months or longer. I'm still transitioning my life and currently have 205 sq ft of storage almost filled with my lifelong "shit" (as my ex calls it!) that I need to deal with before I buy and move into a trailer "somewhere" in BC. Once all that is done, I'll have time to "play" (as my ex calls it!) on my computer!
author_association: COLLABORATOR
user: login: pedolsky
created_at: 2025-05-15T14:11:36Z
body: @Joe91 , could you please give an example of using this new action? 
author_association: NONE
user: login: Joe91
created_at: 2025-05-15T19:12:57Z
body: here is a little example out of one of my python-scripts that uses this:

```
# Send voice command to Alexa
hass.services.call('media_player', 'play_media', {
    'entity_id': 'media_player.echo_dot',
    'media_content_id': media_content_id,
    'media_content_type': 'custom'
})

# get response
response = ""
timestamp = 0
for i in range(25): 
    time.sleep(1)
    # get the history records
    hass.services.call('alexa_media', 'get_history_records', {
        'entity_id': 'media_player.echo_dot'
    })
    state = hass.states.get('media_player.echo_dot')
    attributes = state.attributes
    response = ""
    
    for record in attributes['history_records']:
        if record['summary'] in media_content_id:
            response = record['response']
            timestamp = int(record['timestamp']/1000)
            break
    if response != "" and timestamp > now:
        break

```
With this I can extract the response and evalute it afterwards.
author_association: CONTRIBUTOR