diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md new file mode 100644 index 0000000..d7e3405 --- /dev/null +++ b/CONTRIBUTION.md @@ -0,0 +1,5 @@ +# Contributions + +Contributions are welcome. If you use HA and a Eufy Vacuum and want to make improvmenets, get in touch. + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4571d73 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2022 Brendan McCluskey + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index cb03fb0..5eccc29 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,266 @@ -# eufy-robovac-hass +[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs) + +[![Sponsor me on Github Sponsors](https://img.shields.io/badge/Sponsor-ea4aaa?style=for-the-badge&logo=github-sponsors&logoColor=%23EA4AAA&labelColor=white)](https://github.com/sponsors/CodeFoodPixels) +[![Tip me through ko-fi](https://img.shields.io/badge/Ko–Fi-FF5E5B?style=for-the-badge&logo=kofi&logoColor=%23FF5E5B&labelColor=white)](https://ko-fi.com/O5O3O08PA) +[![Tip me through PayPal](https://img.shields.io/badge/Paypal.me-00457C?style=for-the-badge&logo=paypal&logoColor=%2300457C&labelColor=white)](https://paypal.me/CodeFoodPixels) +[![Tip me through Monzo](https://img.shields.io/badge/Monzo.me-14233C?style=for-the-badge&logo=monzo&logoColor=%2314233C&labelColor=white)](https://monzo.me/codefoodpixels) + +# Eufy RobovVac control for Home Assistant + +A brand new version Eufy RoboVac integration for Home Assistant that includes a Config Flow to add your RoboVac(s) and the local key and ID required. All you need to do is enter your Eufy app credentials and the Config Flow will look up the details for you. After the initial config use the configuration button on the Integration to enter the RoboVac IP address when prompted. + +This work has evovled from the original work by Richard Mitchell https://github.com/mitchellrj and the countless others who have contributed over the last couple of years. It also builds on the work done by Andre Borie https://gitlab.com/Rjevski/eufy-device-id-and-local-key-grabber to get the required local ID and key. + +This project has been forked many times since the I am building upon the original work done by Richard and attempting to simplfy the operation and number of files involved. + +## Installation ## +Couple of Pre-reqs +1. Make sure your Home Assistant Core is up to date +2. Remove any previous Eufy or RoboVac installation including entries in the configuration.yaml + + +If you want you can clone this repo manually, oterwise use HACS (Recommended). + +### Using HACS +1. In HACS add this repo as an integration additional repository. +2. Then install it. +3. Restart Home Assistant +4. Go to the Integrations Page and Click +Add Integration button +5. Search for Eufy Robovac and select it +6. Enter your Eufy username and password (The ones you use to login to the add with) and submit +7. If youve done it correctly you should get a success dialoge and option to enter an Area for each RoboVac you have +8. Click Finish +9. On the Integrations Screen Locate your Eufy Robovac card and click the configure button +10. Select the Radio button beside the Vacuum name and type its IP addess in the box and press Submit +(You need to repeat steps 9 and 10 for each RoboVac you have) +11. Enjoy + +Please note: You may have to get a new version of the access key for your vacuum from time to time if Eufy change it. Worst case you have to Delete the integration and re add it to get the new key. + +### Optional 1: Scripts + +The integration is designed to work with the standard Home Assistant Lovelace card but that doesnt support all the options of your Robovac. I have created some scripts to send the relevant commands to the Robovac. + +Add the below text to your scripts.yaml file for a xxC RoboVAC. It should be in the same folder as your configuration.yaml +``` +15c_smallroomclean: + alias: 15C_smallRoomClean + sequence: + - service: vacuum.send_command + data: + command: smallRoomClean + target: + entity_id: vacuum.15c + mode: single +15c_edgeclean: + alias: 15C_edgeClean + sequence: + - service: vacuum.send_command + data: + command: edgeClean + target: + entity_id: vacuum.15c + mode: single +15c_dock: + alias: 15C_dock + sequence: + - service: vacuum.return_to_base + target: + entity_id: vacuum.15c + mode: single +``` +If you have a Gxx add this to your scripts.yaml +``` +g30_autoclean: + alias: G30_autoClean + sequence: + - service: vacuum.send_command + data: + command: autoClean + target: + entity_id: vacuum.g30 + mode: single +g30_autoreturn: + alias: G30_autoReturn + sequence: + - service: vacuum.send_command + data: + command: autoReturn + target: + entity_id: vacuum.g30 + mode: single +g30_donotdisturb: + alias: G30_do_Not_Disturb + sequence: + - service: vacuum.send_command + data: + command: doNotDisturb + target: + entity_id: vacuum.g30 + mode: single +g30_dock: + alias: G30_dock + sequence: + - service: vacuum.return_to_base + target: + entity_id: vacuum.g30 + mode: single +``` +If you have an X8 add this to your scripts.yaml +``` +x8_boostiq: + alias: x8_boostIQ + sequence: + - service: vacuum.send_command + data: + command: boostIQ + target: + entity_id: vacuum.x8 + mode: single +x8_autoclean: + alias: x8_autoClean + sequence: + - service: vacuum.send_command + data: + command: autoClean + target: + entity_id: vacuum.x8 + mode: single +x8_autoreturn: + alias: X8_autoReturn + sequence: + - service: vacuum.send_command + data: + command: autoReturn + target: + entity_id: vacuum.x8 + mode: single +x8_donotdisturb: + alias: X8_do_Not_Disturb + sequence: + - service: vacuum.send_command + data: + command: doNotDisturb + target: + entity_id: vacuum.x8 + mode: single +x8_dock: + alias: X8_dock + sequence: + - service: vacuum.return_to_base + target: + entity_id: vacuum.x8 + mode: single +``` +The facilities in the script options above only work on the those model series. i.e. You cant do edge cleaning on the G30 and you cant do the autoreturn on the 15C. + +### Optional 2 : Lovelace Card + +Search in HACS for the Vacuum Card by Denys Dovhan and install it and configure it in lovelace to use you vacuum. Note there is a minor "feature" in the vacuum card where it doesnt show the correct values in toolbar when they update and there is a template adjusting what is being displayed. A screen refresh shows the correct vaules. Hopefully this will be fixed soon. + +Edit the lovelace vaccum card and add the following to the cards yaml if you have a xxC. +``` +type: custom:vacuum-card +entity: vacuum.15c +image: default +show_name: true +show_status: true +show_toolbar: true +shortcuts: + - name: Dock + service: script.15c_dock + icon: mdi:home-map-marker + - name: Edge Cleaning + service: script.15c_edgeclean + icon: mdi:square-outline + - name: Small Room + service: script.15c_smallroomclean + icon: mdi:timer-cog-outline +``` +Again if you have the Gxx you will add these lines to the cards yaml. +``` +type: custom:vacuum-card +entity: vacuum.g30 +image: default +shortcuts: + - name: Dock + service: script.g30_dock + icon: mdi:home-map-marker + - name: Auto Clean + service: script.g30_autoclean + icon: mdi:caps-lock + - name: Auto Return + service: script.g30_autoreturn + icon: mdi:arrow-u-down-left-bold + - name: Do Not Disturb + service: script.g30_donotdisturb + icon: mdi:volume-off +stats: + default: + - attribute: cleaning_area + unit: sq meters + subtitle: Cleaning Area + - attribute: cleaning_time + value_template: '{{ (value | float(0) / 60) | round(1) }}' + unit: minutes + subtitle: Cleaning time + - attribute: auto_return + subtitle: Auto Ret + value_template: '{% if (value == true) %}On{% else %}Off{% endif %}' + - attribute: do_not_disturb + subtitle: Dnd + value_template: '{% if (value == true) %}On{% else %}Off{% endif %}' +``` +Again if you have the X8 you will add these lines to the cards yaml. +``` +type: custom:vacuum-card +entity: vacuum.x8 +image: default +stats: + default: + - attribute: cleaning_area + unit: sq meters + subtitle: Cleaning Area + - attribute: cleaning_time + value_template: '{{ (value | float(0) / 60) | round(1) }}' + unit: minutes + subtitle: Cleaning time + - attribute: boost_iq + subtitle: Boost IQ + value_template: '{% if (value == true) %}On{% else %}Off{% endif %}' + - attribute: auto_return + subtitle: Auto Ret + value_template: '{% if (value == true) %}On{% else %}Off{% endif %}' + - attribute: do_not_disturb + subtitle: Dnd + value_template: '{% if (value == true) %}On{% else %}Off{% endif %}' +shortcuts: + - name: Dock + service: script.x8_dock + icon: mdi:home-map-marker + - name: Auto Clean + service: script.x8_autoclean + icon: mdi:caps-lock + - name: Boost IQ + service: script.x8_boostiq + icon: mdi:bootstrap + - name: Auto Return + service: script.x8_autoreturn + icon: mdi:arrow-u-down-left-bold + - name: Do Not Disturb + service: script.x8_donotdisturb + icon: mdi:volume-off +``` + +## Debugging ## +I have left quite a few debug statements in the code and they may be useful to see whats happening by looking in the System Log files. The Log Viewer Addon available in the Home Assistance store can be very useful to watch the logs being updated in real time. To get the debugging to add to the logs you need to add the below text to your configuration.yaml +``` +logger: + default: warning + logs: + custom_components.robovac.vacuum: debug + custom_components.robovac.tuyalocalapi: debug +``` +--- + diff --git a/custom_components/robovac/__init__.py b/custom_components/robovac/__init__.py new file mode 100644 index 0000000..a8b6742 --- /dev/null +++ b/custom_components/robovac/__init__.py @@ -0,0 +1,97 @@ +# Copyright 2022 Brendan McCluskey +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""The Eufy Robovac integration.""" +from __future__ import annotations +import logging + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform, CONF_IP_ADDRESS +from homeassistant.core import HomeAssistant +from .const import CONF_VACS, DOMAIN + +from .tuyalocaldiscovery import TuyaLocalDiscovery + +PLATFORMS = [Platform.VACUUM, Platform.SENSOR] +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass, entry) -> bool: + hass.data.setdefault(DOMAIN, {CONF_VACS:{}}) + + async def update_device(device): + entry = async_get_config_entry_for_device(hass, device["gwId"]) + + if entry == None: + return + + if not entry.state.recoverable: + return + + hass_data = entry.data.copy() + if ( + device["gwId"] in hass_data[CONF_VACS] + and device.get("ip") is not None + and hass_data[CONF_VACS][device["gwId"]].get("autodiscovery", True) + ): + if hass_data[CONF_VACS][device["gwId"]][CONF_IP_ADDRESS] != device["ip"]: + hass_data[CONF_VACS][device["gwId"]][CONF_IP_ADDRESS] = device["ip"] + hass.config_entries.async_update_entry(entry, data=hass_data) + await hass.config_entries.async_reload(entry.entry_id) + _LOGGER.debug( + "Updated ip address of {} to {}".format( + device["gwId"], device["ip"] + ) + ) + + tuyalocaldiscovery = TuyaLocalDiscovery(update_device) + try: + await tuyalocaldiscovery.start() + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, tuyalocaldiscovery.close) + except Exception: + _LOGGER.exception("failed to set up discovery") + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Eufy Robovac from a config entry.""" + entry.async_on_unload(entry.add_update_listener(update_listener)) + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms( + entry, PLATFORMS + ): + """Nothing""" + return unload_ok + + +async def update_listener(hass, entry): + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) + + +def async_get_config_entry_for_device(hass, device_id): + current_entries = hass.config_entries.async_entries(DOMAIN) + for entry in current_entries: + if device_id in entry.data[CONF_VACS]: + return entry + return None diff --git a/custom_components/robovac/config_flow.py b/custom_components/robovac/config_flow.py new file mode 100644 index 0000000..d0e7c86 --- /dev/null +++ b/custom_components/robovac/config_flow.py @@ -0,0 +1,281 @@ +# Copyright 2022 Brendan McCluskey +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Config flow for Eufy Robovac integration.""" +from __future__ import annotations + +import logging +from typing import Any, Optional +from copy import deepcopy + +import voluptuous as vol +import homeassistant.helpers.config_validation as cv + +from homeassistant import config_entries +from homeassistant.core import callback +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.selector import selector + +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_NAME, + CONF_ID, + CONF_MODEL, + CONF_USERNAME, + CONF_PASSWORD, + CONF_IP_ADDRESS, + CONF_DESCRIPTION, + CONF_MAC, + CONF_CLIENT_ID, + CONF_REGION, + CONF_TIME_ZONE, + CONF_COUNTRY_CODE, +) + +from .countries import ( + get_phone_code_by_country_code, + get_phone_code_by_region, + get_region_by_country_code, + get_region_by_phone_code, +) + +from .const import CONF_AUTODISCOVERY, DOMAIN, CONF_VACS + +from .tuyawebapi import TuyaAPISession +from .eufywebapi import EufyLogon + +_LOGGER = logging.getLogger(__name__) + +USER_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } +) + + +def get_eufy_vacuums(self): + """Login to Eufy and get the vacuum details""" + + eufy_session = EufyLogon(self["username"], self["password"]) + response = eufy_session.get_user_info() + if response.status_code != 200: + raise CannotConnect + + user_response = response.json() + if user_response["res_code"] != 1: + raise InvalidAuth + + response = eufy_session.get_device_info( + user_response["user_info"]["request_host"], + user_response["user_info"]["id"], + user_response["access_token"], + ) + + device_response = response.json() + + response = eufy_session.get_user_settings( + user_response["user_info"]["request_host"], + user_response["user_info"]["id"], + user_response["access_token"], + ) + settings_response = response.json() + + self[CONF_CLIENT_ID] = user_response["user_info"]["id"] + if ( + "tuya_home" in settings_response["setting"]["home_setting"] + and "tuya_region_code" + in settings_response["setting"]["home_setting"]["tuya_home"] + ): + self[CONF_REGION] = settings_response["setting"]["home_setting"]["tuya_home"][ + "tuya_region_code" + ] + if user_response["user_info"]["phone_code"]: + self[CONF_COUNTRY_CODE] = user_response["user_info"]["phone_code"] + else: + self[CONF_COUNTRY_CODE] = get_phone_code_by_region(self[CONF_REGION]) + elif user_response["user_info"]["phone_code"]: + self[CONF_REGION] = get_region_by_phone_code( + user_response["user_info"]["phone_code"] + ) + self[CONF_COUNTRY_CODE] = user_response["user_info"]["phone_code"] + elif user_response["user_info"]["country"]: + self[CONF_REGION] = get_region_by_country_code( + user_response["user_info"]["country"] + ) + self[CONF_COUNTRY_CODE] = get_phone_code_by_country_code( + user_response["user_info"]["country"] + ) + else: + self[CONF_REGION] = "EU" + self[CONF_COUNTRY_CODE] = "44" + + self[CONF_TIME_ZONE] = user_response["user_info"]["timezone"] + + tuya_client = TuyaAPISession( + username="eh-" + self[CONF_CLIENT_ID], + region=self[CONF_REGION], + timezone=self[CONF_TIME_ZONE], + phone_code=self[CONF_COUNTRY_CODE], + ) + + items = device_response["devices"] + self[CONF_VACS] = {} + for item in items: + if item["product"]["appliance"] == "Cleaning": + try: + device = tuya_client.get_device(item["id"]) + _LOGGER.debug("Robovac schema: {}".format(device["schema"])) + + vac_details = { + CONF_ID: item["id"], + CONF_MODEL: item["product"]["product_code"], + CONF_NAME: item["alias_name"], + CONF_DESCRIPTION: item["name"], + CONF_MAC: item["wifi"]["mac"], + CONF_IP_ADDRESS: "", + CONF_AUTODISCOVERY: True, + CONF_ACCESS_TOKEN: device["localKey"], + } + self[CONF_VACS][item["id"]] = vac_details + except: + _LOGGER.debug( + "Vacuum {} found on Eufy, but not on Tuya. Skipping.".format( + item["id"] + ) + ) + + return response + + +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: + """Validate the user input allows us to connect.""" + await hass.async_add_executor_job(get_eufy_vacuums, data) + return data + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Eufy Robovac.""" + + data: Optional[dict[str, Any]] + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if user_input is None: + return self.async_show_form(step_id="user", data_schema=USER_SCHEMA) + errors = {} + try: + unique_id = user_input[CONF_USERNAME] + valid_data = await validate_input(self.hass, user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + except Exception as e: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception: {}".format(e)) + errors["base"] = "unknown" + else: + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + # return await self.async_step_repo(valid_data) + return self.async_create_entry(title=unique_id, data=valid_data) + return self.async_show_form( + step_id="user", data_schema=USER_SCHEMA, errors=errors + ) + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + + +class CannotConnect(HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(HomeAssistantError): + """Error to indicate there is invalid auth.""" + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handles options flow for the component.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + self.config_entry = config_entry + self.selected_vacuum = None + + async def async_step_init(self, user_input=None): + errors = {} + + if user_input is not None: + self.selected_vacuum = user_input["selected_vacuum"] + return await self.async_step_edit() + + vacuums_config = self.config_entry.data[CONF_VACS] + vacuum_list = {} + for vacuum_id in vacuums_config: + vacuum_list[vacuum_id] = vacuums_config[vacuum_id]["name"] + + devices_schema = vol.Schema( + {vol.Required("selected_vacuum"): vol.In(vacuum_list)} + ) + + return self.async_show_form( + step_id="init", data_schema=devices_schema, errors=errors + ) + + async def async_step_edit(self, user_input=None): + """Manage the options for the custom component.""" + errors = {} + + vacuums = self.config_entry.data[CONF_VACS] + + if user_input is not None: + updated_vacuums = deepcopy(vacuums) + updated_vacuums[self.selected_vacuum][CONF_AUTODISCOVERY] = user_input[ + CONF_AUTODISCOVERY + ] + if user_input[CONF_IP_ADDRESS]: + updated_vacuums[self.selected_vacuum][CONF_IP_ADDRESS] = user_input[ + CONF_IP_ADDRESS + ] + + self.hass.config_entries.async_update_entry( + self.config_entry, + data={CONF_VACS: updated_vacuums}, + ) + + return self.async_create_entry(title="", data={}) + + options_schema = vol.Schema( + { + vol.Required( + CONF_AUTODISCOVERY, + default=vacuums[self.selected_vacuum].get(CONF_AUTODISCOVERY, True), + ): bool, + vol.Optional( + CONF_IP_ADDRESS, + default=vacuums[self.selected_vacuum].get(CONF_IP_ADDRESS), + ): str, + } + ) + + return self.async_show_form( + step_id="edit", data_schema=options_schema, errors=errors + ) diff --git a/custom_components/robovac/const.py b/custom_components/robovac/const.py new file mode 100644 index 0000000..a188705 --- /dev/null +++ b/custom_components/robovac/const.py @@ -0,0 +1,8 @@ +"""Constants for the Eufy Robovac integration.""" + +DOMAIN = "robovac" +CONF_VACS = "vacuums" +CONF_AUTODISCOVERY = "autodiscovery" +REFRESH_RATE = 60 +PING_RATE = 10 +TIMEOUT = 5 diff --git a/custom_components/robovac/countries.py b/custom_components/robovac/countries.py new file mode 100644 index 0000000..37b670b --- /dev/null +++ b/custom_components/robovac/countries.py @@ -0,0 +1,228 @@ +COUNTRIES = [ + {"country_code": "AF", "phone_code": "93", "tuya_region": "EU"}, + {"country_code": "AL", "phone_code": "355", "tuya_region": "EU"}, + {"country_code": "DZ", "phone_code": "213", "tuya_region": "EU"}, + {"country_code": "AO", "phone_code": "244", "tuya_region": "EU"}, + {"country_code": "AR", "phone_code": "54", "tuya_region": "AZ"}, + {"country_code": "AM", "phone_code": "374", "tuya_region": "EU"}, + {"country_code": "AU", "phone_code": "61", "tuya_region": "AZ"}, + {"country_code": "AT", "phone_code": "43", "tuya_region": "EU"}, + {"country_code": "AZ", "phone_code": "994", "tuya_region": "EU"}, + {"country_code": "BH", "phone_code": "973", "tuya_region": "EU"}, + {"country_code": "BD", "phone_code": "880", "tuya_region": "EU"}, + {"country_code": "BY", "phone_code": "375", "tuya_region": "EU"}, + {"country_code": "BE", "phone_code": "32", "tuya_region": "EU"}, + {"country_code": "BZ", "phone_code": "501", "tuya_region": "EU"}, + {"country_code": "BJ", "phone_code": "229", "tuya_region": "EU"}, + {"country_code": "BT", "phone_code": "975", "tuya_region": "EU"}, + {"country_code": "BO", "phone_code": "591", "tuya_region": "AZ"}, + {"country_code": "BA", "phone_code": "387", "tuya_region": "EU"}, + {"country_code": "BW", "phone_code": "267", "tuya_region": "EU"}, + {"country_code": "BR", "phone_code": "55", "tuya_region": "AZ"}, + {"country_code": "VG", "phone_code": "1284", "tuya_region": "EU"}, + {"country_code": "BN", "phone_code": "673", "tuya_region": "EU"}, + {"country_code": "BG", "phone_code": "359", "tuya_region": "EU"}, + {"country_code": "BF", "phone_code": "226", "tuya_region": "EU"}, + {"country_code": "BI", "phone_code": "257", "tuya_region": "EU"}, + {"country_code": "KH", "phone_code": "855", "tuya_region": "EU"}, + {"country_code": "CM", "phone_code": "237", "tuya_region": "EU"}, + {"country_code": "US", "phone_code": "1", "tuya_region": "AZ"}, + {"country_code": "CA", "phone_code": "1", "tuya_region": "AZ"}, + {"country_code": "CV", "phone_code": "238", "tuya_region": "EU"}, + {"country_code": "KY", "phone_code": "1345", "tuya_region": "EU"}, + {"country_code": "CF", "phone_code": "236", "tuya_region": "EU"}, + {"country_code": "TD", "phone_code": "235", "tuya_region": "EU"}, + {"country_code": "CL", "phone_code": "56", "tuya_region": "AZ"}, + {"country_code": "CN", "phone_code": "86", "tuya_region": "AY"}, + {"country_code": "CO", "phone_code": "57", "tuya_region": "AZ"}, + {"country_code": "KM", "phone_code": "269", "tuya_region": "EU"}, + {"country_code": "CG", "phone_code": "242", "tuya_region": "EU"}, + {"country_code": "CD", "phone_code": "243", "tuya_region": "EU"}, + {"country_code": "CR", "phone_code": "506", "tuya_region": "EU"}, + {"country_code": "HR", "phone_code": "385", "tuya_region": "EU"}, + {"country_code": "CY", "phone_code": "357", "tuya_region": "EU"}, + {"country_code": "CZ", "phone_code": "420", "tuya_region": "EU"}, + {"country_code": "DK", "phone_code": "45", "tuya_region": "EU"}, + {"country_code": "DJ", "phone_code": "253", "tuya_region": "EU"}, + {"country_code": "DO", "phone_code": "1809", "tuya_region": "EU"}, + {"country_code": "DO", "phone_code": "1829", "tuya_region": "EU"}, + {"country_code": "DO", "phone_code": "1849", "tuya_region": "EU"}, + {"country_code": "EC", "phone_code": "593", "tuya_region": "AZ"}, + {"country_code": "EG", "phone_code": "20", "tuya_region": "EU"}, + {"country_code": "SV", "phone_code": "503", "tuya_region": "EU"}, + {"country_code": "GQ", "phone_code": "240", "tuya_region": "EU"}, + {"country_code": "ER", "phone_code": "291", "tuya_region": "EU"}, + {"country_code": "EE", "phone_code": "372", "tuya_region": "EU"}, + {"country_code": "ET", "phone_code": "251", "tuya_region": "EU"}, + {"country_code": "FJ", "phone_code": "679", "tuya_region": "EU"}, + {"country_code": "FI", "phone_code": "358", "tuya_region": "EU"}, + {"country_code": "FR", "phone_code": "33", "tuya_region": "EU"}, + {"country_code": "GA", "phone_code": "241", "tuya_region": "EU"}, + {"country_code": "GM", "phone_code": "220", "tuya_region": "EU"}, + {"country_code": "GE", "phone_code": "995", "tuya_region": "EU"}, + {"country_code": "DE", "phone_code": "49", "tuya_region": "EU"}, + {"country_code": "GH", "phone_code": "233", "tuya_region": "EU"}, + {"country_code": "GR", "phone_code": "30", "tuya_region": "EU"}, + {"country_code": "GL", "phone_code": "299", "tuya_region": "EU"}, + {"country_code": "GT", "phone_code": "502", "tuya_region": "AZ"}, + {"country_code": "GN", "phone_code": "224", "tuya_region": "EU"}, + {"country_code": "GY", "phone_code": "592", "tuya_region": "EU"}, + {"country_code": "HT", "phone_code": "509", "tuya_region": "EU"}, + {"country_code": "HN", "phone_code": "504", "tuya_region": "EU"}, + {"country_code": "HK", "phone_code": "852", "tuya_region": "AZ"}, + {"country_code": "HU", "phone_code": "36", "tuya_region": "EU"}, + {"country_code": "IS", "phone_code": "354", "tuya_region": "EU"}, + {"country_code": "IN", "phone_code": "91", "tuya_region": "IN"}, + {"country_code": "ID", "phone_code": "62", "tuya_region": "AZ"}, + {"country_code": "IR", "phone_code": "98", "tuya_region": "EU"}, + {"country_code": "IQ", "phone_code": "964", "tuya_region": "EU"}, + {"country_code": "IE", "phone_code": "353", "tuya_region": "EU"}, + {"country_code": "IM", "phone_code": "44", "tuya_region": "EU"}, + {"country_code": "IL", "phone_code": "972", "tuya_region": "EU"}, + {"country_code": "IT", "phone_code": "39", "tuya_region": "AZ"}, + {"country_code": "CI", "phone_code": "225", "tuya_region": "EU"}, + {"country_code": "JM", "phone_code": "1876", "tuya_region": "EU"}, + {"country_code": "JP", "phone_code": "81", "tuya_region": "AZ"}, + {"country_code": "JO", "phone_code": "962", "tuya_region": "EU"}, + {"country_code": "KZ", "phone_code": "7", "tuya_region": "EU"}, + {"country_code": "KE", "phone_code": "254", "tuya_region": "EU"}, + {"country_code": "KR", "phone_code": "82", "tuya_region": "AZ"}, + {"country_code": "KW", "phone_code": "965", "tuya_region": "EU"}, + {"country_code": "KG", "phone_code": "996", "tuya_region": "EU"}, + {"country_code": "LA", "phone_code": "856", "tuya_region": "EU"}, + {"country_code": "LV", "phone_code": "371", "tuya_region": "EU"}, + {"country_code": "LB", "phone_code": "961", "tuya_region": "EU"}, + {"country_code": "LS", "phone_code": "266", "tuya_region": "EU"}, + {"country_code": "LR", "phone_code": "231", "tuya_region": "EU"}, + {"country_code": "LY", "phone_code": "218", "tuya_region": "EU"}, + {"country_code": "LT", "phone_code": "370", "tuya_region": "EU"}, + {"country_code": "LU", "phone_code": "352", "tuya_region": "EU"}, + {"country_code": "MO", "phone_code": "853", "tuya_region": "AZ"}, + {"country_code": "MK", "phone_code": "389", "tuya_region": "EU"}, + {"country_code": "MG", "phone_code": "261", "tuya_region": "EU"}, + {"country_code": "MW", "phone_code": "265", "tuya_region": "EU"}, + {"country_code": "MY", "phone_code": "60", "tuya_region": "AZ"}, + {"country_code": "MV", "phone_code": "960", "tuya_region": "EU"}, + {"country_code": "ML", "phone_code": "223", "tuya_region": "EU"}, + {"country_code": "MT", "phone_code": "356", "tuya_region": "EU"}, + {"country_code": "MR", "phone_code": "222", "tuya_region": "EU"}, + {"country_code": "MU", "phone_code": "230", "tuya_region": "EU"}, + {"country_code": "MX", "phone_code": "52", "tuya_region": "AZ"}, + {"country_code": "MD", "phone_code": "373", "tuya_region": "EU"}, + {"country_code": "MC", "phone_code": "377", "tuya_region": "EU"}, + {"country_code": "MN", "phone_code": "976", "tuya_region": "EU"}, + {"country_code": "ME", "phone_code": "382", "tuya_region": "EU"}, + {"country_code": "MA", "phone_code": "212", "tuya_region": "EU"}, + {"country_code": "MZ", "phone_code": "258", "tuya_region": "EU"}, + {"country_code": "MM", "phone_code": "95", "tuya_region": "AZ"}, + {"country_code": "NA", "phone_code": "264", "tuya_region": "EU"}, + {"country_code": "NP", "phone_code": "977", "tuya_region": "EU"}, + {"country_code": "NL", "phone_code": "31", "tuya_region": "EU"}, + {"country_code": "NZ", "phone_code": "64", "tuya_region": "AZ"}, + {"country_code": "NI", "phone_code": "505", "tuya_region": "AZ"}, + {"country_code": "NE", "phone_code": "227", "tuya_region": "EU"}, + {"country_code": "NG", "phone_code": "234", "tuya_region": "EU"}, + {"country_code": "KP", "phone_code": "850", "tuya_region": "EU"}, + {"country_code": "NO", "phone_code": "47", "tuya_region": "EU"}, + {"country_code": "OM", "phone_code": "968", "tuya_region": "EU"}, + {"country_code": "PK", "phone_code": "92", "tuya_region": "EU"}, + {"country_code": "PA", "phone_code": "507", "tuya_region": "EU"}, + {"country_code": "PY", "phone_code": "595", "tuya_region": "AZ"}, + {"country_code": "PE", "phone_code": "51", "tuya_region": "AZ"}, + {"country_code": "PH", "phone_code": "63", "tuya_region": "AZ"}, + {"country_code": "PL", "phone_code": "48", "tuya_region": "EU"}, + {"country_code": "PF", "phone_code": "689", "tuya_region": "EU"}, + {"country_code": "PT", "phone_code": "351", "tuya_region": "EU"}, + {"country_code": "PR", "phone_code": "1787", "tuya_region": "AZ"}, + {"country_code": "QA", "phone_code": "974", "tuya_region": "EU"}, + {"country_code": "RE", "phone_code": "262", "tuya_region": "EU"}, + {"country_code": "RO", "phone_code": "40", "tuya_region": "EU"}, + {"country_code": "RU", "phone_code": "7", "tuya_region": "EU"}, + {"country_code": "RW", "phone_code": "250", "tuya_region": "EU"}, + {"country_code": "SM", "phone_code": "378", "tuya_region": "EU"}, + {"country_code": "SA", "phone_code": "966", "tuya_region": "EU"}, + {"country_code": "SN", "phone_code": "221", "tuya_region": "EU"}, + {"country_code": "RS", "phone_code": "381", "tuya_region": "EU"}, + {"country_code": "SL", "phone_code": "232", "tuya_region": "EU"}, + {"country_code": "SG", "phone_code": "65", "tuya_region": "EU"}, + {"country_code": "SK", "phone_code": "421", "tuya_region": "EU"}, + {"country_code": "SI", "phone_code": "386", "tuya_region": "EU"}, + {"country_code": "SO", "phone_code": "252", "tuya_region": "EU"}, + {"country_code": "ZA", "phone_code": "27", "tuya_region": "EU"}, + {"country_code": "ES", "phone_code": "34", "tuya_region": "EU"}, + {"country_code": "LK", "phone_code": "94", "tuya_region": "EU"}, + {"country_code": "SD", "phone_code": "249", "tuya_region": "EU"}, + {"country_code": "SR", "phone_code": "597", "tuya_region": "AZ"}, + {"country_code": "SZ", "phone_code": "268", "tuya_region": "EU"}, + {"country_code": "SE", "phone_code": "46", "tuya_region": "EU"}, + {"country_code": "CH", "phone_code": "41", "tuya_region": "EU"}, + {"country_code": "SY", "phone_code": "963", "tuya_region": "EU"}, + {"country_code": "TW", "phone_code": "886", "tuya_region": "AZ"}, + {"country_code": "TJ", "phone_code": "992", "tuya_region": "EU"}, + {"country_code": "TZ", "phone_code": "255", "tuya_region": "EU"}, + {"country_code": "TH", "phone_code": "66", "tuya_region": "AZ"}, + {"country_code": "TG", "phone_code": "228", "tuya_region": "EU"}, + {"country_code": "TO", "phone_code": "676", "tuya_region": "EU"}, + {"country_code": "TT", "phone_code": "1868", "tuya_region": "EU"}, + {"country_code": "TN", "phone_code": "216", "tuya_region": "EU"}, + {"country_code": "TR", "phone_code": "90", "tuya_region": "EU"}, + {"country_code": "TM", "phone_code": "993", "tuya_region": "EU"}, + {"country_code": "VI", "phone_code": "1340", "tuya_region": "EU"}, + {"country_code": "UG", "phone_code": "256", "tuya_region": "EU"}, + {"country_code": "UA", "phone_code": "380", "tuya_region": "EU"}, + {"country_code": "AE", "phone_code": "971", "tuya_region": "EU"}, + {"country_code": "GB", "phone_code": "44", "tuya_region": "EU"}, + {"country_code": "UY", "phone_code": "598", "tuya_region": "AZ"}, + {"country_code": "UZ", "phone_code": "998", "tuya_region": "EU"}, + {"country_code": "VA", "phone_code": "379", "tuya_region": "EU"}, + {"country_code": "VE", "phone_code": "58", "tuya_region": "AZ"}, + {"country_code": "VN", "phone_code": "84", "tuya_region": "AZ"}, + {"country_code": "YE", "phone_code": "967", "tuya_region": "EU"}, + {"country_code": "ZR", "phone_code": "243", "tuya_region": "EU"}, + {"country_code": "ZM", "phone_code": "260", "tuya_region": "EU"}, + {"country_code": "ZW", "phone_code": "263", "tuya_region": "EU"}, + {"country_code": "NCL", "phone_code": "687", "tuya_region": "EU"}, + {"country_code": "MQ", "phone_code": "596", "tuya_region": "EU"}, +] + + +def get_region_by_country_code(country_code): + country = next( + (item for item in COUNTRIES if item["country_code"] == country_code), None + ) + + if country is None: + return "EU" + + return country["tuya_region"] + + +def get_region_by_phone_code(phone_code): + country = next( + (item for item in COUNTRIES if item["phone_code"] == phone_code), None + ) + + if country is None: + return "EU" + + return country["tuya_region"] + + +def get_phone_code_by_region(region): + country = next((item for item in COUNTRIES if item["tuya_region"] == region), None) + + if country is None: + return "44" + + return country["phone_code"] + + +def get_phone_code_by_country_code(country_code): + country = next( + (item for item in COUNTRIES if item["country_code"] == country_code), None + ) + + if country is None: + return "44" + + return country["phone_code"] diff --git a/custom_components/robovac/errors.py b/custom_components/robovac/errors.py new file mode 100644 index 0000000..424ed77 --- /dev/null +++ b/custom_components/robovac/errors.py @@ -0,0 +1,40 @@ +ERROR_MESSAGES = { + "IP_ADDRESS": "IP Address not set", + "CONNECTION_FAILED": "Connection to the vacuum failed", + "UNSUPPORTED_MODEL": "This model is not supported", + "no_error": "None", + 1:"Front bumper stuck", + 2:"Wheel stuck", + 3:"Side brush", + 4:"Rolling brush bar stuck", + 5:"Device trapped", + 6:"Device trapped", + 7:"Wheel suspended", + 8:"Low battery", + 9:"Magnetic boundary", + 12:"Right wall sensor", + 13:"Device tilted", + 14:"Insert dust collector", + 17:"Restricted area detected", + 18:"Laser cover stuck", + 19:"Laser sesor stuck", + 20:"Laser sensor blocked", + 21:"Base blocked", + "S1":"Battery", + "S2":"Wheel Module", + "S3":"Side Brush", + "S4":"Suction Fan", + "S5":"Rolling Brush", + "S8":"Path Tracking Sensor", + "Wheel_stuck":"Wheel stuck", + "R_brush_stuck":"Rolling brush stuck", + "Crash_bar_stuck":"Front bumper stuck", + "sensor_dirty":"Sensor dirty", + "N_enough_pow":"Low battery", + "Stuck_5_min":"Device trapped", + "Fan_stuck":"Fan stuck", + "S_brush_stuck":"Side brush stuck", +} + +def getErrorMessage(code): + return ERROR_MESSAGES.get(code, code) diff --git a/custom_components/robovac/eufywebapi.py b/custom_components/robovac/eufywebapi.py new file mode 100644 index 0000000..683c280 --- /dev/null +++ b/custom_components/robovac/eufywebapi.py @@ -0,0 +1,45 @@ +"""Original Work from here: Andre Borie https://gitlab.com/Rjevski/eufy-device-id-and-local-key-grabber""" + +import requests + +eufyheaders = { + "User-Agent": "EufyHome-Android-2.4.0", + "timezone": "Europe/London", + "category": "Home", + "token": "", + "uid": "", + "openudid": "sdk_gphone64_arm64", + "clientType": "2", + "language": "en", + "country": "US", + "Accept-Encoding": "gzip", +} + + +class EufyLogon: + def __init__(self, username, password): + self.username = username + self.password = password + + def get_user_info(self): + login_url = "https://home-api.eufylife.com/v1/user/email/login" + login_auth = { + "client_Secret": "GQCpr9dSp3uQpsOMgJ4xQ", + "client_id": "eufyhome-app", + "email": self.username, + "password": self.password, + } + + return requests.post(login_url, json=login_auth, headers=eufyheaders) + + def get_user_settings(self, url, userid, token): + setting_url = url + "/v1/user/setting" + eufyheaders["token"] = token + eufyheaders["id"] = userid + return requests.request("GET", setting_url, headers=eufyheaders, timeout=1.5) + + def get_device_info(self, url, userid, token): + device_url = url + "/v1/device/v2" + eufyheaders["token"] = token + eufyheaders["id"] = userid + return requests.request("GET", device_url, headers=eufyheaders) diff --git a/custom_components/robovac/manifest.json b/custom_components/robovac/manifest.json new file mode 100644 index 0000000..21c1d6a --- /dev/null +++ b/custom_components/robovac/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "robovac", + "name": "Eufy Robovac", + "codeowners": ["@codefoodpixels"], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/codefoodpixels/robovac", + "integration_type": "device", + "iot_class": "local_polling", + "issue_tracker": "https://github.com/codefoodpixels/robovac/issues", + "requirements": [], + "version": "1.0.0" +} diff --git a/custom_components/robovac/robovac.py b/custom_components/robovac/robovac.py new file mode 100644 index 0000000..43fdeae --- /dev/null +++ b/custom_components/robovac/robovac.py @@ -0,0 +1,45 @@ +from .vacuums.base import RobovacCommand +from .tuyalocalapi import TuyaDevice +from .vacuums import ROBOVAC_MODELS + + +class ModelNotSupportedException(Exception): + """This model is not supported""" + + +class RoboVac(TuyaDevice): + """""" + + def __init__(self, model_code, *args, **kwargs): + if model_code not in ROBOVAC_MODELS: + raise ModelNotSupportedException( + "Model {} is not supported".format(model_code) + ) + + self.model_details = ROBOVAC_MODELS[model_code] + super().__init__(self.model_details, *args, **kwargs) + + def getHomeAssistantFeatures(self): + return self.model_details.homeassistant_features + + def getRoboVacFeatures(self): + return self.model_details.robovac_features + + def getFanSpeeds(self): + return self.model_details.commands[RobovacCommand.FAN_SPEED]["values"] + + def getModes(self): + return self.model_details.commands[RobovacCommand.MODE]["values"] + + def getSupportedCommands(self): + return list(self.model_details.commands.keys()) + + def getCommandCodes(self): + command_codes = {} + for key, value in self.model_details.commands.items(): + if isinstance(value, dict): + command_codes[key] = str(value["code"]) + else: + command_codes[key] = str(value) + + return command_codes diff --git a/custom_components/robovac/sensor.py b/custom_components/robovac/sensor.py new file mode 100644 index 0000000..539c4f2 --- /dev/null +++ b/custom_components/robovac/sensor.py @@ -0,0 +1,63 @@ +import logging +from datetime import timedelta + +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE, EntityCategory, CONF_NAME, CONF_ID +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.device_registry import DeviceInfo + +from .const import CONF_VACS, DOMAIN, REFRESH_RATE + +_LOGGER = logging.getLogger(__name__) + +BATTERY = "Battery" +SCAN_INTERVAL = timedelta(seconds=REFRESH_RATE) + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Initialize my test integration 2 config entry.""" + vacuums = config_entry.data[CONF_VACS] + for item in vacuums: + item = vacuums[item] + entity = RobovacSensorEntity(item) + async_add_entities([entity]) + +class RobovacSensorEntity(SensorEntity): + _attr_has_entity_name = True + _attr_device_class = SensorDeviceClass.BATTERY + _attr_entity_category = EntityCategory.DIAGNOSTIC + _attr_native_unit_of_measurement = PERCENTAGE + _attr_available = False + + def __init__(self, item): + self.robovac = item + self.robovac_id = item[CONF_ID] + self._attr_unique_id = item[CONF_ID] + self._battery_level = None + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, item[CONF_ID])}, + name=item[CONF_NAME] + ) + + def update(self): + try: + self._battery_level = self.hass.data[DOMAIN][CONF_VACS][self.robovac_id].battery_level + self._attr_available = True + except: + _LOGGER.debug("Failed to get battery level for {}".format(self.robovac_id)) + self._battery_level = None + self._attr_available = False + + @property + def native_value(self) -> str | None: + """Return the state.""" + if self._battery_level is not None: + return self._battery_level + return None + diff --git a/custom_components/robovac/strings.json b/custom_components/robovac/strings.json new file mode 100644 index 0000000..446bb9f --- /dev/null +++ b/custom_components/robovac/strings.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Username" + }, + "description": "Enter your Eufy account details" + } + } + }, + "options": { + "step": { + "init": { + "title": "Manage vacuums", + "data": { + "selected_vacuum": "Select the Vacuum to edit" + } + }, + "edit": { + "title": "Edit vacuum", + "data": { + "autodiscovery": "Enable autodiscovery", + "ip_address": "IP Address" + }, + "description": "Autodiscovery will automatically update the IP address" + } + } + } +} diff --git a/custom_components/robovac/translations/en.json b/custom_components/robovac/translations/en.json new file mode 100644 index 0000000..446bb9f --- /dev/null +++ b/custom_components/robovac/translations/en.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Username" + }, + "description": "Enter your Eufy account details" + } + } + }, + "options": { + "step": { + "init": { + "title": "Manage vacuums", + "data": { + "selected_vacuum": "Select the Vacuum to edit" + } + }, + "edit": { + "title": "Edit vacuum", + "data": { + "autodiscovery": "Enable autodiscovery", + "ip_address": "IP Address" + }, + "description": "Autodiscovery will automatically update the IP address" + } + } + } +} diff --git a/custom_components/robovac/tuyalocalapi.py b/custom_components/robovac/tuyalocalapi.py new file mode 100644 index 0000000..e7cf854 --- /dev/null +++ b/custom_components/robovac/tuyalocalapi.py @@ -0,0 +1,929 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019 Richard Mitchell +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Based on portions of https://github.com/codetheweb/tuyapi/ +# +# MIT License +# +# Copyright (c) 2017 Max Isom +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import asyncio +import base64 +import json +import logging +import socket +import struct +import sys +import time +import traceback +from typing import Callable, Coroutine + +from cryptography.hazmat.backends.openssl import backend as openssl_backend +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.hashes import Hash, MD5 +from cryptography.hazmat.primitives.padding import PKCS7 + +from .vacuums.base import RobovacCommand + +INITIAL_BACKOFF = 5 +INITIAL_QUEUE_TIME = 0.1 +BACKOFF_MULTIPLIER = 1.70224 +_LOGGER = logging.getLogger(__name__) +MESSAGE_PREFIX_FORMAT = ">IIII" +MESSAGE_SUFFIX_FORMAT = ">II" +MAGIC_PREFIX = 0x000055AA +MAGIC_SUFFIX = 0x0000AA55 +MAGIC_SUFFIX_BYTES = struct.pack(">I", MAGIC_SUFFIX) +CRC_32_TABLE = [ + 0x00000000, + 0x77073096, + 0xEE0E612C, + 0x990951BA, + 0x076DC419, + 0x706AF48F, + 0xE963A535, + 0x9E6495A3, + 0x0EDB8832, + 0x79DCB8A4, + 0xE0D5E91E, + 0x97D2D988, + 0x09B64C2B, + 0x7EB17CBD, + 0xE7B82D07, + 0x90BF1D91, + 0x1DB71064, + 0x6AB020F2, + 0xF3B97148, + 0x84BE41DE, + 0x1ADAD47D, + 0x6DDDE4EB, + 0xF4D4B551, + 0x83D385C7, + 0x136C9856, + 0x646BA8C0, + 0xFD62F97A, + 0x8A65C9EC, + 0x14015C4F, + 0x63066CD9, + 0xFA0F3D63, + 0x8D080DF5, + 0x3B6E20C8, + 0x4C69105E, + 0xD56041E4, + 0xA2677172, + 0x3C03E4D1, + 0x4B04D447, + 0xD20D85FD, + 0xA50AB56B, + 0x35B5A8FA, + 0x42B2986C, + 0xDBBBC9D6, + 0xACBCF940, + 0x32D86CE3, + 0x45DF5C75, + 0xDCD60DCF, + 0xABD13D59, + 0x26D930AC, + 0x51DE003A, + 0xC8D75180, + 0xBFD06116, + 0x21B4F4B5, + 0x56B3C423, + 0xCFBA9599, + 0xB8BDA50F, + 0x2802B89E, + 0x5F058808, + 0xC60CD9B2, + 0xB10BE924, + 0x2F6F7C87, + 0x58684C11, + 0xC1611DAB, + 0xB6662D3D, + 0x76DC4190, + 0x01DB7106, + 0x98D220BC, + 0xEFD5102A, + 0x71B18589, + 0x06B6B51F, + 0x9FBFE4A5, + 0xE8B8D433, + 0x7807C9A2, + 0x0F00F934, + 0x9609A88E, + 0xE10E9818, + 0x7F6A0DBB, + 0x086D3D2D, + 0x91646C97, + 0xE6635C01, + 0x6B6B51F4, + 0x1C6C6162, + 0x856530D8, + 0xF262004E, + 0x6C0695ED, + 0x1B01A57B, + 0x8208F4C1, + 0xF50FC457, + 0x65B0D9C6, + 0x12B7E950, + 0x8BBEB8EA, + 0xFCB9887C, + 0x62DD1DDF, + 0x15DA2D49, + 0x8CD37CF3, + 0xFBD44C65, + 0x4DB26158, + 0x3AB551CE, + 0xA3BC0074, + 0xD4BB30E2, + 0x4ADFA541, + 0x3DD895D7, + 0xA4D1C46D, + 0xD3D6F4FB, + 0x4369E96A, + 0x346ED9FC, + 0xAD678846, + 0xDA60B8D0, + 0x44042D73, + 0x33031DE5, + 0xAA0A4C5F, + 0xDD0D7CC9, + 0x5005713C, + 0x270241AA, + 0xBE0B1010, + 0xC90C2086, + 0x5768B525, + 0x206F85B3, + 0xB966D409, + 0xCE61E49F, + 0x5EDEF90E, + 0x29D9C998, + 0xB0D09822, + 0xC7D7A8B4, + 0x59B33D17, + 0x2EB40D81, + 0xB7BD5C3B, + 0xC0BA6CAD, + 0xEDB88320, + 0x9ABFB3B6, + 0x03B6E20C, + 0x74B1D29A, + 0xEAD54739, + 0x9DD277AF, + 0x04DB2615, + 0x73DC1683, + 0xE3630B12, + 0x94643B84, + 0x0D6D6A3E, + 0x7A6A5AA8, + 0xE40ECF0B, + 0x9309FF9D, + 0x0A00AE27, + 0x7D079EB1, + 0xF00F9344, + 0x8708A3D2, + 0x1E01F268, + 0x6906C2FE, + 0xF762575D, + 0x806567CB, + 0x196C3671, + 0x6E6B06E7, + 0xFED41B76, + 0x89D32BE0, + 0x10DA7A5A, + 0x67DD4ACC, + 0xF9B9DF6F, + 0x8EBEEFF9, + 0x17B7BE43, + 0x60B08ED5, + 0xD6D6A3E8, + 0xA1D1937E, + 0x38D8C2C4, + 0x4FDFF252, + 0xD1BB67F1, + 0xA6BC5767, + 0x3FB506DD, + 0x48B2364B, + 0xD80D2BDA, + 0xAF0A1B4C, + 0x36034AF6, + 0x41047A60, + 0xDF60EFC3, + 0xA867DF55, + 0x316E8EEF, + 0x4669BE79, + 0xCB61B38C, + 0xBC66831A, + 0x256FD2A0, + 0x5268E236, + 0xCC0C7795, + 0xBB0B4703, + 0x220216B9, + 0x5505262F, + 0xC5BA3BBE, + 0xB2BD0B28, + 0x2BB45A92, + 0x5CB36A04, + 0xC2D7FFA7, + 0xB5D0CF31, + 0x2CD99E8B, + 0x5BDEAE1D, + 0x9B64C2B0, + 0xEC63F226, + 0x756AA39C, + 0x026D930A, + 0x9C0906A9, + 0xEB0E363F, + 0x72076785, + 0x05005713, + 0x95BF4A82, + 0xE2B87A14, + 0x7BB12BAE, + 0x0CB61B38, + 0x92D28E9B, + 0xE5D5BE0D, + 0x7CDCEFB7, + 0x0BDBDF21, + 0x86D3D2D4, + 0xF1D4E242, + 0x68DDB3F8, + 0x1FDA836E, + 0x81BE16CD, + 0xF6B9265B, + 0x6FB077E1, + 0x18B74777, + 0x88085AE6, + 0xFF0F6A70, + 0x66063BCA, + 0x11010B5C, + 0x8F659EFF, + 0xF862AE69, + 0x616BFFD3, + 0x166CCF45, + 0xA00AE278, + 0xD70DD2EE, + 0x4E048354, + 0x3903B3C2, + 0xA7672661, + 0xD06016F7, + 0x4969474D, + 0x3E6E77DB, + 0xAED16A4A, + 0xD9D65ADC, + 0x40DF0B66, + 0x37D83BF0, + 0xA9BCAE53, + 0xDEBB9EC5, + 0x47B2CF7F, + 0x30B5FFE9, + 0xBDBDF21C, + 0xCABAC28A, + 0x53B39330, + 0x24B4A3A6, + 0xBAD03605, + 0xCDD70693, + 0x54DE5729, + 0x23D967BF, + 0xB3667A2E, + 0xC4614AB8, + 0x5D681B02, + 0x2A6F2B94, + 0xB40BBE37, + 0xC30C8EA1, + 0x5A05DF1B, + 0x2D02EF8D, +] + + +class TuyaException(Exception): + """Base for Tuya exceptions.""" + + +class InvalidKey(TuyaException): + """The local key is invalid.""" + + +class InvalidMessage(TuyaException): + """The message received is invalid.""" + + +class MessageDecodeFailed(TuyaException): + """The message received cannot be decoded as JSON.""" + + +class ConnectionException(TuyaException): + """The socket connection failed.""" + + +class ConnectionTimeoutException(ConnectionException): + """The socket connection timed out.""" + + +class RequestResponseCommandMismatch(TuyaException): + """The command in the response didn't match the one from the request.""" + + +class ResponseTimeoutException(TuyaException): + """Did not recieve a response to the request within the timeout""" + + +class BackoffException(TuyaException): + """Backoff time not reached""" + + +class TuyaCipher: + """Tuya cryptographic helpers.""" + + def __init__(self, key, version): + """Initialize the cipher.""" + self.version = version + self.key = key + self.cipher = Cipher( + algorithms.AES(key.encode("ascii")), modes.ECB(), backend=openssl_backend + ) + + def get_prefix_size_and_validate(self, command, encrypted_data): + try: + version = tuple(map(int, encrypted_data[:3].decode("utf8").split("."))) + except ValueError: + version = (0, 0) + if version != self.version: + return 0 + if version < (3, 3): + hash = encrypted_data[3:19].decode("ascii") + expected_hash = self.hash(encrypted_data[19:]) + if hash != expected_hash: + return 0 + return 19 + else: + if command in (Message.SET_COMMAND, Message.GRATUITOUS_UPDATE): + _, sequence, __, ___ = struct.unpack_from(">IIIH", encrypted_data, 3) + return 15 + return 0 + + def decrypt(self, command, data): + prefix_size = self.get_prefix_size_and_validate(command, data) + data = data[prefix_size:] + decryptor = self.cipher.decryptor() + if self.version < (3, 3): + data = base64.b64decode(data) + decrypted_data = decryptor.update(data) + decrypted_data += decryptor.finalize() + unpadder = PKCS7(128).unpadder() + unpadded_data = unpadder.update(decrypted_data) + unpadded_data += unpadder.finalize() + + return unpadded_data + + def encrypt(self, command, data): + encrypted_data = b"" + if data: + padder = PKCS7(128).padder() + padded_data = padder.update(data) + padded_data += padder.finalize() + encryptor = self.cipher.encryptor() + encrypted_data = encryptor.update(padded_data) + encrypted_data += encryptor.finalize() + + prefix = ".".join(map(str, self.version)).encode("utf8") + if self.version < (3, 3): + payload = base64.b64encode(encrypted_data) + hash = self.hash(payload) + prefix += hash.encode("utf8") + else: + payload = encrypted_data + if command in (Message.SET_COMMAND, Message.GRATUITOUS_UPDATE): + prefix += b"\x00" * 12 + else: + prefix = b"" + + return prefix + payload + + def hash(self, data): + digest = Hash(MD5(), backend=openssl_backend) + to_hash = "data={}||lpv={}||{}".format( + data.decode("ascii"), ".".join(map(str, self.version)), self.key + ) + digest.update(to_hash.encode("utf8")) + intermediate = digest.finalize().hex() + return intermediate[8:24] + + +def crc(data): + """Calculate the Tuya-flavored CRC of some data.""" + c = 0xFFFFFFFF + for b in data: + c = (c >> 8) ^ CRC_32_TABLE[(c ^ b) & 255] + + return c ^ 0xFFFFFFFF + + +class Message: + PING_COMMAND = 0x09 + GET_COMMAND = 0x0A + SET_COMMAND = 0x07 + GRATUITOUS_UPDATE = 0x08 + + def __init__( + self, + command, + payload=None, + sequence=None, + encrypt=False, + device=None, + expect_response=True, + ttl=5, + ): + if payload is None: + payload = b"" + self.payload = payload + self.command = command + self.original_sequence = sequence + if sequence is None: + self.set_sequence() + else: + self.sequence = sequence + self.encrypt = encrypt + self.device = device + self.expiry = int(time.time()) + ttl + self.expect_response = expect_response + self.listener = None + if expect_response is True: + self.listener = asyncio.Semaphore(0) + if device is not None: + device._listeners[self.sequence] = self.listener + + def __repr__(self): + return "{}({}, {!r}, {!r}, {})".format( + self.__class__.__name__, + hex(self.command), + self.payload, + self.sequence, + "".format(self.device) if self.device else None, + ) + + def set_sequence(self): + self.sequence = int(time.perf_counter() * 1000) & 0xFFFFFFFF + + def hex(self): + return self.bytes().hex() + + def bytes(self): + payload_data = self.payload + if isinstance(payload_data, dict): + payload_data = json.dumps(payload_data, separators=(",", ":")) + if not isinstance(payload_data, bytes): + payload_data = payload_data.encode("utf8") + + if self.encrypt: + payload_data = self.device.cipher.encrypt(self.command, payload_data) + + payload_size = len(payload_data) + struct.calcsize(MESSAGE_SUFFIX_FORMAT) + + header = struct.pack( + MESSAGE_PREFIX_FORMAT, + MAGIC_PREFIX, + self.sequence, + self.command, + payload_size, + ) + if self.device and self.device.version >= (3, 3): + checksum = crc(header + payload_data) + else: + checksum = crc(payload_data) + footer = struct.pack(MESSAGE_SUFFIX_FORMAT, checksum, MAGIC_SUFFIX) + return header + payload_data + footer + + __bytes__ = bytes + + async def async_send(self): + await self.device._async_send(self) + + @classmethod + def from_bytes(cls, device, data, cipher=None): + try: + prefix, sequence, command, payload_size = struct.unpack_from( + MESSAGE_PREFIX_FORMAT, data + ) + except struct.error as e: + raise InvalidMessage("Invalid message header format.") from e + if prefix != MAGIC_PREFIX: + raise InvalidMessage("Magic prefix missing from message.") + + # check for an optional return code + header_size = struct.calcsize(MESSAGE_PREFIX_FORMAT) + try: + (return_code,) = struct.unpack_from(">I", data, header_size) + except struct.error as e: + raise InvalidMessage("Unable to unpack return code.") from e + if return_code >> 8: + payload_data = data[ + header_size : header_size + + payload_size + - struct.calcsize(MESSAGE_SUFFIX_FORMAT) + ] + return_code = None + else: + payload_data = data[ + header_size + + struct.calcsize(">I") : header_size + + payload_size + - struct.calcsize(MESSAGE_SUFFIX_FORMAT) + ] + + try: + expected_crc, suffix = struct.unpack_from( + MESSAGE_SUFFIX_FORMAT, + data, + header_size + payload_size - struct.calcsize(MESSAGE_SUFFIX_FORMAT), + ) + except struct.error as e: + raise InvalidMessage("Invalid message suffix format.") from e + if suffix != MAGIC_SUFFIX: + raise InvalidMessage("Magic suffix missing from message") + + actual_crc = crc( + data[: header_size + payload_size - struct.calcsize(MESSAGE_SUFFIX_FORMAT)] + ) + if expected_crc != actual_crc: + raise InvalidMessage("CRC check failed") + + payload = None + if payload_data: + try: + payload_data = cipher.decrypt(command, payload_data) + except ValueError as e: + pass + try: + payload_text = payload_data.decode("utf8") + except UnicodeDecodeError as e: + device._LOGGER.debug(payload_data.hex()) + device._LOGGER.error(e) + raise MessageDecodeFailed() from e + try: + payload = json.loads(payload_text) + except json.decoder.JSONDecodeError as e: + # data may be encrypted + device._LOGGER.debug(payload_data.hex()) + device._LOGGER.error(e) + raise MessageDecodeFailed() from e + + return cls(command, payload, sequence) + + +class TuyaDevice: + """Represents a generic Tuya device.""" + + def __init__( + self, + model_details, + device_id, + host, + timeout, + ping_interval, + update_entity_state, + local_key=None, + port=6668, + gateway_id=None, + version=(3, 3), + ): + """Initialize the device.""" + self._LOGGER = _LOGGER.getChild(device_id) + self.model_details = model_details + self.device_id = device_id + self.host = host + self.port = port + if not gateway_id: + gateway_id = self.device_id + self.gateway_id = gateway_id + self.version = version + self.timeout = timeout + self.last_pong = 0 + self.ping_interval = ping_interval + self.update_entity_state_cb = update_entity_state + + if len(local_key) != 16: + raise InvalidKey("Local key should be a 16-character string") + + self.cipher = TuyaCipher(local_key, self.version) + self.writer = None + self._response_task = None + self._recieve_task = None + self._ping_task = None + self._handlers: dict[int, Callable[[Message], Coroutine]] = { + Message.GRATUITOUS_UPDATE: self.async_gratuitous_update_state, + Message.PING_COMMAND: self._async_pong_received, + } + self._dps = {} + self._connected = False + self._enabled = True + self._queue = [] + self._listeners = {} + self._backoff = False + self._queue_interval = INITIAL_QUEUE_TIME + self._failures = 0 + + asyncio.create_task(self.process_queue()) + + def __repr__(self): + return "{}({!r}, {!r}, {!r}, {!r})".format( + self.__class__.__name__, + self.device_id, + self.host, + self.port, + self.cipher.key, + ) + + def __str__(self): + return "{} ({}:{})".format(self.device_id, self.host, self.port) + + async def process_queue(self): + if self._enabled is False: + return + + self.clean_queue() + + if len(self._queue) > 0: + self._LOGGER.debug( + "Processing queue. Current length: {}".format(len(self._queue)) + ) + try: + message = self._queue.pop(0) + await message.async_send() + self._failures = 0 + self._queue_interval = INITIAL_QUEUE_TIME + self._backoff = False + except Exception as e: + self._failures += 1 + self._LOGGER.debug( + "{} failures. Most recent: {}".format(self._failures, e) + ) + if self._failures > 3: + self._backoff = True + self._queue_interval = min( + INITIAL_BACKOFF * (BACKOFF_MULTIPLIER ** (self._failures - 4)), + 600, + ) + self._LOGGER.warn( + "{} failures, backing off for {} seconds".format( + self._failures, self._queue_interval + ) + ) + + await asyncio.sleep(self._queue_interval) + asyncio.create_task(self.process_queue()) + + def clean_queue(self): + cleaned_queue = [] + now = int(time.time()) + for item in self._queue: + if item.expiry > now: + cleaned_queue.append(item) + self._queue = cleaned_queue + + async def async_connect(self): + if self._connected is True or self._enabled is False: + return + + sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) + sock.settimeout(self.timeout) + self._LOGGER.debug("Connecting to {}".format(self)) + try: + sock.connect((self.host, self.port)) + except (socket.timeout, TimeoutError) as e: + self._dps[self.model_details.commands[RobovacCommand.ERROR]] = ( + "CONNECTION_FAILED" + ) + raise ConnectionTimeoutException("Connection timed out") + loop = asyncio.get_running_loop() + loop.create_connection + self.reader, self.writer = await asyncio.open_connection(sock=sock) + self._connected = True + + if self._ping_task is None: + self._ping_task = asyncio.create_task(self.async_ping(self.ping_interval)) + + asyncio.create_task(self._async_handle_message()) + + async def async_disable(self): + self._enabled = False + + await self.async_disconnect() + + async def async_disconnect(self): + if self._connected is False: + return + + self._LOGGER.debug("Disconnected from {}".format(self)) + self._connected = False + self.last_pong = 0 + + if self.writer is not None: + self.writer.close() + await self.writer.wait_closed() + + if self.reader is not None and not self.reader.at_eof(): + self.reader.feed_eof() + + async def async_get(self): + payload = {"gwId": self.gateway_id, "devId": self.device_id} + encrypt = False if self.version < (3, 3) else True + message = Message(Message.GET_COMMAND, payload, encrypt=encrypt, device=self) + self._queue.append(message) + response = await self.async_recieve(message) + await self.async_update_state(response) + + async def async_set(self, dps): + t = int(time.time()) + payload = {"devId": self.device_id, "uid": "", "t": t, "dps": dps} + message = Message( + Message.SET_COMMAND, + payload, + encrypt=True, + device=self, + expect_response=False, + ) + self._queue.append(message) + + async def async_ping(self, ping_interval): + if self._enabled is False: + return + + if self._backoff is True: + self._LOGGER.debug("Currently in backoff, not adding ping to queue") + else: + self.last_ping = time.time() + encrypt = False if self.version < (3, 3) else True + message = Message( + Message.PING_COMMAND, + sequence=0, + encrypt=encrypt, + device=self, + expect_response=False, + ) + self._queue.append(message) + + await asyncio.sleep(ping_interval) + self._ping_task = asyncio.create_task(self.async_ping(self.ping_interval)) + if self.last_pong < self.last_ping: + await self.async_disconnect() + + async def _async_pong_received(self, message): + self.last_pong = time.time() + + async def async_gratuitous_update_state(self, state_message): + await self.async_update_state(state_message) + await self.update_entity_state_cb() + + async def async_update_state(self, state_message, _=None): + if ( + state_message is not None + and state_message.payload + and state_message.payload["dps"] + ): + self._dps.update(state_message.payload["dps"]) + self._LOGGER.debug("Received updated state {}: {}".format(self, self._dps)) + + @property + def state(self): + return dict(self._dps) + + @state.setter + def state_setter(self, new_values): + asyncio.create_task(self.async_set(new_values)) + + async def _async_handle_message(self): + if self._enabled is False or self._connected is False: + return + + try: + self._response_task = asyncio.create_task( + self.reader.readuntil(MAGIC_SUFFIX_BYTES) + ) + await self._response_task + response_data = self._response_task.result() + message = Message.from_bytes(self, response_data, self.cipher) + except Exception as e: + if isinstance(e, InvalidMessage): + self._LOGGER.debug("Invalid message from {}: {}".format(self, e)) + elif isinstance(e, MessageDecodeFailed): + self._LOGGER.debug("Failed to decrypt message from {}".format(self)) + elif isinstance(e, asyncio.IncompleteReadError): + if self._connected: + self._LOGGER.debug("Incomplete read") + elif isinstance(e, ConnectionResetError): + self._LOGGER.debug( + "Connection reset: {}\n{}".format(e, traceback.format_exc()) + ) + await self.async_disconnect() + + else: + self._LOGGER.debug("Received message from {}: {}".format(self, message)) + if message.sequence in self._listeners: + sem = self._listeners[message.sequence] + if isinstance(sem, asyncio.Semaphore): + self._listeners[message.sequence] = message + sem.release() + else: + handler = self._handlers.get(message.command, None) + if handler is not None: + asyncio.create_task(handler(message)) + + self._response_task = None + asyncio.create_task(self._async_handle_message()) + + async def _async_send(self, message, retries=2): + self._LOGGER.debug("Sending to {}: {}".format(self, message)) + try: + await self.async_connect() + self.writer.write(message.bytes()) + await self.writer.drain() + except Exception as e: + if retries == 0: + if isinstance(e, socket.error): + await self.async_disconnect() + + raise ConnectionException( + "Connection to {} failed: {}".format(self, e) + ) + elif isinstance(e, asyncio.IncompleteReadError): + raise InvalidMessage( + "Incomplete read from: {} : {}".format(self, e) + ) + else: + raise TuyaException("Failed to send data to {}".format(self)) + + if isinstance(e, socket.error): + self._LOGGER.debug( + "Retrying send due to error. Connection to {} failed: {}".format( + self, e + ) + ) + elif isinstance(e, asyncio.IncompleteReadError): + self._LOGGER.debug( + "Retrying send due to error. Incomplete read from: {} : {}. Partial data recieved: {}".format( + self, e, e.partial + ) + ) + else: + self._LOGGER.debug( + "Retrying send due to error. Failed to send data to {}".format(self) + ) + await asyncio.sleep(0.25) + await self._async_send(message, retries=retries - 1) + + async def async_recieve(self, message): + if message.expect_response is True: + try: + self._recieve_task = asyncio.create_task( + asyncio.wait_for(message.listener.acquire(), timeout=self.timeout) + ) + await self._recieve_task + response = self._listeners.pop(message.sequence) + + if isinstance(response, Exception): + raise response + + return response + except Exception as e: + del self._listeners[message.sequence] + await self.async_disconnect() + + if isinstance(e, TimeoutError): + raise ResponseTimeoutException( + "Timed out waiting for response to sequence number {}".format( + message.sequence + ) + ) + + raise e diff --git a/custom_components/robovac/tuyalocaldiscovery.py b/custom_components/robovac/tuyalocaldiscovery.py new file mode 100644 index 0000000..29994ae --- /dev/null +++ b/custom_components/robovac/tuyalocaldiscovery.py @@ -0,0 +1,57 @@ +import asyncio +import json +import logging +from hashlib import md5 + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +_LOGGER = logging.getLogger(__name__) + +UDP_KEY = md5(b"yGAdlopoPVldABfn").digest() + + +class DiscoveryPortsNotAvailableException(Exception): + """This model is not supported""" + + +class TuyaLocalDiscovery(asyncio.DatagramProtocol): + def __init__(self, callback): + self.devices = {} + self._listeners = [] + self.discovered_callback = callback + + async def start(self): + loop = asyncio.get_running_loop() + listener = loop.create_datagram_endpoint( + lambda: self, local_addr=("0.0.0.0", 6666), reuse_port=True + ) + encrypted_listener = loop.create_datagram_endpoint( + lambda: self, local_addr=("0.0.0.0", 6667), reuse_port=True + ) + + try: + self._listeners = await asyncio.gather(listener, encrypted_listener) + _LOGGER.debug("Listening to broadcasts on UDP port 6666 and 6667") + except Exception as e: + raise DiscoveryPortsNotAvailableException( + "Ports 6666 and 6667 are needed for autodiscovery but are unavailable. This may be due to having the localtuya integration installed and it not allowing other integrations to use the same ports. A pull request has been raised to address this: https://github.com/rospogrigio/localtuya/pull/1481" + ) + + def close(self, *args, **kwargs): + for transport, _ in self._listeners: + transport.close() + + def datagram_received(self, data, addr): + data = data[20:-8] + try: + cipher = Cipher(algorithms.AES(UDP_KEY), modes.ECB(), default_backend()) + decryptor = cipher.decryptor() + padded_data = decryptor.update(data) + decryptor.finalize() + data = padded_data[: -ord(padded_data[len(padded_data) - 1 :])] + + except Exception: + data = data.decode() + + decoded = json.loads(data) + asyncio.ensure_future(self.discovered_callback(decoded)) diff --git a/custom_components/robovac/tuyawebapi.py b/custom_components/robovac/tuyawebapi.py new file mode 100644 index 0000000..fefb4d8 --- /dev/null +++ b/custom_components/robovac/tuyawebapi.py @@ -0,0 +1,242 @@ +"""Original Work from here: Andre Borie https://gitlab.com/Rjevski/eufy-device-id-and-local-key-grabber""" + +from hashlib import md5, sha256 +import hmac +import json +import math +import random +import string +import time +import uuid + +from cryptography.hazmat.backends.openssl import backend as openssl_backend +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +import requests + +TUYA_INITIAL_BASE_URL = "https://a1.tuyaeu.com" + +EUFY_HMAC_KEY = ( + "A_cepev5pfnhua4dkqkdpmnrdxx378mpjr_s8x78u7xwymasd9kqa7a73pjhxqsedaj".encode() +) + + +def unpadded_rsa(key_exponent: int, key_n: int, plaintext: bytes) -> bytes: + keylength = math.ceil(key_n.bit_length() / 8) + input_nr = int.from_bytes(plaintext, byteorder="big") + crypted_nr = pow(input_nr, key_exponent, key_n) + return crypted_nr.to_bytes(keylength, byteorder="big") + + +def shuffled_md5(value: str) -> str: + _hash = md5(value.encode("utf-8")).hexdigest() + return _hash[8:16] + _hash[0:8] + _hash[24:32] + _hash[16:24] + + +TUYA_PASSWORD_INNER_CIPHER = Cipher( + algorithms.AES( + bytearray( + [36, 78, 109, 138, 86, 172, 135, 145, 36, 67, 45, 139, 108, 188, 162, 196] + ) + ), + modes.CBC( + bytearray( + [119, 36, 86, 242, 167, 102, 76, 243, 57, 44, 53, 151, 233, 62, 87, 71] + ) + ), + backend=openssl_backend, +) + +DEFAULT_TUYA_HEADERS = {"User-Agent": "TY-UA=APP/Android/2.4.0/SDK/null"} + +SIGNATURE_RELEVANT_PARAMETERS = { + "a", + "v", + "lat", + "lon", + "lang", + "deviceId", + "appVersion", + "ttid", + "isH5", + "h5Token", + "os", + "clientId", + "postData", + "time", + "requestId", + "et", + "n4h5", + "sid", + "sp", +} + +DEFAULT_TUYA_QUERY_PARAMS = { + "appVersion": "2.4.0", + "deviceId": "", + "platform": "sdk_gphone64_arm64", + "clientId": "yx5v9uc3ef9wg3v9atje", + "lang": "en", + "osSystem": "12", + "os": "Android", + "timeZoneId": "", + "ttid": "android", + "et": "0.0.1", + "sdkVersion": "3.0.8cAnker", +} + + +class TuyaAPISession: + username = None + country_code = None + session_id = None + + def __init__(self, username, region, timezone, phone_code): + self.session = requests.session() + self.session.headers = DEFAULT_TUYA_HEADERS.copy() + self.default_query_params = DEFAULT_TUYA_QUERY_PARAMS.copy() + self.default_query_params["deviceId"] = self.generate_new_device_id() + self.username = username + self.country_code = phone_code + self.base_url = { + "AZ": "https://a1.tuyaus.com", + "AY": "https://a1.tuyacn.com", + "IN": "https://a1.tuyain.com", + "EU": "https://a1.tuyaeu.com", + }.get(region, "https://a1.tuyaeu.com") + + DEFAULT_TUYA_QUERY_PARAMS["timeZoneId"] = timezone + + @staticmethod + def generate_new_device_id(): + expected_length = 44 + base64_characters = string.ascii_letters + string.digits + device_id_dependent_part = "8534c8ec0ed0" + return device_id_dependent_part + "".join( + random.choice(base64_characters) + for _ in range(expected_length - len(device_id_dependent_part)) + ) + + @staticmethod + def get_signature(query_params: dict, encoded_post_data: str): + query_params = query_params.copy() + if encoded_post_data: + query_params["postData"] = encoded_post_data + sorted_pairs = sorted(query_params.items()) + filtered_pairs = filter( + lambda p: p[0] and p[0] in SIGNATURE_RELEVANT_PARAMETERS, sorted_pairs + ) + mapped_pairs = map( + # postData is pre-emptively hashed (for performance reasons?), everything else is included as-is + lambda p: p[0] + "=" + (shuffled_md5(p[1]) if p[0] == "postData" else p[1]), + filtered_pairs, + ) + message = "||".join(mapped_pairs) + return hmac.HMAC( + key=EUFY_HMAC_KEY, msg=message.encode("utf-8"), digestmod=sha256 + ).hexdigest() + + def _request( + self, + action: str, + version="1.0", + data: dict = None, + query_params: dict = None, + _requires_session=True, + ): + if not self.session_id and _requires_session: + self.acquire_session() + + current_time = time.time() + request_id = uuid.uuid4() + extra_query_params = { + "time": str(int(current_time)), + "requestId": str(request_id), + "a": action, + "v": version, + **(query_params or {}), + } + query_params = {**self.default_query_params, **extra_query_params} + encoded_post_data = json.dumps(data, separators=(",", ":")) if data else "" + resp = self.session.post( + self.base_url + "/api.json", + params={ + **query_params, + "sign": self.get_signature(query_params, encoded_post_data), + }, + data={"postData": encoded_post_data} if encoded_post_data else None, + ) + resp.raise_for_status() + data = resp.json() + if "result" not in data: + raise Exception( + f"No 'result' key in the response - the entire response is {data}." + ) + return data["result"] + + def request_token(self, username, country_code): + return self._request( + action="tuya.m.user.uid.token.create", + data={"uid": username, "countryCode": country_code}, + _requires_session=False, + ) + + def determine_password(self, username: str): + new_uid = username + padded_size = 16 * math.ceil(len(new_uid) / 16) + password_uid = new_uid.zfill(padded_size) + encryptor = TUYA_PASSWORD_INNER_CIPHER.encryptor() + encrypted_uid = encryptor.update(password_uid.encode("utf8")) + encrypted_uid += encryptor.finalize() + return md5(encrypted_uid.hex().upper().encode("utf-8")).hexdigest() + + def request_session(self, username, password, country_code): + token_response = self.request_token(username, country_code) + encrypted_password = unpadded_rsa( + key_exponent=int(token_response["exponent"]), + key_n=int(token_response["publicKey"]), + plaintext=password.encode("utf-8"), + ) + data = { + "uid": username, + "createGroup": True, + "ifencrypt": 1, + "passwd": encrypted_password.hex(), + "countryCode": country_code, + "options": '{"group": 1}', + "token": token_response["token"], + } + + try: + return self._request( + action="tuya.m.user.uid.password.login.reg", + data=data, + _requires_session=False, + ) + except Exception as e: + error_password = md5("12345678".encode("utf8")).hexdigest() + + if password != error_password: + return self.request_session(username, error_password, country_code) + else: + raise e + + def acquire_session(self): + password = self.determine_password(self.username) + session_response = self.request_session( + self.username, password, self.country_code + ) + self.session_id = self.default_query_params["sid"] = session_response["sid"] + self.base_url = session_response["domain"]["mobileApiUrl"] + self.country_code = ( + session_response["phoneCode"] + if session_response["phoneCode"] + else self.getCountryCode(session_response["domain"]["regionCode"]) + ) + + def list_homes(self): + return self._request(action="tuya.m.location.list", version="2.1") + + def get_device(self, devId): + return self._request( + action="tuya.m.device.get", version="1.0", data={"devId": devId} + ) diff --git a/custom_components/robovac/vacuum.py b/custom_components/robovac/vacuum.py new file mode 100644 index 0000000..44f9e77 --- /dev/null +++ b/custom_components/robovac/vacuum.py @@ -0,0 +1,591 @@ +# Copyright 2022 Brendan McCluskey +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Eufy Robovac sensor platform.""" +from __future__ import annotations +from collections.abc import Mapping + +from datetime import timedelta +import logging +import asyncio +import base64 +import json +import time +import ast + +from typing import Any +from enum import IntEnum, StrEnum +from homeassistant.loader import bind_hass +from homeassistant.components.vacuum import ( + StateVacuumEntity, + STATE_CLEANING, + STATE_DOCKED, + STATE_ERROR, + STATE_IDLE, + STATE_RETURNING, + STATE_PAUSED +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import ( + CONNECTION_NETWORK_MAC, +) +from homeassistant.helpers.entity import DeviceInfo + +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_MODEL, + CONF_NAME, + CONF_ID, + CONF_IP_ADDRESS, + CONF_DESCRIPTION, + CONF_MAC, + STATE_UNAVAILABLE, +) + +from .vacuums.base import RoboVacEntityFeature, RobovacCommand + +from .tuyalocalapi import TuyaException +from .const import CONF_VACS, DOMAIN, REFRESH_RATE, PING_RATE, TIMEOUT + +from .errors import getErrorMessage +from .robovac import ( + ModelNotSupportedException, + RoboVac, +) + +from homeassistant.const import ATTR_BATTERY_LEVEL + +ATTR_BATTERY_ICON = "battery_icon" +ATTR_ERROR = "error" +ATTR_FAN_SPEED = "fan_speed" +ATTR_FAN_SPEED_LIST = "fan_speed_list" +ATTR_STATUS = "status" +ATTR_ERROR_CODE = "error_code" +ATTR_MODEL_CODE = "model_code" +ATTR_CLEANING_AREA = "cleaning_area" +ATTR_CLEANING_TIME = "cleaning_time" +ATTR_AUTO_RETURN = "auto_return" +ATTR_DO_NOT_DISTURB = "do_not_disturb" +ATTR_BOOST_IQ = "boost_iq" +ATTR_CONSUMABLES = "consumables" +ATTR_MODE = "mode" + +MODE_MAPPING = { #152 + "AggO": "Auto cleaning", + "BBoCCAE=": "Start auto", + "AggN": "Pause", + "AggG": "Stop / Go to charge", + "AA==": "Standby" +} + +EMPTY_MAPPING = { #173 + "BBICGAE=": "Empty dust", + "BBICIAE=": "Wash mop", + "BBICEAE=": "Dry mop" +} + +TUYA_STATUS_MAPPING = { #153 + "BgoAEAUyAA==": "AUTO", + "BgoAEAVSAA==": "POSITION", + "CAoAEAUyAggB": "PAUSE", + "CAoCCAEQBTIA": "ROOM", + "CAoCCAEQBVIA": "ROOM_POSITION", + "CgoCCAEQBTICCAE=": "ROOM_PAUSE", + "CAoCCAIQBTIA": "SPOT", + "CAoCCAIQBVIA": "SPOT_POSITION", + "CgoCCAIQBTICCAE=": "SPOT_PAUSE", + "BAoAEAY=": "START_MANUAL", + "BBAHQgA=": "GOING_TO_CHARGE", + "BBADGgA=": "CHARGING", + "BhADGgIIAQ==": "COMPLETED", + "AA==": "STANDBY", + "AhAB": "SLEEPING", +} + +STATUS_MAPPING = { + "AUTO" : "Auto cleaning", + "POSITION": "Positioning", + "PAUSE": "Cleaning paused", + "ROOM": "Cleaning room", + "ROOM_POSITION": "Positioning room", + "ROOM_PAUSE": "Cleaning room paused", + "SPOT": "Spot cleaning", + "SPOT_POSITION": "Positioning spot", + "SPOT_PAUSE": "Cleaning spot paused", + "START_MANUAL": "Manual mode", + "GOING_TO_CHARGE": "Recharge", + "CHARGING": "Charging", + "COMPLETED": "Completed", + "STANDBY": "Standby", + "SLEEPING": "Sleeping", +} + +ERROR_MAPPING = { #177 + "DAiI6suO9dXszgFSAA==": "no_error", + "FAjwudWorOPszgEaAqURUgQSAqUR": "Sidebrush stuck", + "FAj+nMu7zuPszgEaAtg2UgQSAtg2": "Robot stuck", + "DAjtzbfps+XszgFSAA==": "no_error", + "DAiom9rd6eTszgFSAA==": "no_error", + "DAia8JTV5OPszgFSAA==": "no_error", + "DAj489bWsePszgFSAA==": "no_error", + +# DAjH1er4vtbszgFSAA== +# DAi73bTN+uLszgFSAA== +# DAj489bWsePszgFSAA== +# DAia8JTV5OPszgFSAA== +# DAiom9rd6eTszgFSAA== +# DAjtzbfps+XszgFSAA== + +} + +_LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(seconds=REFRESH_RATE) +UPDATE_RETRIES = 3 + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Initialize my test integration 2 config entry.""" + vacuums = config_entry.data[CONF_VACS] + for item in vacuums: + item = vacuums[item] + entity = RoboVacEntity(item) + hass.data[DOMAIN][CONF_VACS][item[CONF_ID]] = entity + async_add_entities([entity]) + + +class RoboVacEntity(StateVacuumEntity): + """Eufy Robovac version of a Vacuum entity""" + + _attr_should_poll = True + + _attr_access_token: str | None = None + _attr_ip_address: str | None = None + _attr_model_code: str | None = None + _attr_cleaning_area: str | None = None + _attr_cleaning_time: str | None = None + _attr_auto_return: str | None = None + _attr_do_not_disturb: str | None = None + _attr_boost_iq: str | None = None + _attr_consumables: str | None = None + _attr_mode: str | None = None + _attr_robovac_supported: str | None = None + + @property + def robovac_supported(self) -> str | None: + """Return the supported features of the vacuum cleaner.""" + return self._attr_robovac_supported + + @property + def mode(self) -> str | None: + """Return the cleaning mode of the vacuum cleaner.""" + return self._attr_mode + + @property + def consumables(self) -> str | None: + """Return the consumables status of the vacuum cleaner.""" + return self._attr_consumables + + @property + def cleaning_area(self) -> str | None: + """Return the cleaning area of the vacuum cleaner.""" + return self._attr_cleaning_area + + @property + def cleaning_time(self) -> str | None: + """Return the cleaning time of the vacuum cleaner.""" + return self._attr_cleaning_time + + @property + def auto_return(self) -> str | None: + """Return the auto_return mode of the vacuum cleaner.""" + return self._attr_auto_return + + @property + def do_not_disturb(self) -> str | None: + """Return the do not disturb mode of the vacuum cleaner.""" + return self._attr_do_not_disturb + + @property + def boost_iq(self) -> str | None: + """Return the boost iq mode of the vacuum cleaner.""" + return self._attr_boost_iq + + @property + def model_code(self) -> str | None: + """Return the model code of the vacuum cleaner.""" + return self._attr_model_code + + @property + def access_token(self) -> str | None: + """Return the fan speed of the vacuum cleaner.""" + return self._attr_access_token + + @property + def ip_address(self) -> str | None: + """Return the ip address of the vacuum cleaner.""" + return self._attr_ip_address + + @property + def state(self) -> str | None: + if self.tuya_state is None: + return STATE_UNAVAILABLE + elif ( + type(self.error_code) is not None + and self.error_code + and self.error_code + not in [ + 0, + "no_error", + ] + ): + _LOGGER.debug( + "State changed to error. Error message: {}".format( + getErrorMessage(self.error_code) + ) + ) + return STATE_ERROR + elif self.tuya_state == "Charging" or self.tuya_state == "Completed": + return STATE_DOCKED + elif self.tuya_state == "Recharge": + return STATE_RETURNING + elif self.tuya_state == "Sleeping" or self.tuya_state == "Standby": + return STATE_IDLE + else: + return STATE_CLEANING + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return the device-specific state attributes of this vacuum.""" + data: dict[str, Any] = {} + + if type(self.error_code) is not None and self.error_code not in [0, "no_error"]: + data[ATTR_ERROR] = getErrorMessage(self.error_code) + if ( + self.robovac_supported & RoboVacEntityFeature.CLEANING_AREA + and self.cleaning_area + ): + data[ATTR_CLEANING_AREA] = self.cleaning_area + if ( + self.robovac_supported & RoboVacEntityFeature.CLEANING_TIME + and self.cleaning_time + ): + data[ATTR_CLEANING_TIME] = self.cleaning_time + if ( + self.robovac_supported & RoboVacEntityFeature.AUTO_RETURN + and self.auto_return + ): + data[ATTR_AUTO_RETURN] = self.auto_return + if ( + self.robovac_supported & RoboVacEntityFeature.DO_NOT_DISTURB + and self.do_not_disturb + ): + data[ATTR_DO_NOT_DISTURB] = self.do_not_disturb + if self.robovac_supported & RoboVacEntityFeature.BOOST_IQ and self.boost_iq: + data[ATTR_BOOST_IQ] = self.boost_iq + if ( + self.robovac_supported & RoboVacEntityFeature.CONSUMABLES + and self.consumables + ): + data[ATTR_CONSUMABLES] = self.consumables + if self.mode: + data[ATTR_MODE] = self.mode + return data + + def __init__(self, item) -> None: + """Initialize Eufy Robovac""" + super().__init__() + self._attr_battery_level = 0 + self._attr_name = item[CONF_NAME] + self._attr_unique_id = item[CONF_ID] + self._attr_model_code = item[CONF_MODEL] + self._attr_ip_address = item[CONF_IP_ADDRESS] + self._attr_access_token = item[CONF_ACCESS_TOKEN] + + self.update_failures = 0 + + try: + self.vacuum = RoboVac( + device_id=self.unique_id, + host=self.ip_address, + local_key=self.access_token, + timeout=TIMEOUT, + ping_interval=PING_RATE, + model_code=self.model_code[0:5], + update_entity_state=self.pushed_update_handler, + ) + except ModelNotSupportedException: + self.error_code = "UNSUPPORTED_MODEL" + + self._attr_supported_features = self.vacuum.getHomeAssistantFeatures() + self._attr_robovac_supported = self.vacuum.getRoboVacFeatures() + + fan_speeds = self.vacuum.getFanSpeeds() + self.fan_speed_map = {} + + for speed in fan_speeds: + self.fan_speed_map[friendly_text(speed)] = speed + + self._attr_fan_speed_list = list(self.fan_speed_map.keys()) + _LOGGER.debug(self._attr_fan_speed_list) + self._tuya_command_codes = self.vacuum.getCommandCodes() + + self._attr_mode = None + self._attr_consumables = None + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, item[CONF_ID])}, + name=item[CONF_NAME], + manufacturer="Eufy", + model=item[CONF_DESCRIPTION], + connections=[ + (CONNECTION_NETWORK_MAC, item[CONF_MAC]), + ], + ) + + self.error_code = None + self.tuya_state = None + self.tuyastatus = None + + async def async_added_to_hass(self): + await self.async_forced_update() + + async def async_update(self): + """Synchronise state from the vacuum.""" + try: + await self.async_update_vacuum() + self.update_failures = 0 + except TuyaException as e: + self.update_failures += 1 + _LOGGER.warn( + "Update errored. Current update failure count: {}. Reason: {}".format( + self.update_failures, e + ) + ) + if self.update_failures >= UPDATE_RETRIES: + self.error_code = "CONNECTION_FAILED" + + async def async_update_vacuum(self): + if self.error_code == "UNSUPPORTED_MODEL": + return + + if self.ip_address == "": + self.error_code = "IP_ADDRESS" + return + + await self.vacuum.async_get() + self.update_entity_values() + + async def async_forced_update(self): + await self.async_update_vacuum() + self.async_write_ha_state() + + async def pushed_update_handler(self): + self.update_entity_values() + self.async_write_ha_state() + + def update_entity_values(self): + self.tuyastatus = self.vacuum._dps + _LOGGER.debug("tuyastatus %s", self.tuyastatus) + + self._attr_battery_level = self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.BATTERY] + ) + _LOGGER.debug("_attr_battery_level %s", self._attr_battery_level) + + self.tuya_state = STATUS_MAPPING.get( + TUYA_STATUS_MAPPING.get( + self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.STATUS] + ), None + ), None + ) + _LOGGER.debug("tuya_state %s", self.tuya_state) + + self.error_code = ERROR_MAPPING.get( + self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.ERROR] + ), None + ) + _LOGGER.debug("error_code %s", self.error_code) + + self._attr_mode = self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.MODE] + ) + _LOGGER.debug("_attr_mode %s", self._attr_mode) + + self._attr_fan_speed = friendly_text( + self.tuyastatus.get(self._tuya_command_codes[RobovacCommand.FAN_SPEED], "") + ) + _LOGGER.debug("_attr_fan_speed %s", self._attr_fan_speed) + + if self.robovac_supported & RoboVacEntityFeature.CLEANING_AREA: + self._attr_cleaning_area = self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.CLEANING_AREA] + ) + _LOGGER.debug("_attr_cleaning_area %s", self._attr_cleaning_area) + + if self.robovac_supported & RoboVacEntityFeature.CLEANING_TIME: + self._attr_cleaning_time = self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.CLEANING_TIME] + ) + _LOGGER.debug("_attr_cleaning_time %s", self._attr_cleaning_time) + + if self.robovac_supported & RoboVacEntityFeature.AUTO_RETURN: + self._attr_auto_return = self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.AUTO_RETURN] + ) + _LOGGER.debug("_attr_auto_return %s", self._attr_auto_return) + + if self.robovac_supported & RoboVacEntityFeature.DO_NOT_DISTURB: + self._attr_do_not_disturb = self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.DO_NOT_DISTURB] + ) + _LOGGER.debug("_attr_do_not_disturb %s", self._attr_do_not_disturb) + + if self.robovac_supported & RoboVacEntityFeature.BOOST_IQ: + self._attr_boost_iq = self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.BOOST_IQ] + ) + _LOGGER.debug("_attr_boost_iq %s", self._attr_boost_iq) + + if self.robovac_supported & RoboVacEntityFeature.CONSUMABLES: + consumables = ast.literal_eval( + base64.b64decode( + self.tuyastatus.get( + self._tuya_command_codes[RobovacCommand.CONSUMABLES] + ) + ).decode("ascii") + ) + _LOGGER.debug("Consumables decoded value is: {}".format(consumables)) + if "consumable" in consumables and "duration" in consumables["consumable"]: + _LOGGER.debug( + "Consumables encoded value is: {}".format( + consumables["consumable"]["duration"] + ) + ) + self._attr_consumables = consumables["consumable"]["duration"] + _LOGGER.debug("_attr_consumables %s", self._attr_consumables) + + async def async_locate(self, **kwargs): + """Locate the vacuum cleaner.""" + _LOGGER.info("Locate Pressed") + code = self._tuya_command_codes[RobovacCommand.LOCATE] + if self.tuyastatus.get(code): + await self.vacuum.async_set({code: False}) + else: + await self.vacuum.async_set({code: True}) + asyncio.create_task(self.async_forced_update()) + + async def async_return_to_base(self, **kwargs): + """Set the vacuum cleaner to return to the dock.""" + _LOGGER.info("Return home Pressed") + await self.vacuum.async_set( + {self._tuya_command_codes[RobovacCommand.MODE]: "AggG"} + ) + asyncio.create_task(self.async_forced_update()) + + async def async_start(self, **kwargs): + await self.vacuum.async_set( + {self._tuya_command_codes[RobovacCommand.MODE]: "BBoCCAE="} + ) + asyncio.create_task(self.async_forced_update()) + + async def async_pause(self, **kwargs): + await self.vacuum.async_set( + {self._tuya_command_codes[RobovacCommand.MODE]: "AggN"} + ) + asyncio.create_task(self.async_forced_update()) + + async def async_stop(self, **kwargs): + await self.async_return_to_base() + asyncio.create_task(self.async_forced_update()) + + async def async_clean_spot(self, **kwargs): + """Perform a spot clean-up.""" + _LOGGER.info("Spot Clean Pressed") + await self.vacuum.async_set( + {self._tuya_command_codes[RobovacCommand.MODE]: "Spot"} + ) + asyncio.create_task(self.async_forced_update()) + + async def async_set_fan_speed(self, fan_speed, **kwargs): + """Set fan speed.""" + _LOGGER.info("Fan Speed Selected") + await self.vacuum.async_set( + { + self._tuya_command_codes[RobovacCommand.FAN_SPEED]: self.fan_speed_map[ + fan_speed + ] + } + ) + asyncio.create_task(self.async_forced_update()) + + async def async_send_command( + self, command: str, params: dict | list | None = None, **kwargs + ) -> None: + """Send a command to a vacuum cleaner.""" + _LOGGER.info("Send Command %s Pressed", command) + if command == "edgeClean": + await self.vacuum.async_set({"5": "Edge"}) + elif command == "smallRoomClean": + await self.vacuum.async_set({"5": "SmallRoom"}) + elif command == "autoClean": + await self.vacuum.async_set({"152": "BBoCCAE="}) + elif command == "autoReturn": + if self.auto_return: + await self.vacuum.async_set({"135": False}) + else: + await self.vacuum.async_set({"135": True}) + elif command == "doNotDisturb": + if self.do_not_disturb: + await self.vacuum.async_set({"139": "MEQ4MDAwMDAw"}) + await self.vacuum.async_set({"107": False}) + else: + await self.vacuum.async_set({"139": "MTAwMDAwMDAw"}) + await self.vacuum.async_set({"107": True}) + elif command == "boostIQ": + if self.boost_iq: + await self.vacuum.async_set({"118": False}) + else: + await self.vacuum.async_set({"118": True}) + elif command == "roomClean": + roomIds = params.get("roomIds", [1]) + count = params.get("count", 1) + clean_request = {"roomIds": roomIds, "cleanTimes": count} + method_call = { + "method": "selectRoomsClean", + "data": clean_request, + "timestamp": round(time.time() * 1000), + } + json_str = json.dumps(method_call, separators=(",", ":")) + base64_str = base64.b64encode(json_str.encode("utf8")).decode("utf8") + _LOGGER.info("roomClean call %s", json_str) + await self.vacuum.async_set({"124": base64_str}) + else: + await self.vacuum.async_set({command: params.get("value", "")}) + asyncio.create_task(self.async_forced_update()) + + async def async_will_remove_from_hass(self): + await self.vacuum.async_disable() + + +def friendly_text(input): + return " ".join( + word[0].upper() + word[1:] for word in input.replace("_", " ").split() + ) diff --git a/custom_components/robovac/vacuums/T1250.py b/custom_components/robovac/vacuums/T1250.py new file mode 100644 index 0000000..794a5b7 --- /dev/null +++ b/custom_components/robovac/vacuums/T1250.py @@ -0,0 +1,44 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T1250: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2103.py b/custom_components/robovac/vacuums/T2103.py new file mode 100644 index 0000000..6940438 --- /dev/null +++ b/custom_components/robovac/vacuums/T2103.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2103: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2117.py b/custom_components/robovac/vacuums/T2117.py new file mode 100644 index 0000000..aae4db6 --- /dev/null +++ b/custom_components/robovac/vacuums/T2117.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2117: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2118.py b/custom_components/robovac/vacuums/T2118.py new file mode 100644 index 0000000..cc927ce --- /dev/null +++ b/custom_components/robovac/vacuums/T2118.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2118: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2119.py b/custom_components/robovac/vacuums/T2119.py new file mode 100644 index 0000000..420a5f5 --- /dev/null +++ b/custom_components/robovac/vacuums/T2119.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2119: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2120.py b/custom_components/robovac/vacuums/T2120.py new file mode 100644 index 0000000..ab28eb8 --- /dev/null +++ b/custom_components/robovac/vacuums/T2120.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2120: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2123.py b/custom_components/robovac/vacuums/T2123.py new file mode 100644 index 0000000..d1817d7 --- /dev/null +++ b/custom_components/robovac/vacuums/T2123.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2123: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2128.py b/custom_components/robovac/vacuums/T2128.py new file mode 100644 index 0000000..8fbdef1 --- /dev/null +++ b/custom_components/robovac/vacuums/T2128.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2128: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2130.py b/custom_components/robovac/vacuums/T2130.py new file mode 100644 index 0000000..1446665 --- /dev/null +++ b/custom_components/robovac/vacuums/T2130.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2130: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2132.py b/custom_components/robovac/vacuums/T2132.py new file mode 100644 index 0000000..63101d6 --- /dev/null +++ b/custom_components/robovac/vacuums/T2132.py @@ -0,0 +1,38 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2132: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.EDGE | RoboVacEntityFeature.SMALL_ROOM + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["No_suction","Standard","Boost_IQ","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + } diff --git a/custom_components/robovac/vacuums/T2150.py b/custom_components/robovac/vacuums/T2150.py new file mode 100644 index 0000000..480a758 --- /dev/null +++ b/custom_components/robovac/vacuums/T2150.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2150: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2181.py b/custom_components/robovac/vacuums/T2181.py new file mode 100644 index 0000000..04ce1e8 --- /dev/null +++ b/custom_components/robovac/vacuums/T2181.py @@ -0,0 +1,46 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2181: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Quiet","Standard","Turbo","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.BOOST_IQ: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2182.py b/custom_components/robovac/vacuums/T2182.py new file mode 100644 index 0000000..414ee46 --- /dev/null +++ b/custom_components/robovac/vacuums/T2182.py @@ -0,0 +1,46 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2182: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Quiet","Standard","Turbo","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.BOOST_IQ: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2190.py b/custom_components/robovac/vacuums/T2190.py new file mode 100644 index 0000000..f906d2b --- /dev/null +++ b/custom_components/robovac/vacuums/T2190.py @@ -0,0 +1,46 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2190: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Quiet","Standard","Turbo","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.BOOST_IQ: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2192.py b/custom_components/robovac/vacuums/T2192.py new file mode 100644 index 0000000..e56aee1 --- /dev/null +++ b/custom_components/robovac/vacuums/T2192.py @@ -0,0 +1,45 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2192: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Quiet","Standard","Turbo","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.BOOST_IQ: 0, + } diff --git a/custom_components/robovac/vacuums/T2193.py b/custom_components/robovac/vacuums/T2193.py new file mode 100644 index 0000000..3996f3b --- /dev/null +++ b/custom_components/robovac/vacuums/T2193.py @@ -0,0 +1,46 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2193: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Quiet","Standard","Turbo","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.BOOST_IQ: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2194.py b/custom_components/robovac/vacuums/T2194.py new file mode 100644 index 0000000..362bfa4 --- /dev/null +++ b/custom_components/robovac/vacuums/T2194.py @@ -0,0 +1,46 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2194: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Quiet","Standard","Turbo","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.BOOST_IQ: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2250.py b/custom_components/robovac/vacuums/T2250.py new file mode 100644 index 0000000..e551d91 --- /dev/null +++ b/custom_components/robovac/vacuums/T2250.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2250: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2251.py b/custom_components/robovac/vacuums/T2251.py new file mode 100644 index 0000000..058cfd4 --- /dev/null +++ b/custom_components/robovac/vacuums/T2251.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2251: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2252.py b/custom_components/robovac/vacuums/T2252.py new file mode 100644 index 0000000..86fea2a --- /dev/null +++ b/custom_components/robovac/vacuums/T2252.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2252: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2253.py b/custom_components/robovac/vacuums/T2253.py new file mode 100644 index 0000000..7bc100d --- /dev/null +++ b/custom_components/robovac/vacuums/T2253.py @@ -0,0 +1,45 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2253: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2254.py b/custom_components/robovac/vacuums/T2254.py new file mode 100644 index 0000000..f0d5f51 --- /dev/null +++ b/custom_components/robovac/vacuums/T2254.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2254: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2255.py b/custom_components/robovac/vacuums/T2255.py new file mode 100644 index 0000000..b7444f8 --- /dev/null +++ b/custom_components/robovac/vacuums/T2255.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2255: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2256.py b/custom_components/robovac/vacuums/T2256.py new file mode 100644 index 0000000..96a44d6 --- /dev/null +++ b/custom_components/robovac/vacuums/T2256.py @@ -0,0 +1,44 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2256: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2257.py b/custom_components/robovac/vacuums/T2257.py new file mode 100644 index 0000000..f641a21 --- /dev/null +++ b/custom_components/robovac/vacuums/T2257.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2257: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2258.py b/custom_components/robovac/vacuums/T2258.py new file mode 100644 index 0000000..bb755a5 --- /dev/null +++ b/custom_components/robovac/vacuums/T2258.py @@ -0,0 +1,44 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2258: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2259.py b/custom_components/robovac/vacuums/T2259.py new file mode 100644 index 0000000..58482ab --- /dev/null +++ b/custom_components/robovac/vacuums/T2259.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2259: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2261.py b/custom_components/robovac/vacuums/T2261.py new file mode 100644 index 0000000..1a23743 --- /dev/null +++ b/custom_components/robovac/vacuums/T2261.py @@ -0,0 +1,46 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2261: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Pure","Standard","Turbo","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.BOOST_IQ: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2262.py b/custom_components/robovac/vacuums/T2262.py new file mode 100644 index 0000000..2b5f879 --- /dev/null +++ b/custom_components/robovac/vacuums/T2262.py @@ -0,0 +1,45 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2262: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.MAP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.ROOM | RoboVacEntityFeature.ZONE | RoboVacEntityFeature.BOOST_IQ | RoboVacEntityFeature.MAP + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Pure","Standard","Turbo","Max"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.BOOST_IQ: 0, + } diff --git a/custom_components/robovac/vacuums/T2266.py b/custom_components/robovac/vacuums/T2266.py new file mode 100644 index 0000000..1f996a2 --- /dev/null +++ b/custom_components/robovac/vacuums/T2266.py @@ -0,0 +1,134 @@ +from homeassistant.components.vacuum import VacuumEntityFeature + +from .base import RobovacCommand, RoboVacEntityFeature + + +class T2266: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + # | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + # | VacuumEntityFeature.MAP + ) + robovac_features = ( + # RoboVacEntityFeature.CLEANING_TIME + # | RoboVacEntityFeature.CLEANING_AREA + RoboVacEntityFeature.DO_NOT_DISTURB + # | RoboVacEntityFeature.AUTO_RETURN + # | RoboVacEntityFeature.ROOM + # | RoboVacEntityFeature.ZONE + | RoboVacEntityFeature.BOOST_IQ + # | RoboVacEntityFeature.MAP + # | RoboVacEntityFeature.CONSUMABLES + ) + commands = { + RobovacCommand.MODE: { # works (Start Auto and Return dock commands tested) + "code": 152, + "values": ["AggN", "AA==", "AggG", "BBoCCAE=", "AggO"], + }, + RobovacCommand.STATUS: { # works (status only) + "code": 153, + "values": [ + "BgoAEAUyAA===", + "BgoAEAVSAA===", + "CAoAEAUyAggB", + "CAoCCAEQBTIA", + "CAoCCAEQBVIA", + "CgoCCAEQBTICCAE=", + "CAoCCAIQBTIA", + "CAoCCAIQBVIA", + "CgoCCAIQBTICCAE=", + "BAoAEAY=", + "BBAHQgA=", + "BBADGgA=", + "BhADGgIIAQ==", + "AA==", + "AhAB", + ], + }, + RobovacCommand.DIRECTION: { # untested + "code": 155, + "values": ["Brake", "Forward", "Back", "Left", "Right"], + }, + RobovacCommand.START_PAUSE: 156, # True, False #works (status only) + RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested + RobovacCommand.FAN_SPEED: { # works (status and update) + "code": 158, + "values": ["Quiet", "Standard", "Turbo", "Max"], + }, + RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update) + RobovacCommand.LOCATE: 160, # True, False #works (status) + # Speaker volume: 161 #works, not yet implemented + RobovacCommand.BATTERY: 163, # int #works (status) + RobovacCommand.CONSUMABLES: 168, # encrypted, not usable + RobovacCommand.RETURN_HOME: 173, # encrypted, not usable + # FgoQMggKAggBEgIQAToECgIIARICCAE= + # FgoQMg4KAggBEggIARj/////DxICCAE= + # FAoQMggKAggBEgIQAToECgIIARIA + # GAoQMggKAggBEgIQAToECgIIARIECAE4AQ== + # GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB + RobovacCommand.ERROR: 177, # #encrypted, few known values + # SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR" + # ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2" + # IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ== + # IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ== + # IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ== + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # Unknown: 151 (true/false) + # Unknown: 154 + # DgoKCgAaAggBIgIIARIA + # DAoICgAaAggBIgASAA== + # Unknown: 164 + # MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA== + # NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA= + # Unknown: 167 + # FAoAEgcIiEoQbhgEGgcI1EgQbBgC + # FgoCEAESBwiIShBuGAQaBwjUSBBsGAI= + # GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg== + # GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI= + # GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw== + # Unknown: 171 + # AhAB + # Unknown: 176 + # MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY= + # LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG + # LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # Unknown: 178 + # DQjRidjfv9bszgESAR8= + # DQiMrPbd+eLszgESAVU= + # DQiW0dXL+uLszgESAR8= + # Cgiv6NbWsePszgE= + # DQjPuorb6eTszgESAR8= + # DQjayd7nsOXszgESASg= + # Unknown: 179 + # EBIOKgwIBRACGAEgwYyAtQY= + # FhIUEhIIBRABIFsowYyAtQYw74yAtQY= + # DhIMKgoIBhgCIPvagrUG + # DhIMKgoIBxgCIJLbgrUG + # EBIOKgwIBxADGAIg3eyCtQY= + # EBIOKgwIBxAEGAIgrPGCtQY= + # DhIMKgoICBgCILHxgrUG + # DhIMKgoICBADIIj6grUG + # DhIMKgoICBADIOqMg7UG + # DhIMKgoICBAEIOuMg7UG + # DBIKKggICSCljYO1Bg== + # DhIMKgoICRACIJmcg7UG + # FhIUEhIICRABIBoomZyDtQYw6pyDtQY= + # DhIMIgoICRABGO+cg7UG + # DhIMIgoICRABGLedg7UG + # IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA== + # Unknown: 169 + # cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE= + # s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01 + } diff --git a/custom_components/robovac/vacuums/T2267.py b/custom_components/robovac/vacuums/T2267.py new file mode 100644 index 0000000..1f20fe4 --- /dev/null +++ b/custom_components/robovac/vacuums/T2267.py @@ -0,0 +1,120 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2267: + homeassistant_features = ( + VacuumEntityFeature.BATTERY +# | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP +# | VacuumEntityFeature.MAP + ) + robovac_features = ( + # RoboVacEntityFeature.CLEANING_TIME + # | RoboVacEntityFeature.CLEANING_AREA + RoboVacEntityFeature.DO_NOT_DISTURB + # | RoboVacEntityFeature.AUTO_RETURN + # | RoboVacEntityFeature.ROOM + # | RoboVacEntityFeature.ZONE + | RoboVacEntityFeature.BOOST_IQ + # | RoboVacEntityFeature.MAP + # | RoboVacEntityFeature.CONSUMABLES + ) + commands = { + RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested) + "code": 152, + "values": ["AggN","AA==","AggG","BBoCCAE=","AggO"], + }, + RobovacCommand.STATUS: { #works (status only) + "code": 153, + "values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"], + }, + RobovacCommand.DIRECTION: { #untested + "code": 155, + "values": ["Brake", "Forward", "Back", "Left", "Right"], + }, + RobovacCommand.START_PAUSE: 156, # True, False #works (status only) + RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested + RobovacCommand.FAN_SPEED: { #works (status and update) + "code": 158, + "values": ["Quiet", "Standard", "Turbo", "Max"], + }, + RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update) + RobovacCommand.LOCATE: 160, # True, False #works (status) + # Speaker volume: 161 #works, not yet implemented + RobovacCommand.BATTERY: 163, # int #works (status) + RobovacCommand.CONSUMABLES: 168, #encrypted, not usable + RobovacCommand.RETURN_HOME: 173, #encrypted, not usable + # FgoQMggKAggBEgIQAToECgIIARICCAE= + # FgoQMg4KAggBEggIARj/////DxICCAE= + # FAoQMggKAggBEgIQAToECgIIARIA + # GAoQMggKAggBEgIQAToECgIIARIECAE4AQ== + # GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB + RobovacCommand.ERROR: 177, # #encrypted, few known values + # SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR" + # ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2" + + + # IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ== + # IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ== + # IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ== + + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # Unknown: 151 (true/false) + # Unknown: 154 + # DgoKCgAaAggBIgIIARIA + # DAoICgAaAggBIgASAA== + # Unknown: 164 + # MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA== + # NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA= + # Unknown: 167 + # FAoAEgcIiEoQbhgEGgcI1EgQbBgC + # FgoCEAESBwiIShBuGAQaBwjUSBBsGAI= + # GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg== + # GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI= + # GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw== + # Unknown: 171 + # AhAB + # Unknown: 176 + # MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY= + # LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG + # LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # Unknown: 178 + # DQjRidjfv9bszgESAR8= + # DQiMrPbd+eLszgESAVU= + # DQiW0dXL+uLszgESAR8= + # Cgiv6NbWsePszgE= + # DQjPuorb6eTszgESAR8= + # DQjayd7nsOXszgESASg= + # Unknown: 179 + # EBIOKgwIBRACGAEgwYyAtQY= + # FhIUEhIIBRABIFsowYyAtQYw74yAtQY= + # DhIMKgoIBhgCIPvagrUG + # DhIMKgoIBxgCIJLbgrUG + # EBIOKgwIBxADGAIg3eyCtQY= + # EBIOKgwIBxAEGAIgrPGCtQY= + # DhIMKgoICBgCILHxgrUG + # DhIMKgoICBADIIj6grUG + # DhIMKgoICBADIOqMg7UG + # DhIMKgoICBAEIOuMg7UG + # DBIKKggICSCljYO1Bg== + # DhIMKgoICRACIJmcg7UG + # FhIUEhIICRABIBoomZyDtQYw6pyDtQY= + # DhIMIgoICRABGO+cg7UG + # DhIMIgoICRABGLedg7UG + # IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA== + # Unknown: 169 + # cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE= + # s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01 + } diff --git a/custom_components/robovac/vacuums/T2270.py b/custom_components/robovac/vacuums/T2270.py new file mode 100644 index 0000000..9ed8a9c --- /dev/null +++ b/custom_components/robovac/vacuums/T2270.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2270: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2272.py b/custom_components/robovac/vacuums/T2272.py new file mode 100644 index 0000000..09529d4 --- /dev/null +++ b/custom_components/robovac/vacuums/T2272.py @@ -0,0 +1,43 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2272: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + } diff --git a/custom_components/robovac/vacuums/T2273.py b/custom_components/robovac/vacuums/T2273.py new file mode 100644 index 0000000..1261ff0 --- /dev/null +++ b/custom_components/robovac/vacuums/T2273.py @@ -0,0 +1,44 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2273: + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP + ) + robovac_features = RoboVacEntityFeature.CLEANING_TIME | RoboVacEntityFeature.CLEANING_AREA | RoboVacEntityFeature.DO_NOT_DISTURB | RoboVacEntityFeature.AUTO_RETURN | RoboVacEntityFeature.CONSUMABLES + commands = { + RobovacCommand.START_PAUSE: 2, + RobovacCommand.DIRECTION: { + "code": 3, + "values": ["forward", "back", "left", "right"], + }, + RobovacCommand.MODE: { + "code": 5, + "values": ["auto", "SmallRoom", "Spot", "Edge", "Nosweep"], + }, + RobovacCommand.STATUS: 15, + RobovacCommand.RETURN_HOME: 101, + RobovacCommand.FAN_SPEED: { + "code": 102, + "values": ["Standard","Turbo","Max","Boost_IQ"], + }, + RobovacCommand.LOCATE: 103, + RobovacCommand.BATTERY: 104, + RobovacCommand.ERROR: 106, + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # RobovacCommand.DO_NOT_DISTURB: 0, + # RobovacCommand.CONSUMABLES: 0, + } diff --git a/custom_components/robovac/vacuums/T2275.py b/custom_components/robovac/vacuums/T2275.py new file mode 100644 index 0000000..8411453 --- /dev/null +++ b/custom_components/robovac/vacuums/T2275.py @@ -0,0 +1,120 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2275: + homeassistant_features = ( + VacuumEntityFeature.BATTERY +# | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP +# | VacuumEntityFeature.MAP + ) + robovac_features = ( + # RoboVacEntityFeature.CLEANING_TIME + # | RoboVacEntityFeature.CLEANING_AREA + RoboVacEntityFeature.DO_NOT_DISTURB + # | RoboVacEntityFeature.AUTO_RETURN + # | RoboVacEntityFeature.ROOM + # | RoboVacEntityFeature.ZONE + | RoboVacEntityFeature.BOOST_IQ + # | RoboVacEntityFeature.MAP + # | RoboVacEntityFeature.CONSUMABLES + ) + commands = { + RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested) + "code": 152, + "values": ["AggN","AA==","AggG","BBoCCAE=","AggO"], + }, + RobovacCommand.STATUS: { #works (status only) + "code": 153, + "values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"], + }, + RobovacCommand.DIRECTION: { #untested + "code": 155, + "values": ["Brake", "Forward", "Back", "Left", "Right"], + }, + RobovacCommand.START_PAUSE: 156, # True, False #works (status only) + RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested + RobovacCommand.FAN_SPEED: { #works (status and update) + "code": 158, + "values": ["Quiet", "Standard", "Turbo", "Max"], + }, + RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update) + RobovacCommand.LOCATE: 160, # True, False #works (status) + # Speaker volume: 161 #works, not yet implemented + RobovacCommand.BATTERY: 163, # int #works (status) + RobovacCommand.CONSUMABLES: 168, #encrypted, not usable + RobovacCommand.RETURN_HOME: 173, #encrypted, not usable + # FgoQMggKAggBEgIQAToECgIIARICCAE= + # FgoQMg4KAggBEggIARj/////DxICCAE= + # FAoQMggKAggBEgIQAToECgIIARIA + # GAoQMggKAggBEgIQAToECgIIARIECAE4AQ== + # GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB + RobovacCommand.ERROR: 177, # #encrypted, few known values + # SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR" + # ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2" + + + # IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ== + # IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ== + # IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ== + + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # Unknown: 151 (true/false) + # Unknown: 154 + # DgoKCgAaAggBIgIIARIA + # DAoICgAaAggBIgASAA== + # Unknown: 164 + # MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA== + # NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA= + # Unknown: 167 + # FAoAEgcIiEoQbhgEGgcI1EgQbBgC + # FgoCEAESBwiIShBuGAQaBwjUSBBsGAI= + # GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg== + # GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI= + # GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw== + # Unknown: 171 + # AhAB + # Unknown: 176 + # MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY= + # LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG + # LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # Unknown: 178 + # DQjRidjfv9bszgESAR8= + # DQiMrPbd+eLszgESAVU= + # DQiW0dXL+uLszgESAR8= + # Cgiv6NbWsePszgE= + # DQjPuorb6eTszgESAR8= + # DQjayd7nsOXszgESASg= + # Unknown: 179 + # EBIOKgwIBRACGAEgwYyAtQY= + # FhIUEhIIBRABIFsowYyAtQYw74yAtQY= + # DhIMKgoIBhgCIPvagrUG + # DhIMKgoIBxgCIJLbgrUG + # EBIOKgwIBxADGAIg3eyCtQY= + # EBIOKgwIBxAEGAIgrPGCtQY= + # DhIMKgoICBgCILHxgrUG + # DhIMKgoICBADIIj6grUG + # DhIMKgoICBADIOqMg7UG + # DhIMKgoICBAEIOuMg7UG + # DBIKKggICSCljYO1Bg== + # DhIMKgoICRACIJmcg7UG + # FhIUEhIICRABIBoomZyDtQYw6pyDtQY= + # DhIMIgoICRABGO+cg7UG + # DhIMIgoICRABGLedg7UG + # IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA== + # Unknown: 169 + # cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE= + # s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01 + } diff --git a/custom_components/robovac/vacuums/T2276.py b/custom_components/robovac/vacuums/T2276.py new file mode 100644 index 0000000..223b0da --- /dev/null +++ b/custom_components/robovac/vacuums/T2276.py @@ -0,0 +1,120 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2276: + homeassistant_features = ( + VacuumEntityFeature.BATTERY +# | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP +# | VacuumEntityFeature.MAP + ) + robovac_features = ( + # RoboVacEntityFeature.CLEANING_TIME + # | RoboVacEntityFeature.CLEANING_AREA + RoboVacEntityFeature.DO_NOT_DISTURB + # | RoboVacEntityFeature.AUTO_RETURN + # | RoboVacEntityFeature.ROOM + # | RoboVacEntityFeature.ZONE + | RoboVacEntityFeature.BOOST_IQ + # | RoboVacEntityFeature.MAP + # | RoboVacEntityFeature.CONSUMABLES + ) + commands = { + RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested) + "code": 152, + "values": ["AggN","AA==","AggG","BBoCCAE=","AggO"], + }, + RobovacCommand.STATUS: { #works (status only) + "code": 153, + "values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"], + }, + RobovacCommand.DIRECTION: { #untested + "code": 155, + "values": ["Brake", "Forward", "Back", "Left", "Right"], + }, + RobovacCommand.START_PAUSE: 156, # True, False #works (status only) + RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested + RobovacCommand.FAN_SPEED: { #works (status and update) + "code": 158, + "values": ["Quiet", "Standard", "Turbo", "Max"], + }, + RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update) + RobovacCommand.LOCATE: 160, # True, False #works (status) + # Speaker volume: 161 #works, not yet implemented + RobovacCommand.BATTERY: 163, # int #works (status) + RobovacCommand.CONSUMABLES: 168, #encrypted, not usable + RobovacCommand.RETURN_HOME: 173, #encrypted, not usable + # FgoQMggKAggBEgIQAToECgIIARICCAE= + # FgoQMg4KAggBEggIARj/////DxICCAE= + # FAoQMggKAggBEgIQAToECgIIARIA + # GAoQMggKAggBEgIQAToECgIIARIECAE4AQ== + # GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB + RobovacCommand.ERROR: 177, # #encrypted, few known values + # SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR" + # ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2" + + + # IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ== + # IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ== + # IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ== + + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # Unknown: 151 (true/false) + # Unknown: 154 + # DgoKCgAaAggBIgIIARIA + # DAoICgAaAggBIgASAA== + # Unknown: 164 + # MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA== + # NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA= + # Unknown: 167 + # FAoAEgcIiEoQbhgEGgcI1EgQbBgC + # FgoCEAESBwiIShBuGAQaBwjUSBBsGAI= + # GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg== + # GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI= + # GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw== + # Unknown: 171 + # AhAB + # Unknown: 176 + # MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY= + # LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG + # LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # Unknown: 178 + # DQjRidjfv9bszgESAR8= + # DQiMrPbd+eLszgESAVU= + # DQiW0dXL+uLszgESAR8= + # Cgiv6NbWsePszgE= + # DQjPuorb6eTszgESAR8= + # DQjayd7nsOXszgESASg= + # Unknown: 179 + # EBIOKgwIBRACGAEgwYyAtQY= + # FhIUEhIIBRABIFsowYyAtQYw74yAtQY= + # DhIMKgoIBhgCIPvagrUG + # DhIMKgoIBxgCIJLbgrUG + # EBIOKgwIBxADGAIg3eyCtQY= + # EBIOKgwIBxAEGAIgrPGCtQY= + # DhIMKgoICBgCILHxgrUG + # DhIMKgoICBADIIj6grUG + # DhIMKgoICBADIOqMg7UG + # DhIMKgoICBAEIOuMg7UG + # DBIKKggICSCljYO1Bg== + # DhIMKgoICRACIJmcg7UG + # FhIUEhIICRABIBoomZyDtQYw6pyDtQY= + # DhIMIgoICRABGO+cg7UG + # DhIMIgoICRABGLedg7UG + # IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA== + # Unknown: 169 + # cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE= + # s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01 + } diff --git a/custom_components/robovac/vacuums/T2277.py b/custom_components/robovac/vacuums/T2277.py new file mode 100644 index 0000000..f4e8fc7 --- /dev/null +++ b/custom_components/robovac/vacuums/T2277.py @@ -0,0 +1,120 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2277: + homeassistant_features = ( + VacuumEntityFeature.BATTERY +# | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP +# | VacuumEntityFeature.MAP + ) + robovac_features = ( + # RoboVacEntityFeature.CLEANING_TIME + # | RoboVacEntityFeature.CLEANING_AREA + RoboVacEntityFeature.DO_NOT_DISTURB + # | RoboVacEntityFeature.AUTO_RETURN + # | RoboVacEntityFeature.ROOM + # | RoboVacEntityFeature.ZONE + | RoboVacEntityFeature.BOOST_IQ + # | RoboVacEntityFeature.MAP + # | RoboVacEntityFeature.CONSUMABLES + ) + commands = { + RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested) + "code": 152, + "values": ["AggN","AA==","AggG","BBoCCAE=","AggO"], + }, + RobovacCommand.STATUS: { #works (status only) + "code": 153, + "values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"], + }, + RobovacCommand.DIRECTION: { #untested + "code": 155, + "values": ["Brake", "Forward", "Back", "Left", "Right"], + }, + RobovacCommand.START_PAUSE: 156, # True, False #works (status only) + RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested + RobovacCommand.FAN_SPEED: { #works (status and update) + "code": 158, + "values": ["Quiet", "Standard", "Turbo", "Max"], + }, + RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update) + RobovacCommand.LOCATE: 160, # True, False #works (status) + # Speaker volume: 161 #works, not yet implemented + RobovacCommand.BATTERY: 163, # int #works (status) + RobovacCommand.CONSUMABLES: 168, #encrypted, not usable + RobovacCommand.RETURN_HOME: 173, #encrypted, not usable + # FgoQMggKAggBEgIQAToECgIIARICCAE= + # FgoQMg4KAggBEggIARj/////DxICCAE= + # FAoQMggKAggBEgIQAToECgIIARIA + # GAoQMggKAggBEgIQAToECgIIARIECAE4AQ== + # GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB + RobovacCommand.ERROR: 177, # #encrypted, few known values + # SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR" + # ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2" + + + # IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ== + # IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ== + # IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ== + + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # Unknown: 151 (true/false) + # Unknown: 154 + # DgoKCgAaAggBIgIIARIA + # DAoICgAaAggBIgASAA== + # Unknown: 164 + # MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA== + # NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA= + # Unknown: 167 + # FAoAEgcIiEoQbhgEGgcI1EgQbBgC + # FgoCEAESBwiIShBuGAQaBwjUSBBsGAI= + # GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg== + # GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI= + # GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw== + # Unknown: 171 + # AhAB + # Unknown: 176 + # MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY= + # LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG + # LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # Unknown: 178 + # DQjRidjfv9bszgESAR8= + # DQiMrPbd+eLszgESAVU= + # DQiW0dXL+uLszgESAR8= + # Cgiv6NbWsePszgE= + # DQjPuorb6eTszgESAR8= + # DQjayd7nsOXszgESASg= + # Unknown: 179 + # EBIOKgwIBRACGAEgwYyAtQY= + # FhIUEhIIBRABIFsowYyAtQYw74yAtQY= + # DhIMKgoIBhgCIPvagrUG + # DhIMKgoIBxgCIJLbgrUG + # EBIOKgwIBxADGAIg3eyCtQY= + # EBIOKgwIBxAEGAIgrPGCtQY= + # DhIMKgoICBgCILHxgrUG + # DhIMKgoICBADIIj6grUG + # DhIMKgoICBADIOqMg7UG + # DhIMKgoICBAEIOuMg7UG + # DBIKKggICSCljYO1Bg== + # DhIMKgoICRACIJmcg7UG + # FhIUEhIICRABIBoomZyDtQYw6pyDtQY= + # DhIMIgoICRABGO+cg7UG + # DhIMIgoICRABGLedg7UG + # IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA== + # Unknown: 169 + # cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE= + # s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01 + } diff --git a/custom_components/robovac/vacuums/T2278.py b/custom_components/robovac/vacuums/T2278.py new file mode 100644 index 0000000..c4b1ce0 --- /dev/null +++ b/custom_components/robovac/vacuums/T2278.py @@ -0,0 +1,120 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2278: + homeassistant_features = ( + VacuumEntityFeature.BATTERY +# | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP +# | VacuumEntityFeature.MAP + ) + robovac_features = ( + # RoboVacEntityFeature.CLEANING_TIME + # | RoboVacEntityFeature.CLEANING_AREA + RoboVacEntityFeature.DO_NOT_DISTURB + # | RoboVacEntityFeature.AUTO_RETURN + # | RoboVacEntityFeature.ROOM + # | RoboVacEntityFeature.ZONE + | RoboVacEntityFeature.BOOST_IQ + # | RoboVacEntityFeature.MAP + # | RoboVacEntityFeature.CONSUMABLES + ) + commands = { + RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested) + "code": 152, + "values": ["AggN","AA==","AggG","BBoCCAE=","AggO"], + }, + RobovacCommand.STATUS: { #works (status only) + "code": 153, + "values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"], + }, + RobovacCommand.DIRECTION: { #untested + "code": 155, + "values": ["Brake", "Forward", "Back", "Left", "Right"], + }, + RobovacCommand.START_PAUSE: 156, # True, False #works (status only) + RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested + RobovacCommand.FAN_SPEED: { #works (status and update) + "code": 158, + "values": ["Quiet", "Standard", "Turbo", "Max"], + }, + RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update) + RobovacCommand.LOCATE: 160, # True, False #works (status) + # Speaker volume: 161 #works, not yet implemented + RobovacCommand.BATTERY: 163, # int #works (status) + RobovacCommand.CONSUMABLES: 168, #encrypted, not usable + RobovacCommand.RETURN_HOME: 173, #encrypted, not usable + # FgoQMggKAggBEgIQAToECgIIARICCAE= + # FgoQMg4KAggBEggIARj/////DxICCAE= + # FAoQMggKAggBEgIQAToECgIIARIA + # GAoQMggKAggBEgIQAToECgIIARIECAE4AQ== + # GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB + RobovacCommand.ERROR: 177, # #encrypted, few known values + # SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR" + # ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2" + + + # IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ== + # IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ== + # IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ== + + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # Unknown: 151 (true/false) + # Unknown: 154 + # DgoKCgAaAggBIgIIARIA + # DAoICgAaAggBIgASAA== + # Unknown: 164 + # MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA== + # NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA= + # Unknown: 167 + # FAoAEgcIiEoQbhgEGgcI1EgQbBgC + # FgoCEAESBwiIShBuGAQaBwjUSBBsGAI= + # GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg== + # GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI= + # GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw== + # Unknown: 171 + # AhAB + # Unknown: 176 + # MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY= + # LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG + # LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # Unknown: 178 + # DQjRidjfv9bszgESAR8= + # DQiMrPbd+eLszgESAVU= + # DQiW0dXL+uLszgESAR8= + # Cgiv6NbWsePszgE= + # DQjPuorb6eTszgESAR8= + # DQjayd7nsOXszgESASg= + # Unknown: 179 + # EBIOKgwIBRACGAEgwYyAtQY= + # FhIUEhIIBRABIFsowYyAtQYw74yAtQY= + # DhIMKgoIBhgCIPvagrUG + # DhIMKgoIBxgCIJLbgrUG + # EBIOKgwIBxADGAIg3eyCtQY= + # EBIOKgwIBxAEGAIgrPGCtQY= + # DhIMKgoICBgCILHxgrUG + # DhIMKgoICBADIIj6grUG + # DhIMKgoICBADIOqMg7UG + # DhIMKgoICBAEIOuMg7UG + # DBIKKggICSCljYO1Bg== + # DhIMKgoICRACIJmcg7UG + # FhIUEhIICRABIBoomZyDtQYw6pyDtQY= + # DhIMIgoICRABGO+cg7UG + # DhIMIgoICRABGLedg7UG + # IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA== + # Unknown: 169 + # cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE= + # s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01 + } diff --git a/custom_components/robovac/vacuums/T2320.py b/custom_components/robovac/vacuums/T2320.py new file mode 100644 index 0000000..1742aaf --- /dev/null +++ b/custom_components/robovac/vacuums/T2320.py @@ -0,0 +1,120 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2320: + homeassistant_features = ( + VacuumEntityFeature.BATTERY +# | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP +# | VacuumEntityFeature.MAP + ) + robovac_features = ( + # RoboVacEntityFeature.CLEANING_TIME + # | RoboVacEntityFeature.CLEANING_AREA + RoboVacEntityFeature.DO_NOT_DISTURB + # | RoboVacEntityFeature.AUTO_RETURN + # | RoboVacEntityFeature.ROOM + # | RoboVacEntityFeature.ZONE + | RoboVacEntityFeature.BOOST_IQ + # | RoboVacEntityFeature.MAP + # | RoboVacEntityFeature.CONSUMABLES + ) + commands = { + RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested) + "code": 152, + "values": ["AggN","AA==","AggG","BBoCCAE=","AggO"], + }, + RobovacCommand.STATUS: { #works (status only) + "code": 153, + "values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"], + }, + RobovacCommand.DIRECTION: { #untested + "code": 155, + "values": ["Brake", "Forward", "Back", "Left", "Right"], + }, + RobovacCommand.START_PAUSE: 156, # True, False #works (status only) + RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested + RobovacCommand.FAN_SPEED: { #works (status and update) + "code": 158, + "values": ["Quiet", "Standard", "Turbo", "Max"], + }, + RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update) + RobovacCommand.LOCATE: 160, # True, False #works (status) + # Speaker volume: 161 #works, not yet implemented + RobovacCommand.BATTERY: 163, # int #works (status) + RobovacCommand.CONSUMABLES: 168, #encrypted, not usable + RobovacCommand.RETURN_HOME: 173, #encrypted, not usable + # FgoQMggKAggBEgIQAToECgIIARICCAE= + # FgoQMg4KAggBEggIARj/////DxICCAE= + # FAoQMggKAggBEgIQAToECgIIARIA + # GAoQMggKAggBEgIQAToECgIIARIECAE4AQ== + # GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB + RobovacCommand.ERROR: 177, # #encrypted, few known values + # SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR" + # ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2" + + + # IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ== + # IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ== + # IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ== + + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # Unknown: 151 (true/false) + # Unknown: 154 + # DgoKCgAaAggBIgIIARIA + # DAoICgAaAggBIgASAA== + # Unknown: 164 + # MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA== + # NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA= + # Unknown: 167 + # FAoAEgcIiEoQbhgEGgcI1EgQbBgC + # FgoCEAESBwiIShBuGAQaBwjUSBBsGAI= + # GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg== + # GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI= + # GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw== + # Unknown: 171 + # AhAB + # Unknown: 176 + # MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY= + # LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG + # LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # Unknown: 178 + # DQjRidjfv9bszgESAR8= + # DQiMrPbd+eLszgESAVU= + # DQiW0dXL+uLszgESAR8= + # Cgiv6NbWsePszgE= + # DQjPuorb6eTszgESAR8= + # DQjayd7nsOXszgESASg= + # Unknown: 179 + # EBIOKgwIBRACGAEgwYyAtQY= + # FhIUEhIIBRABIFsowYyAtQYw74yAtQY= + # DhIMKgoIBhgCIPvagrUG + # DhIMKgoIBxgCIJLbgrUG + # EBIOKgwIBxADGAIg3eyCtQY= + # EBIOKgwIBxAEGAIgrPGCtQY= + # DhIMKgoICBgCILHxgrUG + # DhIMKgoICBADIIj6grUG + # DhIMKgoICBADIOqMg7UG + # DhIMKgoICBAEIOuMg7UG + # DBIKKggICSCljYO1Bg== + # DhIMKgoICRACIJmcg7UG + # FhIUEhIICRABIBoomZyDtQYw6pyDtQY= + # DhIMIgoICRABGO+cg7UG + # DhIMIgoICRABGLedg7UG + # IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA== + # Unknown: 169 + # cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE= + # s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01 + } diff --git a/custom_components/robovac/vacuums/T2351.py b/custom_components/robovac/vacuums/T2351.py new file mode 100644 index 0000000..3c15698 --- /dev/null +++ b/custom_components/robovac/vacuums/T2351.py @@ -0,0 +1,120 @@ +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand + + +class T2351: + homeassistant_features = ( + VacuumEntityFeature.BATTERY +# | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STOP +# | VacuumEntityFeature.MAP + ) + robovac_features = ( + # RoboVacEntityFeature.CLEANING_TIME + # | RoboVacEntityFeature.CLEANING_AREA + RoboVacEntityFeature.DO_NOT_DISTURB + # | RoboVacEntityFeature.AUTO_RETURN + # | RoboVacEntityFeature.ROOM + # | RoboVacEntityFeature.ZONE + | RoboVacEntityFeature.BOOST_IQ + # | RoboVacEntityFeature.MAP + # | RoboVacEntityFeature.CONSUMABLES + ) + commands = { + RobovacCommand.MODE: { #works (Start Auto and Return dock commands tested) + "code": 152, + "values": ["AggN","AA==","AggG","BBoCCAE=","AggO"], + }, + RobovacCommand.STATUS: { #works (status only) + "code": 153, + "values": ["BgoAEAUyAA===","BgoAEAVSAA===","CAoAEAUyAggB","CAoCCAEQBTIA","CAoCCAEQBVIA","CgoCCAEQBTICCAE=","CAoCCAIQBTIA","CAoCCAIQBVIA","CgoCCAIQBTICCAE=","BAoAEAY=","BBAHQgA=","BBADGgA=","BhADGgIIAQ==","AA==","AhAB"], + }, + RobovacCommand.DIRECTION: { #untested + "code": 155, + "values": ["Brake", "Forward", "Back", "Left", "Right"], + }, + RobovacCommand.START_PAUSE: 156, # True, False #works (status only) + RobovacCommand.DO_NOT_DISTURB: 157, # DgoAEgoKABICCBYaAggI #untested + RobovacCommand.FAN_SPEED: { #works (status and update) + "code": 158, + "values": ["Quiet", "Standard", "Turbo", "Max"], + }, + RobovacCommand.BOOST_IQ: 159, # True, False #works (status and update) + RobovacCommand.LOCATE: 160, # True, False #works (status) + # Speaker volume: 161 #works, not yet implemented + RobovacCommand.BATTERY: 163, # int #works (status) + RobovacCommand.CONSUMABLES: 168, #encrypted, not usable + RobovacCommand.RETURN_HOME: 173, #encrypted, not usable + # FgoQMggKAggBEgIQAToECgIIARICCAE= + # FgoQMg4KAggBEggIARj/////DxICCAE= + # FAoQMggKAggBEgIQAToECgIIARIA + # GAoQMggKAggBEgIQAToECgIIARIECAE4AQ== + # GgoQMggKAggBEgIQAToECgIIARIGCAEYATgB + RobovacCommand.ERROR: 177, # #encrypted, few known values + # SIDEBRUSH_STUCK: "FAjwudWorOPszgEaAqURUgQSAqUR" + # ROBOT_STUCK: "FAj+nMu7zuPszgEaAtg2UgQSAtg2" + + + # IQofCgIIAhICCAIaAggCKgIIAjoCCBugAe7Pqs6M1+zOAQ== + # IQofCgIIAhICCAIaAggCKgIIAjoCCBqgAYPx0a331uzOAQ== + # IQofCgIIBBICCAQaAggEKgIIBDoCCCmgAcSfs6Lo5uzOAQ== + + # These commands need codes adding + # RobovacCommand.CLEANING_AREA: 0, + # RobovacCommand.CLEANING_TIME: 0, + # RobovacCommand.AUTO_RETURN: 0, + # Unknown: 151 (true/false) + # Unknown: 154 + # DgoKCgAaAggBIgIIARIA + # DAoICgAaAggBIgASAA== + # Unknown: 164 + # MBoAIiwKBgi4y/q0BhIECAEQARoMCAESBBgJIB4aAgg+Kg4aDBIKCgIIARIAGgAiAA== + # NAgGEAYaACIsCgYIuMv6tAYSBAgBEAEaDAgBEgQYCSAeGgIIPioOGgwSCgoCCAESABoAIgA= + # Unknown: 167 + # FAoAEgcIiEoQbhgEGgcI1EgQbBgC + # FgoCEAESBwiIShBuGAQaBwjUSBBsGAI= + # GAoECDwQARIHCIhKEG4YBBoHCNRIEGwYAg== + # GQoFCLQBEAQSBwiIShBuGAQaBwjUSBBsGAI= + # GwoFCKApEDgSCAiocxCmARgFGggI9HEQpAEYAw== + # Unknown: 171 + # AhAB + # Unknown: 176 + # MQoAGgBSCBoAIgIIASoAWDJiHwodChFBIG5ldHdvcmsgZm9yIHlvdRABGgYQ4/7/tAY= + # LwoAGgBSCBoAIgIIASoAWFZiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEPvagrUG + # LwoAGgBSCBoAIgIIASoAWCJiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # LwoAGgBSCBoAIgIIASoAWDBiHQobChFBIG5ldHdvcmsgZm9yIHlvdRoGEK2YgLUG + # Unknown: 178 + # DQjRidjfv9bszgESAR8= + # DQiMrPbd+eLszgESAVU= + # DQiW0dXL+uLszgESAR8= + # Cgiv6NbWsePszgE= + # DQjPuorb6eTszgESAR8= + # DQjayd7nsOXszgESASg= + # Unknown: 179 + # EBIOKgwIBRACGAEgwYyAtQY= + # FhIUEhIIBRABIFsowYyAtQYw74yAtQY= + # DhIMKgoIBhgCIPvagrUG + # DhIMKgoIBxgCIJLbgrUG + # EBIOKgwIBxADGAIg3eyCtQY= + # EBIOKgwIBxAEGAIgrPGCtQY= + # DhIMKgoICBgCILHxgrUG + # DhIMKgoICBADIIj6grUG + # DhIMKgoICBADIOqMg7UG + # DhIMKgoICBAEIOuMg7UG + # DBIKKggICSCljYO1Bg== + # DhIMKgoICRACIJmcg7UG + # FhIUEhIICRABIBoomZyDtQYw6pyDtQY= + # DhIMIgoICRABGO+cg7UG + # DhIMIgoICRABGLedg7UG + # IRIfCh0ICRgBMPvagrUGOMmdg7UGQKApSDhQO1gBYAdqAA== + # Unknown: 169 + # cwoSZXVmeSBDbGVhbiBMNjAgU0VTGhFDODpGRTowRjo3Nzo5NDo5QyIGMS4zLjI0KAVCKDM2NGFjOGNkNjQzZjllMDczZjg4NzlmNGFhOTdkZGE5OGUzMjg5NTRiFggBEgQIAhABGgQIAhABIgIIASoCCAE= + # s \x12eufy Clean L60 SES\x1a\x11C8:FE:0F:77:94:9C"\x061.3.24(\x05B(364ac8cd643f9e073f8879f4aa97dda98e328954b\x16\x08\x01\x12\x04\x08\x02\x10\x01\x1a\x04\x08\x02\x10\x01"\x02\x08\x01*\x02\x08\x01 + } diff --git a/custom_components/robovac/vacuums/__init__.py b/custom_components/robovac/vacuums/__init__.py new file mode 100644 index 0000000..bbfd56f --- /dev/null +++ b/custom_components/robovac/vacuums/__init__.py @@ -0,0 +1,83 @@ +from .T1250 import T1250 +from .T2103 import T2103 +from .T2117 import T2117 +from .T2118 import T2118 +from .T2119 import T2119 +from .T2120 import T2120 +from .T2123 import T2123 +from .T2128 import T2128 +from .T2130 import T2130 +from .T2132 import T2132 +from .T2150 import T2150 +from .T2181 import T2181 +from .T2182 import T2182 +from .T2190 import T2190 +from .T2192 import T2192 +from .T2193 import T2193 +from .T2194 import T2194 +from .T2250 import T2250 +from .T2251 import T2251 +from .T2252 import T2252 +from .T2253 import T2253 +from .T2254 import T2254 +from .T2255 import T2255 +from .T2256 import T2256 +from .T2257 import T2257 +from .T2258 import T2258 +from .T2259 import T2259 +from .T2261 import T2261 +from .T2262 import T2262 +from .T2266 import T2266 +from .T2267 import T2267 +from .T2270 import T2270 +from .T2272 import T2272 +from .T2273 import T2273 +from .T2275 import T2275 +from .T2276 import T2276 +from .T2277 import T2277 +from .T2278 import T2278 +from .T2320 import T2320 +from .T2351 import T2351 + +ROBOVAC_MODELS = { + "T2103": T2103, + "T2117": T2117, + "T2118": T2118, + "T2119": T2119, + "T2120": T2120, + "T2123": T2123, + "T2128": T2128, + "T2130": T2130, + "T2132": T2132, + "T1250": T1250, + "T2250": T2250, + "T2251": T2251, + "T2252": T2252, + "T2253": T2253, + "T2254": T2254, + "T2150": T2150, + "T2255": T2255, + "T2256": T2256, + "T2257": T2257, + "T2258": T2258, + "T2259": T2259, + "T2270": T2270, + "T2272": T2272, + "T2273": T2273, + "T2181": T2181, + "T2182": T2182, + "T2190": T2190, + "T2192": T2192, + "T2193": T2193, + "T2194": T2194, + "T2267": T2267, + "T2277": T2277, + "T2278": T2278, + "T2261": T2261, + "T2262": T2262, + "T2266": T2266, + "T2320": T2320, + "T2351": T2351, + "T2275": T2275, + "T2276": T2276, +} diff --git a/custom_components/robovac/vacuums/base.py b/custom_components/robovac/vacuums/base.py new file mode 100644 index 0000000..90f4bec --- /dev/null +++ b/custom_components/robovac/vacuums/base.py @@ -0,0 +1,35 @@ +from enum import IntEnum, StrEnum + + +class RoboVacEntityFeature(IntEnum): + """Supported features of the RoboVac entity.""" + + EDGE = 1 + SMALL_ROOM = 2 + CLEANING_TIME = 4 + CLEANING_AREA = 8 + DO_NOT_DISTURB = 16 + AUTO_RETURN = 32 + CONSUMABLES = 64 + ROOM = 128 + ZONE = 256 + MAP = 512 + BOOST_IQ = 1024 + + +class RobovacCommand(StrEnum): + START_PAUSE = "start_pause" + DIRECTION = "direction" + MODE = "mode" + STATUS = "status" + RETURN_HOME = "return_home" + FAN_SPEED = "fan_speed" + LOCATE = "locate" + BATTERY = "battery" + ERROR = "error" + CLEANING_AREA = "cleaning_area" + CLEANING_TIME = "cleaning_time" + AUTO_RETURN = "auto_return" + DO_NOT_DISTURB = "do_not_disturb" + BOOST_IQ = "boost_iq" + CONSUMABLES = "consumables" diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..f9c430b --- /dev/null +++ b/hacs.json @@ -0,0 +1,4 @@ +{ + "name": "Eufy RoboVac", + "render_readme": true +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f107a40 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6041 @@ +{ + "name": "robovac", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "conventional-changelog-conventionalcommits": "^7.0.2", + "semantic-release": "^23.0.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", + "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.0.0", + "@octokit/request": "^8.0.2", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", + "integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", + "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", + "dev": true, + "dependencies": { + "@octokit/request": "^8.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.0.tgz", + "integrity": "sha512-NKi0bJEZqOSbBLMv9kdAcuocpe05Q2xAXNLTGi0HN2GSMFJHNZuSoPNa0tcQFTOFCKe+ZaYBZ3lpXh1yxgUDCA==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz", + "integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==", + "dev": true, + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.2.0.tgz", + "integrity": "sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^9.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", + "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", + "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "dev": true, + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@semantic-release/commit-analyzer": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-11.1.0.tgz", + "integrity": "sha512-cXNTbv3nXR2hlzHjAMgbuiQVtvWHTlwwISt60B+4NZv01y/QRY7p2HcJm8Eh2StzcTJoNnflvKjHH/cjFS7d5g==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "import-from-esm": "^1.0.3", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/github": { + "version": "9.2.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-9.2.6.tgz", + "integrity": "sha512-shi+Lrf6exeNZF+sBhK+P011LSbhmIAoUEgEY6SsxF8irJ+J2stwI5jkyDQ+4gzYyDImzV6LCKdYB9FXnQRWKA==", + "dev": true, + "dependencies": { + "@octokit/core": "^5.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-retry": "^6.0.0", + "@octokit/plugin-throttling": "^8.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^6.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/npm": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-11.0.2.tgz", + "integrity": "sha512-owtf3RjyPvRE63iUKZ5/xO4uqjRpVQDUB9+nnXj0xwfIeM9pRl+cG+zGDzdftR4m3f2s4Wyf3SexW+kF5DFtWA==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^8.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.0.0", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "engines": { + "node": "^18.17 || >=20" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/release-notes-generator": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-12.1.0.tgz", + "integrity": "sha512-g6M9AjUKAZUZnxaJZnouNBeDNTCUrJ5Ltj+VJ60gJeDaRRahcHsry9HW8yKrnKkKNkx5lbWiEP1FPMqVNQz8Kg==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-changelog-writer": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^1.0.3", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-pkg-up": "^11.0.0" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "dependencies": { + "type-fest": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "dev": true + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-7.0.1.tgz", + "integrity": "sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==", + "dev": true, + "dependencies": { + "conventional-commits-filter": "^4.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "meow": "^12.0.1", + "semver": "^7.5.2", + "split2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-filter": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-4.0.0.tgz", + "integrity": "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, + "node_modules/env-ci": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.0.0.tgz", + "integrity": "sha512-apikxMgkipkgTvMdRT9MNqWx5VLOci79F4VBd7Op/7OPjjoanjdAvn6fglMCCEf/1bAh8eOiuEVCUs4V3qP3nQ==", + "dev": true, + "dependencies": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.0.1.tgz", + "integrity": "sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ==", + "dev": true, + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", + "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", + "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", + "dev": true, + "dependencies": { + "semver-regex": "^4.0.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/git-log-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", + "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==", + "dev": true, + "dependencies": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" + } + }, + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "dependencies": { + "through2": "~2.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", + "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/hook-std": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", + "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from-esm": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.3.tgz", + "integrity": "sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" + }, + "engines": { + "node": ">=16.20" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/index-to-position": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", + "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/into-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", + "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", + "dev": true, + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", + "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/issue-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", + "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "dev": true, + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/marked": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.0.tgz", + "integrity": "sha512-Vkwtq9rLqXryZnWaQc86+FHLC6tr/fycMfYAhiOIXkrNmeGAyhSxjqu0Rs1i0bBqw5u0S7+lV9fdH2ZSVaoa0w==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-terminal": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.0.0.tgz", + "integrity": "sha512-sNEx8nn9Ktcm6pL0TnRz8tnXq/mSS0Q1FRSwJOAqw4lAB4l49UeDf85Gm1n9RPFm5qurCPjwi1StAQT2XExhZw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^6.2.0", + "chalk": "^5.3.0", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.3", + "node-emoji": "^2.1.3", + "supports-hyperlinks": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <13" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz", + "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true + }, + "node_modules/node-emoji": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", + "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.4.0.tgz", + "integrity": "sha512-RS7Mx0OVfXlOcQLRePuDIYdFCVBPCNapWHplDK+mh7GDdP/Tvor4ocuybRRPSvfcRb2vjRJt1fHCqw3cr8qACQ==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^7.2.1", + "@npmcli/config": "^8.0.2", + "@npmcli/fs": "^3.1.0", + "@npmcli/map-workspaces": "^3.0.4", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.1", + "@npmcli/run-script": "^7.0.4", + "@sigstore/tuf": "^2.3.0", + "abbrev": "^2.0.0", + "archy": "~1.0.0", + "cacache": "^18.0.2", + "chalk": "^5.3.0", + "ci-info": "^4.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.3", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^7.0.1", + "ini": "^4.1.1", + "init-package-json": "^6.0.0", + "is-cidr": "^5.0.3", + "json-parse-even-better-errors": "^3.0.1", + "libnpmaccess": "^8.0.1", + "libnpmdiff": "^6.0.3", + "libnpmexec": "^7.0.4", + "libnpmfund": "^5.0.1", + "libnpmhook": "^10.0.0", + "libnpmorg": "^6.0.1", + "libnpmpack": "^6.0.3", + "libnpmpublish": "^9.0.2", + "libnpmsearch": "^7.0.0", + "libnpmteam": "^6.0.0", + "libnpmversion": "^5.0.1", + "make-fetch-happen": "^13.0.0", + "minimatch": "^9.0.3", + "minipass": "^7.0.4", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^10.0.1", + "nopt": "^7.2.0", + "normalize-package-data": "^6.0.0", + "npm-audit-report": "^5.0.0", + "npm-install-checks": "^6.3.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-profile": "^9.0.0", + "npm-registry-fetch": "^16.1.0", + "npm-user-validate": "^2.0.0", + "npmlog": "^7.0.1", + "p-map": "^4.0.0", + "pacote": "^17.0.6", + "parse-conflict-json": "^3.0.1", + "proc-log": "^3.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^2.1.0", + "semver": "^7.5.4", + "spdx-expression-parse": "^3.0.1", + "ssri": "^10.0.5", + "supports-color": "^9.4.0", + "tar": "^6.2.0", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^5.0.0", + "which": "^4.0.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "7.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.0.0", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/query": "^3.0.1", + "@npmcli/run-script": "^7.0.2", + "bin-links": "^4.0.1", + "cacache": "^18.0.0", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.1", + "json-parse-even-better-errors": "^3.0.0", + "json-stringify-nice": "^1.1.4", + "minimatch": "^9.0.0", + "nopt": "^7.0.0", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "parse-conflict-json": "^3.0.0", + "proc-log": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.5", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "ci-info": "^4.0.0", + "ini": "^4.1.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^17.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.2.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.1.1", + "@sigstore/core": "^0.2.0", + "@sigstore/protobuf-specs": "^0.2.1", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1", + "tuf-js": "^2.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "0.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.1.1", + "@sigstore/core": "^0.2.0", + "@sigstore/protobuf-specs": "^0.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "18.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/gauge": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.3.10", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hasown": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.0", + "promzard": "^1.0.0", + "read": "^2.0.0", + "read-package-json": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "4.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "2.3.6", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "6.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/disparity-colors": "^3.0.0", + "@npmcli/installed-package-contents": "^2.0.2", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^9.0.0", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4", + "tar": "^6.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "7.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "ci-info": "^4.0.0", + "npm-package-arg": "^11.0.1", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "proc-log": "^3.0.0", + "read": "^2.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "6.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "9.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^6.0.0", + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7", + "sigstore": "^2.2.0", + "ssri": "^10.0.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.3", + "@npmcli/run-script": "^7.0.2", + "json-parse-even-better-errors": "^3.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "13.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "6.3.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "11.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "16.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npmlog": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "17.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.10.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~1.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.1.1", + "@sigstore/core": "^0.2.0", + "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/sign": "^2.2.1", + "@sigstore/tuf": "^2.3.0", + "@sigstore/verify": "^0.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.16", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "10.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-filter": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", + "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", + "dev": true, + "dependencies": { + "p-map": "^7.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-map": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.1.tgz", + "integrity": "sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-reduce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", + "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-11.0.0.tgz", + "integrity": "sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==", + "deprecated": "Renamed to read-package-up", + "dev": true, + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.3.tgz", + "integrity": "sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", + "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "index-to-position": "^0.1.2", + "type-fest": "^4.7.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.3.tgz", + "integrity": "sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "dev": true, + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semantic-release": { + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.0.2.tgz", + "integrity": "sha512-OnVYJ6Xgzwe1x8MKswba7RU9+5djS1MWRTrTn5qsq3xZYpslroZkV9Pt0dA2YcIuieeuSZWJhn+yUWoBUHO5Fw==", + "dev": true, + "dependencies": { + "@semantic-release/commit-analyzer": "^11.0.0", + "@semantic-release/error": "^4.0.0", + "@semantic-release/github": "^9.0.0", + "@semantic-release/npm": "^11.0.0", + "@semantic-release/release-notes-generator": "^12.0.0", + "aggregate-error": "^5.0.0", + "cosmiconfig": "^9.0.0", + "debug": "^4.0.0", + "env-ci": "^11.0.0", + "execa": "^8.0.0", + "figures": "^6.0.0", + "find-versions": "^5.1.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^3.0.0", + "hosted-git-info": "^7.0.0", + "import-from-esm": "^1.3.1", + "lodash-es": "^4.17.21", + "marked": "^12.0.0", + "marked-terminal": "^7.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^3.0.0", + "p-reduce": "^3.0.0", + "read-pkg-up": "^11.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^4.0.0", + "signale": "^1.2.1", + "yargs": "^17.5.1" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, + "engines": { + "node": ">=20.8.1" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "dependencies": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/signale/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/signale/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/signale/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "dependencies": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..768bd98 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "conventional-changelog-conventionalcommits": "^7.0.2", + "semantic-release": "^23.0.2" + } +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..53029ae --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +cryptography==41.0.4 +homeassistant==2023.8.4 +Requests==2.31.0 +setuptools==68.0.0 +voluptuous==0.13.1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e1591a1 --- /dev/null +++ b/setup.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2022 Brendan McCluskey +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import re +from setuptools import setup, find_packages +import sys +import warnings + +dynamic_requires = [] + +setup( + name="robovac", + version="1.0", + author="Luke Morrigan", + url="http://github.com/codefoodpixels/robovac", + packages=find_packages(), + scripts=[], + description="Python API for controlling Eufy Robovac vacuum cleaners", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + ], +)