Bidirectional Shopping List Sync Automation

Thanks for sharing this automation. I have it mostly working, but I want to implement the check to see if both lists are identical so I can run the sync more frequently than 20 minutes. Where exactly does this section of the code go? Should I be inserting it as the first action? Below is the actions section of my automation and it doesn’t currently work for me.

  actions:
    - alias: Check if lists are different
      sequence:
        - alias: Get ALL list entries of both lists
          action: todo.get_items
          metadata: {}
          data:
            status:
              - needs_action
              - completed
          response_variable: all_items
          target:
            entity_id:
              - todo.alexa_shopping_list
              - todo.shopping_list
        - alias: Check If both lists are identical
          if:
            - condition: template
              value_template: >
                {% set shopping_list_items = all_items['todo.shopping_list']['items'] |
                selectattr('status', 'equalto', 'needs_action') | map(attribute='summary') | list | sort %}

                {% set alexa_shopping_list_items = all_items['todo.alexa_shopping_list']['items'] | 
                selectattr('status', 'equalto', 'needs_action') | map(attribute='summary') | list | sort %}

                {{ shopping_list_items == alexa_shopping_list_items }}
          then:
            - stop: Both lists are identical
      enabled: true
    - choose:
        - conditions:
            - condition: template
              value_template: >-
                {{ states.todo.shopping_list.last_changed >
                states.todo.alexa_shopping_list.last_changed }}
              alias: Alexa list is more up-to-date than AnyList
            - condition: template
              value_template: " {{ (now() - states.todo.alexa_shopping_list.last_changed).total_seconds() > 180 }}"
              alias: AnyList list has not been updated for several minutes
          sequence:
            - sequence:
                - alias: Clear the AnyList list
                  sequence:
                    - alias: Retrieve existing entries from AnyList
                      target:
                        entity_id: todo.alexa_shopping_list
                      data:
                        status:
                          - needs_action
                          - completed
                      response_variable: alexa_shopping_list_items
                      action: todo.get_items
                    - alias: Empty the AnyList list
                      repeat:
                        while:
                          - condition: template
                            value_template: "{{ (states('todo.alexa_shopping_list') | int ) > 0 }}"
                        sequence:
                          - alias: Refresh AnyList list after deletion
                            target:
                              entity_id: todo.alexa_shopping_list
                            data:
                              status:
                                - needs_action
                                - completed
                            response_variable: alexa_shopping_list_items
                            action: todo.get_items
                          - delay: "00:00:02"
                          - repeat:
                              for_each: >-
                                {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items'] |
                                map(attribute='summary') | list }}
                              sequence:
                                - target:
                                    entity_id: todo.alexa_shopping_list
                                  data: |
                                    {{ dict(item=repeat.item) }}
                                  action: todo.remove_item
                - alias: Update AnyList list with Alexa items
                  sequence:
                    - alias: Retrieve all entries from Alexa
                      target:
                        entity_id: todo.shopping_list
                      data:
                        status:
                          - needs_action
                          - completed
                      response_variable: shopping_list_items
                      action: todo.get_items
                    - alias: Define variables for unique items
                      variables:
                        unique_needs_action_items: |-
                          {{ shopping_list_items['todo.shopping_list']['items']
                            | selectattr('status', 'eq', 'needs_action')
                            | map(attribute='uid')
                            | list
                            | map('regex_replace', '^', '{\"uid\": \"')
                            | map('regex_replace', '$', '\"}')
                            | map('from_json')
                            | list }}
                        unique_completed_items: |-
                          {{ shopping_list_items['todo.shopping_list']['items']
                            | selectattr('status', 'eq', 'completed')
                            | map(attribute='uid')
                            | list
                            | map('regex_replace', '^', '{\"uid\": \"')
                            | map('regex_replace', '$', '\"}')
                            | map('from_json')
                            | list }}
                    - alias: Copy pending items from Alexa to AnyList
                      repeat:
                        for_each: "{{ unique_needs_action_items }}"
                        sequence:
                          - target:
                              entity_id: todo.alexa_shopping_list
                            data:
                              item: >-
                                {{ shopping_list_items['todo.shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                            action: todo.add_item
                    - alias: >-
                        Copy completed items from Alexa to AnyList and mark as
                        completed
                      repeat:
                        for_each: "{{ unique_completed_items }}"
                        sequence:
                          - target:
                              entity_id: todo.alexa_shopping_list
                            data:
                              item: >-
                                {{ shopping_list_items['todo.shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                            action: todo.add_item
                          - target:
                              entity_id: todo.alexa_shopping_list
                            data:
                              item: >-
                                {{ shopping_list_items['todo.shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                              status: completed
                            action: todo.update_item
              alias: Update AnyList list
          alias: Alexa list is more up-to-date than AnyList list
        - conditions:
            - alias: AnyList list is more up-to-date than Alexa list
              condition: template
              value_template: >-
                {{ states.todo.alexa_shopping_list.last_changed >
                states.todo.shopping_list.last_changed }}
            - alias: Alexa list has not been updated for several minutes
              condition: template
              value_template: " {{ (now() - states.todo.shopping_list.last_changed).total_seconds() > 180 }}"
          sequence:
            - alias: Update Alexa list with AnyList items
              sequence:
                - alias: Clear the Alexa list
                  sequence:
                    - alias: Retrieve existing entries from Alexa
                      target:
                        entity_id: todo.shopping_list
                      data:
                        status:
                          - needs_action
                          - completed
                      response_variable: shopping_list_items
                      action: todo.get_items
                    - alias: Empty the Alexa list
                      repeat:
                        while:
                          - condition: template
                            value_template: "{{ (states('todo.shopping_list') | int ) > 0 }}"
                        sequence:
                          - alias: Refresh Alexa list after deletion
                            target:
                              entity_id: todo.shopping_list
                            data:
                              status:
                                - needs_action
                                - completed
                            response_variable: shopping_list_items
                            action: todo.get_items
                          - delay: "00:00:02"
                          - repeat:
                              for_each: >-
                                {{ shopping_list_items['todo.shopping_list']['items'] |
                                map(attribute='summary') | list }}
                              sequence:
                                - target:
                                    entity_id: todo.shopping_list
                                  data: |
                                    {{ dict(item=repeat.item) }}
                                  action: todo.remove_item
                - alias: Update Alexa list with AnyList items
                  sequence:
                    - alias: Retrieve all entries from AnyList
                      target:
                        entity_id: todo.alexa_shopping_list
                      data:
                        status:
                          - needs_action
                          - completed
                      response_variable: alexa_shopping_list_items
                      action: todo.get_items
                    - alias: Define variables for unique items
                      variables:
                        unique_needs_action_items: |-
                          {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items']
                            | selectattr('status', 'eq', 'needs_action')
                            | map(attribute='uid')
                            | list
                            | map('regex_replace', '^', '{\"uid\": \"')
                            | map('regex_replace', '$', '\"}')
                            | map('from_json')
                            | list }}
                        unique_completed_items: |-
                          {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items']
                            | selectattr('status', 'eq', 'completed')
                            | map(attribute='uid')
                            | list
                            | map('regex_replace', '^', '{\"uid\": \"')
                            | map('regex_replace', '$', '\"}')
                            | map('from_json')
                            | list }}
                    - alias: Copy pending items from AnyList to Alexa
                      repeat:
                        for_each: "{{ unique_needs_action_items }}"
                        sequence:
                          - target:
                              entity_id: todo.shopping_list
                            data:
                              item: >-
                                {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                            action: todo.add_item
                    - alias: >-
                        Copy completed items from AnyList to Alexa and mark as
                        completed
                      repeat:
                        for_each: "{{ unique_completed_items }}"
                        sequence:
                          - target:
                              entity_id: todo.shopping_list
                            data:
                              item: >-
                                {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                            action: todo.add_item
                          - target:
                              entity_id: todo.shopping_list
                            data:
                              item: >-
                                {{ alexa_shopping_list_items['todo.alexa_shopping_list']['items'] |
                                selectattr('uid', 'eq', repeat.item.uid) |
                                map(attribute='summary') | first }}
                              status: completed
                            action: todo.update_item
          alias: AnyList list is more up-to-date than Alexa list
      enabled: true
  mode: single

Any suggestions would be really appreciated.

I ended up implementing shpongledsummer’s original script/automation solution with some changes. I’ve been attempting to sync Alexa with AnyList using the two integrations and the bi-directional sync solution wasn’t ideal because the integrations update at different intervals and it was causing duplicates and all sorts of other problems. So what I did was create one script to update Anylist when an Alexa item was added and a second script to mark an Alexa item as complete when I check off an AnyList item. Because I only use AnyList for my shopping, I don’t really care if AnyList adds new items I create within the app to Alexa. But I do want Alexa updated when I mark an item completed within AnyList for obvious reasons. So I only need one-way sync for these tasks to accomplish what I need. Here’s my final solution, which is working well so far:

Scripts
Update AnyList when a new item is added to Alexa:

update_anylist:
  alias: Sync New Alexa Items with AnyList
  icon: mdi:food-fork-drink
  sequence:
    - alias: Update list | AnyList
      sequence:
        - alias: Retrieve existing entries from Alexa Shopping List
          target:
            entity_id: todo.shopping_list
          data:
            status:
              - needs_action
          response_variable: source_items
          action: todo.get_items
        - alias: Retrieve existing entries from AnyList
          target:
            entity_id: todo.anylist_shopping_list
          data:
            status:
              - needs_action
          response_variable: existing_items
          action: todo.get_items
        - alias: Add new entries to AnyList list
          repeat:
            for_each: |-
              {{
                source_items['todo.shopping_list']['items']
                | selectattr('status', 'eq', 'needs_action')
                | rejectattr('summary', 'in', existing_items['todo.anylist_shopping_list']['items'] | map(attribute='summary') | list)
                | list
              }}
            sequence:
              - target:
                  entity_id: todo.anylist_shopping_list
                data:
                  item: "{{ repeat.item.summary }}"
                action: todo.add_item
        - alias: Reset re-added items
          repeat:
            for_each: |-
              {{
                source_items['todo.shopping_list']['items']
                | selectattr('needs_action')
                | selectattr('summary', 'in', existing_items['todo.anylist_shopping_list']['items'] | selectattr('status', 'eq', 'completed') | map(attribute='summary') | list)
                | list
              }}
            sequence:
              - target:
                  entity_id: todo.anylist_shopping_list
                data:
                  item: "{{ repeat.item.summary }}"
                  status: needs_action
                action: todo.update_item

Update Alexa when an item is marked as complete in AnyList:

update_alexa_list:
  alias: Sync Completed Anylist Items with Alexa
  icon: mdi:food-fork-drink
  sequence:
    - alias: Update list | Alexa
      sequence:
        - alias: Retrieve existing entries from AnyList
          target:
            entity_id: todo.anylist_shopping_list
          data:
            status:
              - completed
          response_variable: source_items
          action: todo.get_items
        - alias: Retrieve existing entries from Alexa
          target:
            entity_id: todo.shopping_list
          data:
            status:
              - needs_action
          response_variable: existing_items
          action: todo.get_items
        - alias: Mark completed items as done in AnyList
          repeat:
            for_each: |-
              {{
                source_items['todo.anylist_shopping_list']['items']
                | selectattr('status', 'eq', 'completed')
                | selectattr('summary', 'in', existing_items['todo.shopping_list']['items'] | selectattr('status', 'ne', 'completed') | map(attribute='summary') | list)
                | list
              }}
            sequence:
              - target:
                  entity_id: todo.shopping_list
                data:
                  item: "{{ repeat.item.summary }}"
                  status: completed
                action: todo.update_item

And here’s the automation:

- id: dishwasher_complete_notification
  alias: Notify when dishwasher starts
  triggers:
    trigger: state
    entity_id: sensor.dishwasher_operation_state
    to: "Ready"
  actions:
    action: notify.mobile_app_chris_iphone
    data:
      message: Dishwasher Has Completed The Cycle
      data:
        push:
          badge: 0
- id: update_shopping_lists
  alias: Sync Lists On Update
  description: Sync Alexa and Anylist when updates are made
  mode: single
  triggers:
    - entity_id: todo.shopping_list
      trigger: state
      alias: Alexa list was updated
      id: Alexa list was updated
    - entity_id: todo.anylist_shopping_list
      trigger: state
      alias: Anylist list was updated
      id: Anylist list was updated
    - alias: 15-minute trigger
      trigger: time_pattern
      id: 15-minute trigger
      minutes: /15
  actions:
    - delay:
        hours: 0
        minutes: 0
        seconds: 5
        milliseconds: 0
    - choose:
        - conditions:
            - condition: trigger
              id:
                - Alexa list was updated
          sequence:
            - action: script.update_anylist
              metadata: {}
              data: {}
              alias: Update Anylist list
          alias: Alexa list was updated (update Anylist)
        - conditions:
            - condition: trigger
              id:
                - Anylist list was updated
          sequence:
            - action: script.update_alexa_list
              metadata: {}
              data: {}
              alias: Update Alexa list
          alias: Anylist list was updated (update Alexa)
        - conditions:
            - condition: trigger
              id:
                - 15-minute trigger
          sequence:
            - action: script.update_anylist
              metadata: {}
              data: {}
              alias: Update Anylist list
          alias: Alexa list was updated (update Anylist based on time)