From 999ae9401f565b8c0560a437e646985a091377e9 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 22 Oct 2021 09:18:09 -0700 Subject: [PATCH 01/88] fix: filter None response from non-compliant IMAP servers * related to #533 --- custom_components/mail_and_packages/helpers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index d054bc80..c9171307 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -462,6 +462,11 @@ def email_search( value = "BAD", err.args[0] _LOGGER.debug("DEBUG email_search value: %s", value) + + (status, data) = value + if data is None: + value = status, [b""] + return value From 959bd9a0026b4bebbb0bbc8dd4f959ea8c248a47 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 22 Oct 2021 10:32:33 -0700 Subject: [PATCH 02/88] fix: properly fix NoneType handling * add test for NoneType return on email_search --- .../mail_and_packages/helpers.py | 12 ++++------ tests/test_helpers.py | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index c9171307..374d03c8 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -463,10 +463,6 @@ def email_search( _LOGGER.debug("DEBUG email_search value: %s", value) - (status, data) = value - if data is None: - value = status, [b""] - return value @@ -511,7 +507,7 @@ def get_mails( ) # Bail out on error - if server_response != "OK": + if server_response != "OK" or data[0] is None: return image_count # Check to see if the path exists, if not make it @@ -781,7 +777,7 @@ def get_count( (server_response, data) = email_search( account, const.SENSOR_DATA[sensor_type][const.ATTR_EMAIL], today, subject ) - if server_response == "OK": + if server_response == "OK" and data[0] is not None: if const.ATTR_BODY in const.SENSOR_DATA[sensor_type].keys(): count += find_text( data, account, const.SENSOR_DATA[sensor_type][const.ATTR_BODY][0] @@ -937,7 +933,7 @@ def amazon_search( account, email_address, today, subject ) - if server_response == "OK": + if server_response == "OK" and data[0] is not None: count += len(data[0].split()) _LOGGER.debug("Amazon delivered email(s) found: %s", count) get_amazon_image(data[0], account, image_path, hass, amazon_image_name) @@ -1039,7 +1035,7 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> (server_response, sdata) = email_search(account, email_address, today) # Bail out on error - if server_response != "OK": + if server_response != "OK" or sdata[0] is None: return info if len(sdata) == 0: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 6d053790..afc69daa 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -825,6 +825,13 @@ async def test_amazon_hub(hass, mock_imap_amazon_the_hub): result = amazon_hub(mock_imap_amazon_the_hub) assert result == {} + with patch( + "custom_components.mail_and_packages.helpers.email_search", + return_value=("OK", [None]), + ): + result = amazon_hub(mock_imap_amazon_the_hub) + assert result == {} + async def test_amazon_shipped_order_exception(hass, mock_imap_amazon_shipped, caplog): with patch("quopri.decodestring", side_effect=ValueError): @@ -1024,3 +1031,19 @@ async def test_fedex_out_for_delivery(hass, mock_imap_fedex_out_for_delivery): ) assert result["count"] == 1 assert result["tracking"] == ["61290912345678912345"] + + +async def test_get_mails_email_search_none( + mock_imap_usps_informed_digest_no_mail, + mock_copyoverlays, + mock_copyfile_exception, + caplog, +): + with patch( + "custom_components.mail_and_packages.helpers.email_search", + return_value=("OK", [None]), + ): + result = get_mails( + mock_imap_usps_informed_digest_no_mail, "./", "5", "mail_today.gif", False + ) + assert result == 0 From 007f69f0fc56832499cfe8c65ad31ae88507ec30 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 22 Oct 2021 12:48:25 -0700 Subject: [PATCH 03/88] fix: attempt to filter None in email_search --- custom_components/mail_and_packages/helpers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 374d03c8..69d0f821 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -463,6 +463,10 @@ def email_search( _LOGGER.debug("DEBUG email_search value: %s", value) + (check, new_value) = value + if new_value == "[None]": + value = (check, "[b'']") + return value From 2d14fd186dabed6455c353972ffc46099ffe3740 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 22 Oct 2021 13:18:16 -0700 Subject: [PATCH 04/88] add test and fix logic --- .../mail_and_packages/helpers.py | 4 ++-- tests/conftest.py | 22 +++++++++++++++++++ tests/test_helpers.py | 7 ++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 69d0f821..e9310942 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -464,8 +464,8 @@ def email_search( _LOGGER.debug("DEBUG email_search value: %s", value) (check, new_value) = value - if new_value == "[None]": - value = (check, "[b'']") + if new_value[0] is None: + value = (check, [b""]) return value diff --git a/tests/conftest.py b/tests/conftest.py index 296cd8db..106db047 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1055,6 +1055,28 @@ def mock_imap_amazon_exception(): yield mock_conn +@pytest.fixture() +def mock_imap_search_error_none(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_search_error_none: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_search_error_none.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [None]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + + @pytest.fixture(autouse=True) def auto_enable_custom_integrations(enable_custom_integrations): yield diff --git a/tests/test_helpers.py b/tests/test_helpers.py index afc69daa..8dc80b44 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1047,3 +1047,10 @@ async def test_get_mails_email_search_none( mock_imap_usps_informed_digest_no_mail, "./", "5", "mail_today.gif", False ) assert result == 0 + + +async def test_email_search_none(mock_imap_search_error_none, caplog): + result = email_search( + mock_imap_search_error_none, "fake@eamil.address", "01-Jan-20" + ) + assert result == ("OK", [b""]) From a659dccf5632da806f6031fa6284ddb347779416 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 27 Oct 2021 08:49:48 -0700 Subject: [PATCH 05/88] Add error message to updatecoordinator failure --- custom_components/mail_and_packages/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index a1c6b66a..bc494e79 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -227,5 +227,6 @@ async def _async_update_data(self): process_emails, self.hass, self.config ) except Exception as error: + _LOGGER.error("Problem updating sensors: %s", error) raise UpdateFailed(error) from error return data From 9c46dfe2080f416cae8cfbde5333c29394eb9d9e Mon Sep 17 00:00:00 2001 From: firstof9 Date: Mon, 1 Nov 2021 12:39:02 -0700 Subject: [PATCH 06/88] feat: add sensors into devices --- custom_components/mail_and_packages/camera.py | 12 ++++++++++ custom_components/mail_and_packages/sensor.py | 22 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/custom_components/mail_and_packages/camera.py b/custom_components/mail_and_packages/camera.py index 910ee7e3..bd3d07ee 100644 --- a/custom_components/mail_and_packages/camera.py +++ b/custom_components/mail_and_packages/camera.py @@ -21,6 +21,7 @@ COORDINATOR, DOMAIN, SENSOR_NAME, + VERSION, ) SERVICE_UPDATE_IMAGE = "update_image" @@ -168,6 +169,17 @@ async def async_on_demand_update(self): """Update state.""" self.async_schedule_update_ha_state(True) + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index a0048b16..26704dcf 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -50,6 +50,17 @@ def __init__(self, config, sensor_type, coordinator, unique_id): self._unique_id = unique_id self.data = self.coordinator.data + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(const.DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": const.VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" @@ -128,6 +139,17 @@ def __init__(self, hass, config, sensor_type, coordinator, unique_id): self._host = config.data[CONF_HOST] self._unique_id = unique_id + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(const.DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": const.VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" From 8c0847f8ab4e0aefcfc6aec5526ac9414f0ed0e8 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Mon, 1 Nov 2021 12:39:02 -0700 Subject: [PATCH 07/88] feat: add sensors into devices --- custom_components/mail_and_packages/camera.py | 12 ++++++++++ custom_components/mail_and_packages/sensor.py | 22 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/custom_components/mail_and_packages/camera.py b/custom_components/mail_and_packages/camera.py index 910ee7e3..bd3d07ee 100644 --- a/custom_components/mail_and_packages/camera.py +++ b/custom_components/mail_and_packages/camera.py @@ -21,6 +21,7 @@ COORDINATOR, DOMAIN, SENSOR_NAME, + VERSION, ) SERVICE_UPDATE_IMAGE = "update_image" @@ -168,6 +169,17 @@ async def async_on_demand_update(self): """Update state.""" self.async_schedule_update_ha_state(True) + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index a0048b16..26704dcf 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -50,6 +50,17 @@ def __init__(self, config, sensor_type, coordinator, unique_id): self._unique_id = unique_id self.data = self.coordinator.data + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(const.DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": const.VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" @@ -128,6 +139,17 @@ def __init__(self, hass, config, sensor_type, coordinator, unique_id): self._host = config.data[CONF_HOST] self._unique_id = unique_id + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(const.DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": const.VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" From 9d62c78d145f842b8360fbdbf21e2b2e786a40c9 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Mon, 1 Nov 2021 12:39:02 -0700 Subject: [PATCH 08/88] feat: add sensors into devices --- custom_components/mail_and_packages/camera.py | 12 ++++++++++ custom_components/mail_and_packages/sensor.py | 22 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/custom_components/mail_and_packages/camera.py b/custom_components/mail_and_packages/camera.py index 910ee7e3..bd3d07ee 100644 --- a/custom_components/mail_and_packages/camera.py +++ b/custom_components/mail_and_packages/camera.py @@ -21,6 +21,7 @@ COORDINATOR, DOMAIN, SENSOR_NAME, + VERSION, ) SERVICE_UPDATE_IMAGE = "update_image" @@ -168,6 +169,17 @@ async def async_on_demand_update(self): """Update state.""" self.async_schedule_update_ha_state(True) + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index a0048b16..26704dcf 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -50,6 +50,17 @@ def __init__(self, config, sensor_type, coordinator, unique_id): self._unique_id = unique_id self.data = self.coordinator.data + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(const.DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": const.VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" @@ -128,6 +139,17 @@ def __init__(self, hass, config, sensor_type, coordinator, unique_id): self._host = config.data[CONF_HOST] self._unique_id = unique_id + @property + def device_info(self) -> dict: + """Return device information about the mailbox.""" + + return { + "connections": {(const.DOMAIN, self._unique_id)}, + "name": self._host, + "manufacturer": "IMAP E-Mail", + "sw_version": const.VERSION, + } + @property def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" From 264118f5e38b7931ee185123e4855b7d171f1768 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Mon, 1 Nov 2021 14:02:04 -0700 Subject: [PATCH 09/88] refactor: utilize SensorEntityDescription --- custom_components/mail_and_packages/const.py | 353 +++++++++++------- .../mail_and_packages/helpers.py | 3 +- custom_components/mail_and_packages/sensor.py | 111 +++--- 3 files changed, 285 insertions(+), 182 deletions(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 2a11908f..e883b92a 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -1,4 +1,11 @@ """ Constants for Mail and Packages.""" +from __future__ import annotations + +from typing import Final +from homeassistant.components.sensor import SensorEntityDescription + +from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC + DOMAIN = "mail_and_packages" DOMAIN_DATA = "{}_data".format(DOMAIN) VERSION = "0.0.0-dev" # Now updated by release workflow @@ -201,139 +208,229 @@ } # Sensor definitions -# Name, unit of measure, icon -SENSOR_TYPES = { - "mail_updated": ["Mail Updated", None, "mdi:update"], - "usps_mail": ["Mail USPS Mail", "piece(s)", "mdi:mailbox-up"], - "usps_delivered": [ - "Mail USPS Delivered", - "package(s)", - "mdi:package-variant-closed", - ], - "usps_delivering": ["Mail USPS Delivering", "package(s)", "mdi:truck-delivery"], - "usps_exception": ["Mail USPS Exception", "package(s)", "mdi:archive-alert"], - "usps_packages": [ - "Mail USPS Packages", - "package(s)", - "mdi:package-variant-closed", - ], - "ups_delivered": [ - "Mail UPS Delivered", - "package(s)", - "mdi:package-variant-closed", - ], - "ups_delivering": ["Mail UPS Delivering", "package(s)", "mdi:truck-delivery"], - "ups_exception": ["Mail UPS Exception", "package(s)", "mdi:archive-alert"], - "ups_packages": ["Mail UPS Packages", "package(s)", "mdi:package-variant-closed"], - "fedex_delivered": [ - "Mail FedEx Delivered", - "package(s)", - "mdi:package-variant-closed", - ], - "fedex_delivering": ["Mail FedEx Delivering", "package(s)", "mdi:truck-delivery"], - "fedex_packages": [ - "Mail FedEx Packages", - "package(s)", - "mdi:package-variant-closed", - ], - "amazon_packages": ["Mail Amazon Packages", "package(s)", "mdi:package"], - "amazon_delivered": [ - "Mail Amazon Packages Delivered", - "package(s)", - "mdi:package-variant-closed", - ], - "amazon_exception": ["Mail Amazon Exception", "package(s)", "mdi:archive-alert"], - "amazon_hub": ["Mail Amazon Hub Packages", "package(s)", "mdi:package"], - "capost_delivered": [ - "Mail Canada Post Delivered", - "package(s)", - "mdi:package-variant-closed", - ], - "capost_delivering": [ - "Mail Canada Post Delivering", - "package(s)", - "mdi:truck-delivery", - ], - "capost_packages": [ - "Mail Canada Post Packages", - "package(s)", - "mdi:package-variant-closed", - ], - "dhl_delivered": [ - "Mail DHL Delivered", - "package(s)", - "mdi:package-variant-closed", - ], - "dhl_delivering": ["Mail DHL Delivering", "package(s)", "mdi:truck-delivery"], - "dhl_packages": ["Mail DHL Packages", "package(s)", "mdi:package-variant-closed"], - "hermes_delivered": [ - "Mail Hermes Delivered", - "package(s)", - "mdi:package-variant-closed", - ], - "hermes_delivering": ["Mail Hermes Delivering", "package(s)", "mdi:truck-delivery"], - "hermes_packages": [ - "Mail Hermes Packages", - "package(s)", - "mdi:package-variant-closed", - ], - "royal_delivered": [ - "Mail Royal Mail Delivered", - "package(s)", - "mdi:package-variant-closed", - ], - "royal_delivering": [ - "Mail Royal Mail Delivering", - "package(s)", - "mdi:truck-delivery", - ], - "royal_packages": [ - "Mail Royal Mail Packages", - "package(s)", - "mdi:package-variant-closed", - ], - "auspost_delivered": [ - "Mail AusPost Delivered", - "package(s)", - "mdi:package-variant", - ], - "auspost_delivering": [ - "Mail AusPost Delivering", - "package(s)", - "mdi:truck-delivery", - ], - "auspost_packages": [ - "Mail AusPost Packages", - "package(s)", - "mdi:package-variant-closed", - ], +SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = { + "mail_updated": SensorEntityDescription( + name="Mail Updated", + icon="mdi:update", + key="mail_updated", + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + "usps_mail": SensorEntityDescription( + name="Mail USPS Mail", + native_unit_of_measurement="piece(s)", + icon="mdi:mailbox-up", + key="usps_mail", + ), + "usps_delivered": SensorEntityDescription( + name="Mail USPS Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="usps_delivered", + ), + "usps_delivering": SensorEntityDescription( + name="Mail USPS Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="usps_delivering", + ), + "usps_exception": SensorEntityDescription( + name="Mail USPS Exception", + native_unit_of_measurement="package(s)", + icon="mdi:archive-alert", + key="usps_exception", + ), + "usps_packages": SensorEntityDescription( + name="Mail USPS Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="usps_packages", + ), + "ups_delivered": SensorEntityDescription( + name="Mail UPS Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="ups_delivered", + ), + "ups_delivering": SensorEntityDescription( + name="Mail UPS Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="ups_delivering", + ), + "ups_exception": SensorEntityDescription( + name="Mail UPS Exception", + native_unit_of_measurement="package(s)", + icon="mdi:archive-alert", + key="ups_exception", + ), + "ups_packages": SensorEntityDescription( + name="Mail UPS Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="ups_packages", + ), + "fedex_delivered": SensorEntityDescription( + name="Mail FedEx Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="fedex_delivered", + ), + "fedex_delivering": SensorEntityDescription( + name="Mail FedEx Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="fedex_delivering", + ), + "fedex_packages": SensorEntityDescription( + name="Mail FedEx Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="fedex_packages", + ), + "amazon_packages": SensorEntityDescription( + name="Mail Amazon Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package", + key="amazon_packages", + ), + "amazon_delivered": SensorEntityDescription( + name="Mail Amazon Packages Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="amazon_delivered", + ), + "amazon_exception": SensorEntityDescription( + name="Mail Amazon Exception", + native_unit_of_measurement="package(s)", + icon="mdi:archive-alert", + key="amazon_exception", + ), + "amazon_hub": SensorEntityDescription( + name="Mail Amazon Hub Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package", + key="amazon_hub", + ), + "capost_delivered": SensorEntityDescription( + name="Mail Canada Post Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="capost_delivered", + ), + "capost_delivering": SensorEntityDescription( + name="Mail Canada Post Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="capost_delivering", + ), + "capost_packages": SensorEntityDescription( + name="Mail Canada Post Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="capost_packages", + ), + "dhl_delivered": SensorEntityDescription( + name="Mail DHL Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="dhl_delivered", + ), + "dhl_delivering": SensorEntityDescription( + name="Mail DHL Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="dhl_delivering", + ), + "dhl_packages": SensorEntityDescription( + name="Mail DHL Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="dhl_packages", + ), + "hermes_delivered": SensorEntityDescription( + name="Mail Hermes Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="hermes_delivered", + ), + "hermes_delivering": SensorEntityDescription( + name="Mail Hermes Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="hermes_delivering", + ), + "hermes_packages": SensorEntityDescription( + name="Mail Hermes Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="hermes_packages", + ), + "royal_delivered": SensorEntityDescription( + name="Mail Royal Mail Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="royal_delivered", + ), + "royal_delivering": SensorEntityDescription( + name="Mail Royal Mail Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="royal_delivering", + ), + "royal_packages": SensorEntityDescription( + name="Mail Royal Mail Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="royal_packages", + ), + "auspost_delivered": SensorEntityDescription( + name="Mail AusPost Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant", + key="auspost_delivered", + ), + "auspost_delivering": SensorEntityDescription( + name="Mail AusPost Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="auspost_delivering", + ), + "auspost_packages": SensorEntityDescription( + name="Mail AusPost Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="auspost_packages", + ), ### # !!! Insert new sensors above these two !!! ### - "zpackages_delivered": [ - "Mail Packages Delivered", - "package(s)", - "mdi:package-variant", - ], - "zpackages_transit": [ - "Mail Packages In Transit", - "package(s)", - "mdi:truck-delivery", - ], + "zpackages_delivered": SensorEntityDescription( + name="Mail Packages Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant", + key="zpackages_delivered", + ), + "zpackages_transit": SensorEntityDescription( + name="Mail Packages In Transit", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="zpackages_transit", + ), } -# Name, unit of measure, icon -IMAGE_SENSORS = { - "usps_mail_image_system_path": [ - "Mail Image System Path", - None, - "mdi:folder-multiple-image", - ], - "usps_mail_image_url": [ - "Mail Image URL", - None, - "mdi:link-variant", - ], +IMAGE_SENSORS: Final[dict[str, SensorEntityDescription]] = { + "usps_mail_image_system_path": SensorEntityDescription( + name="Mail Image System Path", + icon="mdi:folder-multiple-image", + key="usps_mail_image_system_path", + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + "usps_mail_image_url": SensorEntityDescription( + name="Mail Image URL", + icon="mdi:link-variant", + key="usps_mail_image_url", + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), } # Name diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index e9310942..cc78c9f4 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -43,8 +43,7 @@ def get_resources() -> dict: """ known_available_resources = { - sensor_id: sensor[const.SENSOR_NAME] - for sensor_id, sensor in const.SENSOR_TYPES.items() + sensor_id: sensor.name for sensor_id, sensor in const.SENSOR_TYPES.items() } return known_available_resources diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index 26704dcf..f7c03fd0 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -7,27 +7,45 @@ import logging from typing import Optional -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import SensorEntity, SensorEntityDescription +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_RESOURCES +from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import const +from .const import ( + AMAZON_EXCEPTION_ORDER, + AMAZON_ORDER, + ATTR_IMAGE, + ATTR_IMAGE_NAME, + ATTR_IMAGE_PATH, + ATTR_ORDER, + ATTR_SERVER, + ATTR_TRACKING_NUM, + CONF_PATH, + COORDINATOR, + DOMAIN, + IMAGE_SENSORS, + SENSOR_TYPES, + VERSION, +) _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, entry, async_add_entities): """Set up the sensor entities.""" - coordinator = hass.data[const.DOMAIN][entry.entry_id][const.COORDINATOR] - unique_id = entry.entry_id + coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR] sensors = [] resources = entry.data[CONF_RESOURCES] for variable in resources: - sensors.append(PackagesSensor(entry, variable, coordinator, unique_id)) + sensors.append(PackagesSensor(entry, SENSOR_TYPES[variable], coordinator)) - for variable in const.IMAGE_SENSORS: - sensors.append(ImagePathSensors(hass, entry, variable, coordinator, unique_id)) + for variable in IMAGE_SENSORS: + sensors.append( + ImagePathSensors(hass, entry, IMAGE_SENSORS[variable], coordinator) + ) async_add_entities(sensors, False) @@ -35,19 +53,21 @@ async def async_setup_entry(hass, entry, async_add_entities): class PackagesSensor(CoordinatorEntity, SensorEntity): """Representation of a sensor.""" - def __init__(self, config, sensor_type, coordinator, unique_id): + def __init__( + self, + config: ConfigEntry, + sensor_description: SensorEntityDescription, + coordinator: str, + ): """Initialize the sensor""" super().__init__(coordinator) + self.entity_description = sensor_description self.coordinator = coordinator self._config = config - self._name = const.SENSOR_TYPES[sensor_type][const.SENSOR_NAME] - self._icon = const.SENSOR_TYPES[sensor_type][const.SENSOR_ICON] - self._attr_native_unit_of_measurement = const.SENSOR_TYPES[sensor_type][ - const.SENSOR_UNIT - ] - self.type = sensor_type + self._name = sensor_description.name + self.type = sensor_description.key self._host = config.data[CONF_HOST] - self._unique_id = unique_id + self._unique_id = self._config.unique_id self.data = self.coordinator.data @property @@ -55,10 +75,10 @@ def device_info(self) -> dict: """Return device information about the mailbox.""" return { - "connections": {(const.DOMAIN, self._unique_id)}, + "connections": {(DOMAIN, self._unique_id)}, "name": self._host, "manufacturer": "IMAP E-Mail", - "sw_version": const.VERSION, + "sw_version": VERSION, } @property @@ -82,11 +102,6 @@ def native_value(self) -> Optional[int]: value = None return value - @property - def icon(self) -> str: - """Return the unit of measurement.""" - return self._icon - @property def should_poll(self) -> bool: """No need to poll. Coordinator notifies entity of updates.""" @@ -101,7 +116,7 @@ def available(self) -> bool: def device_state_attributes(self) -> Optional[str]: """Return device specific state attributes.""" attr = {} - attr[const.ATTR_SERVER] = self._host + attr[ATTR_SERVER] = self._host tracking = f"{self.type.split('_')[0]}_tracking" data = self.coordinator.data @@ -111,43 +126,46 @@ def device_state_attributes(self) -> Optional[str]: if "Amazon" in self._name: if self._name == "amazon_exception": - attr[const.ATTR_ORDER] = data[const.AMAZON_EXCEPTION_ORDER] + attr[ATTR_ORDER] = data[AMAZON_EXCEPTION_ORDER] else: - attr[const.ATTR_ORDER] = data[const.AMAZON_ORDER] + attr[ATTR_ORDER] = data[AMAZON_ORDER] elif self._name == "Mail USPS Mail": - attr[const.ATTR_IMAGE] = data[const.ATTR_IMAGE_NAME] + attr[ATTR_IMAGE] = data[ATTR_IMAGE_NAME] elif "_delivering" in self.type and tracking in self.data.keys(): - attr[const.ATTR_TRACKING_NUM] = data[tracking] + attr[ATTR_TRACKING_NUM] = data[tracking] return attr class ImagePathSensors(CoordinatorEntity, SensorEntity): """Representation of a sensor.""" - def __init__(self, hass, config, sensor_type, coordinator, unique_id): + def __init__( + self, + hass: HomeAssistant, + config: ConfigEntry, + sensor_description: SensorEntityDescription, + coordinator: str, + ): """Initialize the sensor""" super().__init__(coordinator) + self.entity_description = sensor_description self.hass = hass self.coordinator = coordinator self._config = config - self._name = const.IMAGE_SENSORS[sensor_type][const.SENSOR_NAME] - self._icon = const.IMAGE_SENSORS[sensor_type][const.SENSOR_ICON] - self._attr_native_unit_of_measurement = const.IMAGE_SENSORS[sensor_type][ - const.SENSOR_UNIT - ] - self.type = sensor_type + self._name = sensor_description.name + self.type = sensor_description.key self._host = config.data[CONF_HOST] - self._unique_id = unique_id + self._unique_id = self._config.unique_id @property def device_info(self) -> dict: """Return device information about the mailbox.""" return { - "connections": {(const.DOMAIN, self._unique_id)}, + "connections": {(DOMAIN, self._unique_id)}, "name": self._host, "manufacturer": "IMAP E-Mail", - "sw_version": const.VERSION, + "sw_version": VERSION, } @property @@ -163,13 +181,13 @@ def name(self) -> str: @property def native_value(self) -> Optional[str]: """Return the state of the sensor.""" - image = self.coordinator.data[const.ATTR_IMAGE_NAME] + image = self.coordinator.data[ATTR_IMAGE_NAME] the_path = None - if const.ATTR_IMAGE_PATH in self.coordinator.data.keys(): - path = self.coordinator.data[const.ATTR_IMAGE_PATH] + if ATTR_IMAGE_PATH in self.coordinator.data.keys(): + path = self.coordinator.data[ATTR_IMAGE_PATH] else: - path = self._config.data[const.CONF_PATH] + path = self._config.data[CONF_PATH] if self.type == "usps_mail_image_system_path": _LOGGER.debug("Updating system image path to: %s", path) @@ -189,11 +207,6 @@ def native_value(self) -> Optional[str]: the_path = f"{url.rstrip('/')}/local/mail_and_packages/{image}" return the_path - @property - def icon(self) -> str: - """Return the unit of measurement.""" - return self._icon - @property def should_poll(self) -> bool: """No need to poll. Coordinator notifies entity of updates.""" @@ -203,9 +216,3 @@ def should_poll(self) -> bool: def available(self) -> bool: """Return if entity is available.""" return self.coordinator.last_update_success - - @property - def device_state_attributes(self) -> Optional[str]: - """Return device specific state attributes.""" - attr = {} - return attr From 649d2eafcb58400cb1b7443dcd7c62ee981c7fbc Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 3 Nov 2021 14:09:36 -0700 Subject: [PATCH 10/88] fix: sensors unique_id wrong --- custom_components/mail_and_packages/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index f7c03fd0..275deec3 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -67,7 +67,7 @@ def __init__( self._name = sensor_description.name self.type = sensor_description.key self._host = config.data[CONF_HOST] - self._unique_id = self._config.unique_id + self._unique_id = self._config.entry_id self.data = self.coordinator.data @property From 8ce784660e45ecd18f0b0d4fd44a09204f6d7b59 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 3 Nov 2021 14:11:35 -0700 Subject: [PATCH 11/88] also the path sensors --- custom_components/mail_and_packages/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index 275deec3..d35196d3 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -155,7 +155,7 @@ def __init__( self._name = sensor_description.name self.type = sensor_description.key self._host = config.data[CONF_HOST] - self._unique_id = self._config.unique_id + self._unique_id = self._config.entry_id @property def device_info(self) -> dict: From 7f283a3878411d74bd7181a90667a6ffc3755a96 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Mon, 15 Nov 2021 15:25:48 -0700 Subject: [PATCH 12/88] fix: Nonetype value when using group * fixes #568 --- custom_components/mail_and_packages/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index cc78c9f4..d436f9b0 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -1058,7 +1058,9 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> # Get combo number from subject line email_subject = msg["subject"] pattern = re.compile(r"{}".format(subject_regex)) - found.append(pattern.search(email_subject).group(3)) + search = pattern.search(email_subject) + if search is not None: + found.append(search.group(3)) info[const.ATTR_COUNT] = len(found) info[const.ATTR_CODE] = found From bba46e1472cc4a8cb9785c0f4e4e176d272e88ee Mon Sep 17 00:00:00 2001 From: firstof9 Date: Mon, 15 Nov 2021 15:32:13 -0700 Subject: [PATCH 13/88] formatting --- custom_components/mail_and_packages/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index e883b92a..a098b80f 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -2,8 +2,8 @@ from __future__ import annotations from typing import Final -from homeassistant.components.sensor import SensorEntityDescription +from homeassistant.components.sensor import SensorEntityDescription from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC DOMAIN = "mail_and_packages" From bac17fea42c65c17c399d269f4b84c01f0e5f8f8 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Mon, 15 Nov 2021 15:46:56 -0700 Subject: [PATCH 14/88] chore: add python 3.10 to tests --- .github/workflows/pytest.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index ae51b9f0..d7289d7f 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -11,7 +11,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9] + python-version: + - 3.8 + - 3.9 + - 3.10 steps: - uses: actions/checkout@v2 From 70539157fb1d5a9f113e8849ada9c5d02c8c19cd Mon Sep 17 00:00:00 2001 From: firstof9 Date: Mon, 15 Nov 2021 15:51:59 -0700 Subject: [PATCH 15/88] formatting --- .github/workflows/pytest.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index d7289d7f..1ed65bdc 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -12,9 +12,9 @@ jobs: strategy: matrix: python-version: - - 3.8 - - 3.9 - - 3.10 + - "3.8" + - "3.9" + - "3.10" steps: - uses: actions/checkout@v2 From 29c7066cc7d7fb4528fe2c05908f834affbb3285 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Tue, 16 Nov 2021 08:23:58 -0700 Subject: [PATCH 16/88] fix: guard against coordinator update failure in cameras --- custom_components/mail_and_packages/camera.py | 4 ++++ custom_components/mail_and_packages/helpers.py | 1 + 2 files changed, 5 insertions(+) diff --git a/custom_components/mail_and_packages/camera.py b/custom_components/mail_and_packages/camera.py index bd3d07ee..628635cc 100644 --- a/custom_components/mail_and_packages/camera.py +++ b/custom_components/mail_and_packages/camera.py @@ -134,6 +134,10 @@ def update_file_path(self) -> None: _LOGGER.debug("Camera Update: %s", self._type) _LOGGER.debug("Custom No Mail: %s", self._no_mail) + if not self._coordinator.last_update_success: + _LOGGER.warning("Update to update camera image. Unavailable.") + return + if self._coordinator.data is None: _LOGGER.warning("Unable to update camera image, no data.") return diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index d436f9b0..c0c3ffe4 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -464,6 +464,7 @@ def email_search( (check, new_value) = value if new_value[0] is None: + _LOGGER.warning("DEBUG email_search value was invalid: None") value = (check, [b""]) return value From d4484d3ae635273783732574166a34e55496a877 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Tue, 16 Nov 2021 08:27:27 -0700 Subject: [PATCH 17/88] disable python 3.10 test for now --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 1ed65bdc..299c642b 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -14,7 +14,7 @@ jobs: python-version: - "3.8" - "3.9" - - "3.10" + # - "3.10" steps: - uses: actions/checkout@v2 From 9855502507769c0dad351fc4c121e51b90a8763f Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 17 Nov 2021 09:24:24 -0700 Subject: [PATCH 18/88] fix: amazon uk email format change * fixes #575 --- .../mail_and_packages/helpers.py | 9 + tests/conftest.py | 25 + tests/test_emails/amazon_uk_shipped_2.eml | 1481 +++++++++++++++++ tests/test_helpers.py | 5 + 4 files changed, 1520 insertions(+) create mode 100644 tests/test_emails/amazon_uk_shipped_2.eml diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index c0c3ffe4..93a50027 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -1193,6 +1193,15 @@ def get_items( ) continue email_msg = email_msg.decode("utf-8", "ignore") + + # Check message body for order number + if ( + (found := pattern.findall(email_msg)) + and len(found) > 0 + and found[0] not in order_number + ): + order_number.append(found[0]) + searches = const.AMAZON_TIME_PATTERN.split(",") for search in searches: _LOGGER.debug("Looking for: %s", search) diff --git a/tests/conftest.py b/tests/conftest.py index a817281a..81dc2805 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -480,6 +480,31 @@ def mock_imap_amazon_shipped_uk(): yield mock_conn +@pytest.fixture() +def mock_imap_amazon_shipped_uk_2(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_amazon_shipped: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_amazon_shipped.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/amazon_uk_shipped_2.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + + @pytest.fixture() def mock_imap_amazon_shipped_alt(): """Mock imap class values.""" diff --git a/tests/test_emails/amazon_uk_shipped_2.eml b/tests/test_emails/amazon_uk_shipped_2.eml new file mode 100644 index 00000000..206ccc45 --- /dev/null +++ b/tests/test_emails/amazon_uk_shipped_2.eml @@ -0,0 +1,1481 @@ +From: "Amazon.co.uk" +Reply-To: no-reply@amazon.co.uk +To: testuser@hotmail.com +Subject: Your Amazon.co.uk order of "30 Ft Navy Blue Party..." has been + dispatched +Content-Type: multipart/alternative; + boundary="----=_Part_51705775_653353746.1636995720703" +Date: Mon, 15 Nov 2021 17:02:00 +0000 +MIME-Version: 1.0 + +------=_Part_51705775_653353746.1636995720703 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +Amazon.co.uk Dispatch Confirmation +Order: #123-4567890-1234567 +https://www.amazon.co.uk/ref=3DTE_tex_g + +___________________________________________________________________________ + +Hello, +We thought you'd like to know that we've dispatched this portion of your or= +der separately to give you a quicker service, at no additional cost to you.= + The remainder of your order will follow as soon as those items become avai= +lable. + + +Arriving: +Tuesday, November 16 + + +Track your item(s): https://www.amazon.co.uk/gp/css/shiptrack/view.html/ref= +=3DTE_typ?ie=3DUTF8&addressID=3Djolpmtkpkplq&latestArrivalDate=3D1637092800= +&orderID=REMOVE_FOR_SECURITY&shipmentDate=3D1636995337&orderingShipmentId= +=REMOVED_FOR_SECURITY&packageId=3D1 + + +Your Amazon Locker order has been sent to the chosen pick-up location: + +Amazon Hub Locker - REMOVED_FOR_SECURITY + + +Order Total: =C2=A311.99 +Paid by Visa: =C2=A311.99 + +Your item(s) will be delivered to the locker location that you have selecte= +d.Your tracking number is: CHANGED_FOR_SECURITY. Depending on the delivery method y= +ou chose, it's possible that the tracking information might not be visible = +immediately. Once your order arrives at the Locker, you'll receive an email= + with instructions on how to collect it. + +Learn more: https://www.amazon.co.uk/locker?ref=3DTE_tex_loc + + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= +=3D + +Order summary +1 X 30 Ft Navy Blue Party Decorations Glitter Metallic Paper Royal Blue Tri= +angle Banner Flag Garland Pennant Bunting for Birthday Baby Shower Graduati= +on A +Sold by MZY Co., Ltd +=C2=A311.99 +___________________________________________________________________________ +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= +=3D + +It's easy to return an item. Visit our Online Returns Centre:=20 +https://www.amazon.co.uk/returns-support?ref=3DTE_tex_r + +Learn how to recycle your packaging at Amazon Second Chance(https://www.ama= +zon.co.uk/amsc?ref_=3Dascyorn). +If you need further assistance with your order, please visit our Customer S= +ervice Help page at: https://www.amazon.co.uk/customerservice?ref=3DTE_tex_= +cs=20 + +We hope to see you again soon. +Amazon.co.uk +___________________________________________________________________________ + +You can cancel this order within 14 days, beginning from the day you receiv= +e the product (subject to certain exceptions: https://www.amazon.co.uk/gp/h= +elp/customer/display/detail.html?nodeId=3D1040616). We will reimburse all p= +ayments received from you for the goods purchased and will also reimburse o= +utbound delivery charges (for the least expensive type of delivery offered = +by us). You will be responsible for the cost of returning the product to us= + unless we delivered it to you in error, it is faulty, or you purchased sho= +es, clothing and accessories (check our Returns Policy: https://www.amazon.= +co.uk/gp/help/customer/display/detail.html?nodeId=3D1161002).You may be sub= +ject to increased return costs if the product can=E2=80=99t be returned nor= +mally by post.=20 + +You can request a cancellation by visiting our Returns Support Centre: http= +s://www.amazon.co.uk/returns; by contacting us: www.amazon.co.uk/contactus;= + or completing this form: https://www.amazon.co.uk/cancellationform and sen= +ding it by post.=20 + +Please also see our Returns Policy: https://www.amazon.co.uk/gp/help/custom= +er/display/detail.html?nodeId=3D1161002 to learn more about our 30 day retu= +rns guarantee which outlines that you can return items for a full refund of= + the item price within 30 days. + +Amazon EU, Soci=C3=A9t=C3=A9 =C3=A0 responsabilit=C3=A9 limit=C3=A9e, 38 av= +enue John F. Kennedy, L-1855 Luxembourg. Share capital: EUR 125.000; Regist= +ered in Luxembourg; RCS Luxembourg No: B 101818; Business Licence Number: 1= +34248; Luxembourg VAT Registration Number: LU 20260743. + +Learn more about your statutory rights here: https://www.amazon.co.uk/Statu= +toryRights?ref=3DTE_tex_sr. + +Please note: This email was sent from a notification-only address that can'= +t accept incoming email. Please do not reply to this message.=20 +------=_Part_51705775_653353746.1636995720703 +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + + +=20 + =20 + =20 + =20 + =20 + =20 + =20 + + =20 + =20 + =20 + +
=20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
=20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
3D"Amazon.co.uk"=20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 +
Your Orders   |   Your Account   |   Amazon.co.uk

Dispatch Confirmation

Order: #026-44808= +09-9307517
=20 + =20 + + =20 + =20 + =20 + +
Hello,

We thought you'd= + like to know that we've dispatched this portion of your order separately t= +o give you a quicker service, at no additional cost to you. The remainder o= +f your order will follow as soon as those items become available. If you ne= +ed to return an item or manage other orders, please visit Your Orders = +on Amazon.co.uk

=20 + =20 + + =20 + =20 + =20 + =20 + +

Arriving:
Tuesday, November 16

=20 + =20 + + =20 + = +=20 + =20 + +
Track your package
Your order was sent to:

Ama= +zon Hub Locker - GOLD
=20 + + VILLAGE=20 +

=20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 +
Order Total: =C2=A311.99
Paid by Visa: =C2=A311.99
=20 + =20 + =20 + =20 + + =20 + =20 + =20 + =20 + +
=20 + =20 + 3D""=20 + =20 + =20 + =20 + + =20 + =20 + =20 + +
=20 + =20 + Just ask: "= +Alexa, where's my stuff?"=20 + =20 +
=20 +
=20 +
=20 + =20 + + =20 + =20 + =20 + +

Your tracking number is: REMOVED_FOR_SECURITY. Once your orde= +r arrives at the Locker, you'll receive an email with instructions on how t= +o collect it. Learn more.

If you have a smartphone, you can use the Amazon Shopping App to receive delivery notifications and track your = +order on the go.

=20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
Order summary=20 + + + + + + +
=20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +

+

=C2=A311.99
=20 + =20 +
=20 + + + + + + +
=20 + =20 + + =20 + =20 + +
It's easy to return an item. Visit our Online Returns Centre.
Le= +arn how to recycle your packaging at Amazon Second Chance.
If you need further assistance wi= +th your order, please visit Customer Service.


We hope to see you again soon.
= + Amazon.co.uk

=20 +
=20 + =20 + + =20 + =20 + =20 + +
+ =20 +
+ = +=20 + =20 + =20 + + + + + + + + + + +
+ + + + + + +
Recommendations for all= + products:
+ + + + + + + +
+ + + + + + + + + +
+=3D"Show
+ + + + + + + + + +

Show Mode Charging Dock for Fire HD...

=C2=A349.99
+ + + + + + + + + +
+=3D"kenable
+ + + + + + + + + +

kenable PW01906 5.5 x 2.1mm DC Power...<= +/p>

=C2=A34.49
+ = +=20 + =20 + =20 + =20 +
=20 + =20 + + =20 + =20 + =20 + +

You can cancel this order within 14 day= +s, beginning from the day you receive the product (subject to certain exceptions). We will reimburse all payments= + received from you for the goods purchased and will also reimburse outbound= + delivery charges (for the least expensive type of delivery offered by us).= + You will be responsible for the cost of returning the product to us unless= + we delivered it to you in error, it is faulty, or you purchased shoes, clo= +thing and accessories (check our Returns= + Policy). You may be subject to increased return costs if the product c= +an=E2=80=99t be returned normally by post.

You can request a cancell= +ation by visiting our Returns Support Centre; by contacti= +ng us; or completing this = +form and sending it by post.

Please also see our Returns Policy to learn more about our 30 day returns= + guarantee which outlines that you can return items for a full refund of th= +e item price within 30 days.

Amazon EU, Soci=C3=A9t=C3=A9 =C3=A0 res= +ponsabilit=C3=A9 limit=C3=A9e, 38 avenue John F. Kennedy, L-1855 Luxembourg= +. Share capital: EUR 125.000; Registered in Luxembourg; RCS Luxembourg No: = +B 101818; Business Licence Number: 134248; Luxembourg VAT Registration Numb= +er: LU 20260743.

Learn more about your statutory rights here.

Please note: This email was sent from a n= +otification-only address that can't accept incoming email. Please do not re= +ply to this message.

=20 + += + +------=_Part_51705775_653353746.1636995720703-- \ No newline at end of file diff --git a/tests/test_helpers.py b/tests/test_helpers.py index ad543086..b32fb001 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -770,6 +770,11 @@ async def test_amazon_shipped_order_uk(hass, mock_imap_amazon_shipped_uk): assert result == ["123-4567890-1234567"] +async def test_amazon_shipped_order_uk(hass, mock_imap_amazon_shipped_uk_2): + result = get_items(mock_imap_amazon_shipped_uk_2, "order") + assert result == ["123-4567890-1234567"] + + async def test_amazon_shipped_order_it(hass, mock_imap_amazon_shipped_it): result = get_items(mock_imap_amazon_shipped_it, "order") assert result == ["405-5236882-9395563"] From a2c74d6f78206cbe7d0dea811a0dfb0f988fcfbe Mon Sep 17 00:00:00 2001 From: firstof9 Date: Tue, 23 Nov 2021 13:30:47 -0700 Subject: [PATCH 19/88] Update wiki-debug.png --- docs/wiki-debug.png | Bin 27218 -> 28608 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/wiki-debug.png b/docs/wiki-debug.png index c4e88b7e7e97c8cde6f2a669444957d76bc2556b..a9ae0ee5551f610f9c5a74d86ef81008b30c2870 100644 GIT binary patch literal 28608 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 H`20TrK~NLS From 9796a27260eb2f2b01967caa7e597ddfd0be8487 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Tue, 23 Nov 2021 13:49:46 -0700 Subject: [PATCH 20/88] chore: adjust release drafter config --- .github/release-drafter.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index eb5a55a4..aee3448e 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -39,6 +39,9 @@ categories: - title: ":mortar_board: Code Quality :mortar_board:" labels: - "code-quality" + - title: ":books: Documentation :books:" + labels: + - "documentation" template: | [![Downloads for this release](https://img.shields.io/github/downloads/moralmunky/Home-Assistant-Mail-And-Packages/$RESOLVED_VERSION/total.svg)](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/releases/$RESOLVED_VERSION) From f33d643354c7a30618da6b95d0d7a60fbc93598a Mon Sep 17 00:00:00 2001 From: firstof9 Date: Thu, 25 Nov 2021 09:49:45 -0700 Subject: [PATCH 21/88] fix: add support for new Amazon Hub email format * fixes #580 --- custom_components/mail_and_packages/const.py | 6 +- .../mail_and_packages/helpers.py | 75 ++++++++++++------- tests/conftest.py | 25 +++++++ tests/test_emails/amazon_hub_notice_2.eml | 62 +++++++++++++++ tests/test_helpers.py | 20 +++++ 5 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 tests/test_emails/amazon_hub_notice_2.eml diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index a098b80f..ff84ae93 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -78,8 +78,10 @@ ) AMAZON_HUB = "amazon_hub" AMAZON_HUB_CODE = "amazon_hub_code" -AMAZON_HUB_EMAIL = "thehub@amazon.com" -AMAZON_HUB_SUBJECT = "(You have a package to pick up)(.*)- (\\d{6})" +AMAZON_HUB_EMAIL = ["thehub@amazon.com", "order-update@amazon.com"] +AMAZON_HUB_SUBJECT = "ready for pickup from Amazon Hub Locker" +AMAZON_HUB_SUBJECT_SEARCH = "(You have a package to pick up)(.*)(\\d{6})" +AMAZON_HUB_BODY = "(Your pickup code is )(\\d{6})" AMAZON_TIME_PATTERN = "will arrive:,estimated delivery date is:,guaranteed delivery date is:,Arriving:,Arriverà:" AMAZON_EXCEPTION_SUBJECT = "Delivery update:" AMAZON_EXCEPTION_BODY = "running late" diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 93a50027..e9ff141f 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -1028,40 +1028,59 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> Returns dict of sensor data """ - email_address = _process_amazon_forwards(fwds) - subject_regex = const.AMAZON_HUB_SUBJECT + email_addresses = _process_amazon_forwards(fwds) + body_regex = const.AMAZON_HUB_BODY + subject_regex = const.AMAZON_HUB_SUBJECT_SEARCH info = {} today = get_formatted_date() - email_address.append(const.AMAZON_HUB_EMAIL) - _LOGGER.debug("[Hub] Amazon email list: %s", str(email_address)) + email_addresses.extend(const.AMAZON_HUB_EMAIL) + _LOGGER.debug("[Hub] Amazon email list: %s", str(email_addresses)) - (server_response, sdata) = email_search(account, email_address, today) - - # Bail out on error - if server_response != "OK" or sdata[0] is None: - return info - - if len(sdata) == 0: - info[const.ATTR_COUNT] = 0 - info[const.ATTR_CODE] = [] - return info + for address in email_addresses: + (server_response, sdata) = email_search( + account, address, today, subject=const.AMAZON_HUB_SUBJECT + ) - found = [] - id_list = sdata[0].split() - _LOGGER.debug("Amazon hub emails found: %s", str(len(id_list))) - for i in id_list: - data = email_fetch(account, i, "(RFC822)")[1] - for response_part in data: - if isinstance(response_part, tuple): - msg = email.message_from_bytes(response_part[1]) + # Bail out on error + if server_response != "OK" or sdata[0] is None: + return info + + if len(sdata) == 0: + info[const.ATTR_COUNT] = 0 + info[const.ATTR_CODE] = [] + return info + + found = [] + id_list = sdata[0].split() + _LOGGER.debug("Amazon hub emails found: %s", str(len(id_list))) + for i in id_list: + data = email_fetch(account, i, "(RFC822)")[1] + for response_part in data: + if isinstance(response_part, tuple): + msg = email.message_from_bytes(response_part[1]) + + # Get combo number from subject line + email_subject = msg["subject"] + pattern = re.compile(r"{}".format(subject_regex)) + search = pattern.search(email_subject) + if search is not None: + if len(search.groups()) > 1: + found.append(search.group(3)) + continue - # Get combo number from subject line - email_subject = msg["subject"] - pattern = re.compile(r"{}".format(subject_regex)) - search = pattern.search(email_subject) - if search is not None: - found.append(search.group(3)) + # Get combo number from message body + try: + email_msg = quopri.decodestring(str(msg.get_payload(0))) + except Exception as err: + _LOGGER.debug("Problem decoding email message: %s", str(err)) + continue + email_msg = email_msg.decode("utf-8", "ignore") + pattern = re.compile(r"{}".format(body_regex)) + search = pattern.search(email_msg) + if search is not None: + if len(search.groups()) > 1: + found.append(search.group(2)) info[const.ATTR_COUNT] = len(found) info[const.ATTR_CODE] = found diff --git a/tests/conftest.py b/tests/conftest.py index 81dc2805..aa450a36 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -655,6 +655,31 @@ def mock_imap_amazon_the_hub(): yield mock_conn +@pytest.fixture() +def mock_imap_amazon_the_hub_2(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_amazon_the_hub: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_amazon_the_hub.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/amazon_hub_notice_2.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + + @pytest.fixture def test_valid_ffmpeg(): """Fixture to mock which""" diff --git a/tests/test_emails/amazon_hub_notice_2.eml b/tests/test_emails/amazon_hub_notice_2.eml new file mode 100644 index 00000000..364a2f13 --- /dev/null +++ b/tests/test_emails/amazon_hub_notice_2.eml @@ -0,0 +1,62 @@ +From: "Amazon.com" +Reply-To: no-reply@amazon.com +To: testuser@gmail.com +Subject: Your package is ready for pickup from Amazon Hub Locker - Location; + please pick up until Thursday, November 25 +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_36839221_2114339660.1637602534811" +Date: Mon, 22 Nov 2021 17:35:34 +0000 + +------=_Part_36839221_2114339660.1637602534811 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: 7bit + +Your package is ready for pickup from Amazon Hub Locker - Position; please pick up until Thursday, November 25 + + +____________________________________________________________________ + + + + + + Hi TestUser, + + Your package with 1 item is ready to be picked up from Amazon Hub Locker - Position. + + http://g-ecx.images-amazon.com/images/G/01/barcodes/SCLZZZZZZZ_.jpg\nUse this code to pick up your package + + Your pickup code is 123456 + + COVID Instructions + + PICK UP UNTIL: + Thursday, November 25 + If you can't pick up your package, it will be returned for a full refund. + + PICKUP LOCATION: + Amazon Hub Locker - Location + 1234 Fizbee Rd + at 7-Eleven + + + City, + IL + 60181-3531 + + + You will find the Locker outside the store.

Available hours:Monday - Sunday 00:00 - 23:59
(Store hours may vary during the holiday period)
+ + + Return or re-order items in Your Orders + + We'd like to hear from you!
Please tell us about your experience + + +____________________________________________________________________ + +Unless otherwise noted, items sold by Amazon.com LLC are subject to sales tax in select states in accordance with the applicable laws of that state. If your order contains one or more items from a seller other than Amazon.com LLC, it may be subject to state and local sales tax, depending upon the sellers business policies and the location of their operations. Learn more about tax and seller information: https://www.amazon.com/gp/help/customer/display.html?ie=UTF8&nodeId=202029700&ref_=hedwig_ppp_received_email_tax +Your invoice can be accessed here: https://www.amazon.com/gp/css/summary/print.html?ie=UTF8&orderID=111-4396615-4512227&ref_=hedwig_ppp_received_email_invoice +This notification was sent from a notification-only address that cannot accept incoming email. Please do not reply to this message. +------=_Part_36839221_2114339660.1637602534811-- diff --git a/tests/test_helpers.py b/tests/test_helpers.py index b32fb001..5938ef2a 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -838,6 +838,26 @@ async def test_amazon_hub(hass, mock_imap_amazon_the_hub): assert result == {} +async def test_amazon_hub_2(hass, mock_imap_amazon_the_hub_2): + result = amazon_hub(mock_imap_amazon_the_hub_2) + assert result["count"] == 1 + assert result["code"] == ["123456"] + + with patch( + "custom_components.mail_and_packages.helpers.email_search", + return_value=("BAD", []), + ): + result = amazon_hub(mock_imap_amazon_the_hub_2) + assert result == {} + + with patch( + "custom_components.mail_and_packages.helpers.email_search", + return_value=("OK", [None]), + ): + result = amazon_hub(mock_imap_amazon_the_hub_2) + assert result == {} + + async def test_amazon_shipped_order_exception(hass, mock_imap_amazon_shipped, caplog): with patch("quopri.decodestring", side_effect=ValueError): get_items(mock_imap_amazon_shipped, "order") From 27259732f2dfbab9b5369774bc50caf4d3012035 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 1 Dec 2021 12:08:26 -0700 Subject: [PATCH 22/88] fix: update fedex subject search *fixes #587 --- custom_components/mail_and_packages/const.py | 1 + tests/conftest.py | 24 + .../test_emails/fedex_out_for_delivery_2.eml | 826 ++++++++++++++++++ tests/test_helpers.py | 8 + 4 files changed, 859 insertions(+) create mode 100644 tests/test_emails/fedex_out_for_delivery_2.eml diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index ff84ae93..e751ba4e 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -147,6 +147,7 @@ "Delivery scheduled for today", "Your package is scheduled for delivery today", "Your package is now out for delivery", + "out for delivery today", ], }, "fedex_packages": {}, diff --git a/tests/conftest.py b/tests/conftest.py index aa450a36..94ee2045 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -404,6 +404,30 @@ def mock_imap_fedex_out_for_delivery(): mock_conn.select.return_value = ("OK", []) yield mock_conn +@pytest.fixture() +def mock_imap_fedex_out_for_delivery_2(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_dhl_out_for_delivery: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_dhl_out_for_delivery.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/fedex_out_for_delivery_2.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + @pytest.fixture() def mock_imap_usps_out_for_delivery(): diff --git a/tests/test_emails/fedex_out_for_delivery_2.eml b/tests/test_emails/fedex_out_for_delivery_2.eml new file mode 100644 index 00000000..22415bf7 --- /dev/null +++ b/tests/test_emails/fedex_out_for_delivery_2.eml @@ -0,0 +1,826 @@ +Date: Wed, 01 Dec 2021 04:07:34 -0600 +From: FedEx Delivery Manager +Reply-To: trackingmail@fedex.com +To: email@gmail.com +Subject: FedEx Shipment 286548999999: Your packages are now out for delivery + today +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_Part_6974041_1805770234.1638353157452" +------=_Part_6974041_1805770234.1638353157452 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: 8bit + + + + + + +FedEx + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + +
+ +
+ +
+
+ + + + + + + + + + +
+ +
+ + + + + + +
+spacer.gif +Hi, My Name. Your packages from Express Water Inc. are now out for delivery today. +spacer.gif +
+
+ +
+ + + + +
+ +
+ + + + + + +
+ + + + + + +
+ + + + + + + +
SCHEDULED DELIVERY
Pending
+
+
+ +
+ + + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
TRACKING IDSTATUS
+ +
+286548999999 + + + + + + +
+ + + + + + + + + +
On FedEx vehicle for delivery
PALMETTO, + FL
+
+
+ +
286548999979 + + + + + + +
+ + + + + + + + + +
On FedEx vehicle for delivery
PALMETTO, + FL
+
+
+ +
+
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MASTER TRACKING NUMBER + +286548999989 +
+ +
FROM + +Express Water Inc. +
12730 Raymer St +
Unit 1 +
North Hollywood, CA, US, 91605
+ +
TO + +My Name +
My Name +
99999 STREET RD +
CITY, STATE, ZIP
+ +
PURCHASE ORDER NUMBER + +8119
+ +
REFERENCE + +113-2208711-1430629
+ +
SHIP DATE + +Wed 11/24/2021 12:00 AM
+ +
PACKAGING TYPE + +Package
+ +
ORIGIN + +North Hollywood, CA, US, 91605
+ +
DESTINATION + +RIVERVIEW, FL, US, 33569
+ +
STANDARD TRANSIT + +Wed, 12/01/2021
+ +
NUMBER OF PIECES + +2
+ +
TOTAL SHIPMENT WEIGHT + +24.00 LB
+ +
SERVICE TYPE + +FedEx Home Delivery
+ +
+
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + + + +
+ + + + +
+FedEx +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
Ask us to hold a package
+ +
Simplify your holiday errands. Pick up holiday packages at convenient FedEx Office®, Walgreens, Dollar General, and other locations—it’s easy and it’s free.
+ +
+ + + + + + + + + + +
+ +
+ +GET THE DETAILS + +
+ +
+
+
+ +
+
+
+ + + + +
+ +
+ + + + +
+ +
+ + + + + + +
+ + + + + + +
+ +
+ + + + + + + +
+ +
+ + + + + + + + + + +
+ +
FOLLOW FEDEX
+ +
+ + + + +
+ +
+ + + + + + + + + + +
+ +Facebook + + + +Twitter + + + +Instagram + + + +LinkedIn + + + +Pinterest + + + +YouTube + + + +Google Plus + +
+
+ + + + + + + +
+ +
+email +   + + Please do not respond to this message. This email was sent from an unattended mailbox. This report was generated at approximately 4:05 AM CST 12/01/2021.
+
All weights are estimated. +
+
To track the latest status of your shipment, click on the tracking number above. +
+
Standard transit is the date and time the package is scheduled to be delivered by, based on the selected service, destination and ship date. Limitations and exceptions may apply. Please see the FedEx Service Guide for terms and conditions of service, including the FedEx Money-Back Guarantee, or contact your FedEx Customer Support representative. +
+
© 2021 Federal Express Corporation. The content of this message is protected by copyright and trademark laws under U.S. and international law. Review our privacy policy. Find information on fraud and security. All rights reserved. +
+
Thank you for your business. +
+
You can unsubscribe or update your email profile at any time.
+
+ +
+
+ + + +------=_Part_6974041_1805770234.1638353157452-- + + diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 5938ef2a..f1c8102d 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1058,6 +1058,14 @@ async def test_fedex_out_for_delivery(hass, mock_imap_fedex_out_for_delivery): assert result["tracking"] == ["61290912345678912345"] +async def test_fedex_out_for_delivery_2(hass, mock_imap_fedex_out_for_delivery_2): + result = get_count( + mock_imap_fedex_out_for_delivery_2, "fedex_delivering", True, "./", hass + ) + assert result["count"] == 1 + assert result["tracking"] == ["286548999999"] + + async def test_get_mails_email_search_none( mock_imap_usps_informed_digest_no_mail, mock_copyoverlays, From deec8a0051b59ed8ea3c64c34676b7d5417cf80c Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 1 Dec 2021 12:29:50 -0700 Subject: [PATCH 23/88] fix: add fedex delivered subject * related to #587 --- custom_components/mail_and_packages/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index e751ba4e..27629a53 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -139,6 +139,7 @@ "email": ["TrackingUpdates@fedex.com", "fedexcanada@fedex.com"], "subject": [ "Your package has been delivered", + "Your packages have been delivered", ], }, "fedex_delivering": { From d2a40684386ccfe64f740f2dd21a9541e386476e Mon Sep 17 00:00:00 2001 From: firstof9 Date: Wed, 1 Dec 2021 13:29:24 -0700 Subject: [PATCH 24/88] fix: update UPS delivering subject related to #592 --- custom_components/mail_and_packages/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 27629a53..f1a87a9b 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -127,6 +127,7 @@ "subject": [ "UPS Update: Package Scheduled for Delivery Today", "UPS Update: Follow Your Delivery on a Live Map", + "UPS Pre-Arrival: Your Driver is Arriving Soon! Follow on a Live Map", ], }, "ups_exception": { From 1368d92b5ad62d79e5158725ff63c1d96242a284 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 3 Dec 2021 09:44:54 -0700 Subject: [PATCH 25/88] refactor: adjust const import --- .../mail_and_packages/helpers.py | 205 +++++++++++------- 1 file changed, 121 insertions(+), 84 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index e9ff141f..014c504b 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -17,6 +17,52 @@ import aiohttp import imageio as io +from const import ( + AMAZON_DELIVERED, + AMAZON_DELIVERED_SUBJECT, + AMAZON_DOMAINS, + AMAZON_EMAIL, + AMAZON_EXCEPTION, + AMAZON_EXCEPTION_ORDER, + AMAZON_EXCEPTION_SUBJECT, + AMAZON_HUB, + AMAZON_HUB_BODY, + AMAZON_HUB_CODE, + AMAZON_HUB_EMAIL, + AMAZON_HUB_SUBJECT, + AMAZON_HUB_SUBJECT_SEARCH, + AMAZON_IMG_PATTERN, + AMAZON_LANGS, + AMAZON_ORDER, + AMAZON_PACKAGES, + AMAZON_PATTERN, + AMAZON_SHIPMENT_TRACKING, + AMAZON_TIME_PATTERN, + ATTR_AMAZON_IMAGE, + ATTR_BODY, + ATTR_CODE, + ATTR_COUNT, + ATTR_EMAIL, + ATTR_IMAGE_NAME, + ATTR_IMAGE_PATH, + ATTR_ORDER, + ATTR_PATTERN, + ATTR_SUBJECT, + ATTR_TRACKING, + ATTR_USPS_MAIL, + CONF_ALLOW_EXTERNAL, + CONF_AMAZON_FWDS, + CONF_CUSTOM_IMG, + CONF_CUSTOM_IMG_FILE, + CONF_DURATION, + CONF_FOLDER, + CONF_GENERATE_MP4, + CONF_PATH, + OVERLAY, + SENSOR_DATA, + SENSOR_TYPES, + SHIPPERS, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, @@ -29,8 +75,6 @@ from PIL import Image from resizeimage import resizeimage -from . import const - _LOGGER = logging.getLogger(__name__) # Config Flow Helpers @@ -43,7 +87,7 @@ def get_resources() -> dict: """ known_available_resources = { - sensor_id: sensor.name for sensor_id, sensor in const.SENSOR_TYPES.items() + sensor_id: sensor.name for sensor_id, sensor in SENSOR_TYPES.items() } return known_available_resources @@ -99,7 +143,7 @@ def process_emails(hass: HomeAssistant, config: ConfigEntry) -> dict: port = config.get(CONF_PORT) user = config.get(CONF_USERNAME) pwd = config.get(CONF_PASSWORD) - folder = config.get(const.CONF_FOLDER) + folder = config.get(CONF_FOLDER) resources = config.get(CONF_RESOURCES) # Create the dict container @@ -122,16 +166,16 @@ def process_emails(hass: HomeAssistant, config: ConfigEntry) -> dict: # USPS Mail Image name image_name = image_file_name(hass, config) _LOGGER.debug("Image name: %s", image_name) - _image[const.ATTR_IMAGE_NAME] = image_name + _image[ATTR_IMAGE_NAME] = image_name # Amazon delivery image name image_name = image_file_name(hass, config, True) _LOGGER.debug("Amazon Image Name: %s", image_name) - _image[const.ATTR_AMAZON_IMAGE] = image_name + _image[ATTR_AMAZON_IMAGE] = image_name - image_path = config.get(const.CONF_PATH) + image_path = config.get(CONF_PATH) _LOGGER.debug("Image path: %s", image_path) - _image[const.ATTR_IMAGE_PATH] = image_path + _image[ATTR_IMAGE_PATH] = image_path data.update(_image) # Only update sensors we're intrested in @@ -139,7 +183,7 @@ def process_emails(hass: HomeAssistant, config: ConfigEntry) -> dict: fetch(hass, config, account, data, sensor) # Copy image file to www directory if enabled - if config.get(const.CONF_ALLOW_EXTERNAL): + if config.get(CONF_ALLOW_EXTERNAL): copy_images(hass, config) return data @@ -148,7 +192,7 @@ def process_emails(hass: HomeAssistant, config: ConfigEntry) -> dict: def copy_images(hass: HomeAssistant, config: ConfigEntry) -> None: """Copy images to www directory if enabled.""" paths = [] - src = f"{hass.config.path()}/{config.get(const.CONF_PATH)}" + src = f"{hass.config.path()}/{config.get(CONF_PATH)}" dst = f"{hass.config.path()}/www/mail_and_packages/" # Setup paths list @@ -190,11 +234,11 @@ def image_file_name( if amazon: mail_none = f"{os.path.dirname(__file__)}/no_deliveries.jpg" image_name = "no_deliveries.jpg" - path = f"{hass.config.path()}/{config.get(const.CONF_PATH)}amazon" + path = f"{hass.config.path()}/{config.get(CONF_PATH)}amazon" else: - path = f"{hass.config.path()}/{config.get(const.CONF_PATH)}" - if config.get(const.CONF_CUSTOM_IMG): - mail_none = config.get(const.CONF_CUSTOM_IMG_FILE) + path = f"{hass.config.path()}/{config.get(CONF_PATH)}" + if config.get(CONF_CUSTOM_IMG): + mail_none = config.get(CONF_CUSTOM_IMG_FILE) else: mail_none = f"{os.path.dirname(__file__)}/mail_none.gif" not_used, image_name = os.path.split(mail_none) @@ -282,15 +326,15 @@ def fetch( Returns integer of sensor passed to it """ - img_out_path = f"{hass.config.path()}/{config.get(const.CONF_PATH)}" - gif_duration = config.get(const.CONF_DURATION) - generate_mp4 = config.get(const.CONF_GENERATE_MP4) - amazon_fwds = config.get(const.CONF_AMAZON_FWDS) - image_name = data[const.ATTR_IMAGE_NAME] - amazon_image_name = data[const.ATTR_AMAZON_IMAGE] + img_out_path = f"{hass.config.path()}/{config.get(CONF_PATH)}" + gif_duration = config.get(CONF_DURATION) + generate_mp4 = config.get(CONF_GENERATE_MP4) + amazon_fwds = config.get(CONF_AMAZON_FWDS) + image_name = data[ATTR_IMAGE_NAME] + amazon_image_name = data[ATTR_AMAZON_IMAGE] - if config.get(const.CONF_CUSTOM_IMG): - nomail = config.get(const.CONF_CUSTOM_IMG_FILE) + if config.get(CONF_CUSTOM_IMG): + nomail = config.get(CONF_CUSTOM_IMG_FILE) else: nomail = None @@ -308,17 +352,17 @@ def fetch( generate_mp4, nomail, ) - elif sensor == const.AMAZON_PACKAGES: - count[sensor] = get_items(account, const.ATTR_COUNT, amazon_fwds) - count[const.AMAZON_ORDER] = get_items(account, const.ATTR_ORDER) - elif sensor == const.AMAZON_HUB: + elif sensor == AMAZON_PACKAGES: + count[sensor] = get_items(account, ATTR_COUNT, amazon_fwds) + count[AMAZON_ORDER] = get_items(account, ATTR_ORDER) + elif sensor == AMAZON_HUB: value = amazon_hub(account, amazon_fwds) - count[sensor] = value[const.ATTR_COUNT] - count[const.AMAZON_HUB_CODE] = value[const.ATTR_CODE] - elif sensor == const.AMAZON_EXCEPTION: + count[sensor] = value[ATTR_COUNT] + count[AMAZON_HUB_CODE] = value[ATTR_CODE] + elif sensor == AMAZON_EXCEPTION: info = amazon_exception(account, amazon_fwds) - count[sensor] = info[const.ATTR_COUNT] - count[const.AMAZON_EXCEPTION_ORDER] = info[const.ATTR_ORDER] + count[sensor] = info[ATTR_COUNT] + count[AMAZON_EXCEPTION_ORDER] = info[ATTR_ORDER] elif "_packages" in sensor: prefix = sensor.split("_")[0] delivering = fetch(hass, config, account, data, f"{prefix}_delivering") @@ -328,17 +372,17 @@ def fetch( prefix = sensor.split("_")[0] delivered = fetch(hass, config, account, data, f"{prefix}_delivered") info = get_count(account, sensor, True) - count[sensor] = max(0, info[const.ATTR_COUNT] - delivered) - count[f"{prefix}_tracking"] = info[const.ATTR_TRACKING] + count[sensor] = max(0, info[ATTR_COUNT] - delivered) + count[f"{prefix}_tracking"] = info[ATTR_TRACKING] elif sensor == "zpackages_delivered": count[sensor] = 0 # initialize the variable - for shipper in const.SHIPPERS: + for shipper in SHIPPERS: delivered = f"{shipper}_delivered" if delivered in data and delivered != sensor: count[sensor] += fetch(hass, config, account, data, delivered) elif sensor == "zpackages_transit": total = 0 - for shipper in const.SHIPPERS: + for shipper in SHIPPERS: delivering = f"{shipper}_delivering" if delivering in data and delivering != sensor: total += fetch(hass, config, account, data, delivering) @@ -348,7 +392,7 @@ def fetch( else: count[sensor] = get_count( account, sensor, False, img_out_path, hass, amazon_image_name - )[const.ATTR_COUNT] + )[ATTR_COUNT] data.update(count) _LOGGER.debug("Sensor: %s Count: %s", sensor, str(count[sensor])) @@ -505,9 +549,9 @@ def get_mails( (server_response, data) = email_search( account, - const.SENSOR_DATA[const.ATTR_USPS_MAIL][const.ATTR_EMAIL], + SENSOR_DATA[ATTR_USPS_MAIL][ATTR_EMAIL], get_formatted_date(), - const.SENSOR_DATA[const.ATTR_USPS_MAIL][const.ATTR_SUBJECT][0], + SENSOR_DATA[ATTR_USPS_MAIL][ATTR_SUBJECT][0], ) # Bail out on error @@ -699,7 +743,7 @@ def resize_images(images: list, width: int, height: int) -> list: def copy_overlays(path: str) -> None: """Copy overlay images to image output path.""" - overlays = const.OVERLAY + overlays = OVERLAY check = all(item in overlays for item in os.listdir(path)) # Copy files if they are missing @@ -755,56 +799,49 @@ def get_count( found = [] # Return Amazon delivered info - if sensor_type == const.AMAZON_DELIVERED: - result[const.ATTR_COUNT] = amazon_search( - account, image_path, hass, amazon_image_name - ) - result[const.ATTR_TRACKING] = "" + if sensor_type == AMAZON_DELIVERED: + result[ATTR_COUNT] = amazon_search(account, image_path, hass, amazon_image_name) + result[ATTR_TRACKING] = "" return result # Bail out if unknown sensor type - if const.ATTR_EMAIL not in const.SENSOR_DATA[sensor_type]: + if ATTR_EMAIL not in SENSOR_DATA[sensor_type]: _LOGGER.debug("Unknown sensor type: %s", str(sensor_type)) - result[const.ATTR_COUNT] = count - result[const.ATTR_TRACKING] = "" + result[ATTR_COUNT] = count + result[ATTR_TRACKING] = "" return result - subjects = const.SENSOR_DATA[sensor_type][const.ATTR_SUBJECT] + subjects = SENSOR_DATA[sensor_type][ATTR_SUBJECT] for subject in subjects: _LOGGER.debug( "Attempting to find mail from (%s) with subject (%s)", - const.SENSOR_DATA[sensor_type][const.ATTR_EMAIL], + SENSOR_DATA[sensor_type][ATTR_EMAIL], subject, ) (server_response, data) = email_search( - account, const.SENSOR_DATA[sensor_type][const.ATTR_EMAIL], today, subject + account, SENSOR_DATA[sensor_type][ATTR_EMAIL], today, subject ) if server_response == "OK" and data[0] is not None: - if const.ATTR_BODY in const.SENSOR_DATA[sensor_type].keys(): + if ATTR_BODY in SENSOR_DATA[sensor_type].keys(): count += find_text( - data, account, const.SENSOR_DATA[sensor_type][const.ATTR_BODY][0] + data, account, SENSOR_DATA[sensor_type][ATTR_BODY][0] ) else: count += len(data[0].split()) _LOGGER.debug( "Search for (%s) with subject (%s) results: %s count: %s", - const.SENSOR_DATA[sensor_type][const.ATTR_EMAIL], + SENSOR_DATA[sensor_type][ATTR_EMAIL], subject, data[0], count, ) found.append(data[0]) - if ( - const.ATTR_PATTERN - in const.SENSOR_DATA[f"{sensor_type.split('_')[0]}_tracking"].keys() - ): - track = const.SENSOR_DATA[f"{sensor_type.split('_')[0]}_tracking"][ - const.ATTR_PATTERN - ][0] + if ATTR_PATTERN in SENSOR_DATA[f"{sensor_type.split('_')[0]}_tracking"].keys(): + track = SENSOR_DATA[f"{sensor_type.split('_')[0]}_tracking"][ATTR_PATTERN][0] if track is not None and get_tracking_num and count > 0: for sdata in found: @@ -815,9 +852,9 @@ def get_count( # Use tracking numbers found for count (more accurate) count = len(tracking) - result[const.ATTR_TRACKING] = tracking + result[ATTR_TRACKING] = tracking - result[const.ATTR_COUNT] = count + result[ATTR_COUNT] = count return result @@ -923,14 +960,14 @@ def amazon_search( Returns email found count as integer """ _LOGGER.debug("Searching for Amazon delivered email(s)...") - domains = const.AMAZON_DOMAINS.split(",") - subjects = const.AMAZON_DELIVERED_SUBJECT + domains = AMAZON_DOMAINS.split(",") + subjects = AMAZON_DELIVERED_SUBJECT today = get_formatted_date() count = 0 for domain in domains: for subject in subjects: - email_address = const.AMAZON_EMAIL + domain + email_address = AMAZON_EMAIL + domain _LOGGER.debug("Amazon email search address: %s", str(email_address)) (server_response, data) = email_search( @@ -973,7 +1010,7 @@ def get_amazon_image( _LOGGER.debug("Processing HTML email...") part = part.get_payload(decode=True) part = part.decode("utf-8", "ignore") - pattern = re.compile(r"{}".format(const.AMAZON_IMG_PATTERN)) + pattern = re.compile(r"{}".format(AMAZON_IMG_PATTERN)) found = pattern.findall(part) for url in found: if url[1] != "us-prod-temp.s3.amazonaws.com": @@ -1029,17 +1066,17 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> Returns dict of sensor data """ email_addresses = _process_amazon_forwards(fwds) - body_regex = const.AMAZON_HUB_BODY - subject_regex = const.AMAZON_HUB_SUBJECT_SEARCH + body_regex = AMAZON_HUB_BODY + subject_regex = AMAZON_HUB_SUBJECT_SEARCH info = {} today = get_formatted_date() - email_addresses.extend(const.AMAZON_HUB_EMAIL) + email_addresses.extend(AMAZON_HUB_EMAIL) _LOGGER.debug("[Hub] Amazon email list: %s", str(email_addresses)) for address in email_addresses: (server_response, sdata) = email_search( - account, address, today, subject=const.AMAZON_HUB_SUBJECT + account, address, today, subject=AMAZON_HUB_SUBJECT ) # Bail out on error @@ -1047,8 +1084,8 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> return info if len(sdata) == 0: - info[const.ATTR_COUNT] = 0 - info[const.ATTR_CODE] = [] + info[ATTR_COUNT] = 0 + info[ATTR_CODE] = [] return info found = [] @@ -1082,8 +1119,8 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> if len(search.groups()) > 1: found.append(search.group(2)) - info[const.ATTR_COUNT] = len(found) - info[const.ATTR_CODE] = found + info[ATTR_COUNT] = len(found) + info[ATTR_CODE] = found return info @@ -1099,7 +1136,7 @@ def amazon_exception( tfmt = get_formatted_date() count = 0 info = {} - domains = const.AMAZON_DOMAINS.split(",") + domains = AMAZON_DOMAINS.split(",") if isinstance(fwds, list): for fwd in fwds: if fwd and fwd != '""': @@ -1114,22 +1151,22 @@ def amazon_exception( _LOGGER.debug("Amazon email search address: %s", str(email_address)) else: email_address = [] - email_address.append(f"{const.AMAZON_EMAIL}{domain}") + email_address.append(f"{AMAZON_EMAIL}{domain}") _LOGGER.debug("Amazon email search address: %s", str(email_address)) (server_response, sdata) = email_search( - account, email_address, tfmt, const.AMAZON_EXCEPTION_SUBJECT + account, email_address, tfmt, AMAZON_EXCEPTION_SUBJECT ) if server_response == "OK": count += len(sdata[0].split()) _LOGGER.debug("Found %s Amazon exceptions", count) - order_numbers = get_tracking(sdata[0], account, const.AMAZON_PATTERN) + order_numbers = get_tracking(sdata[0], account, AMAZON_PATTERN) for order in order_numbers: order_number.append(order) - info[const.ATTR_COUNT] = count - info[const.ATTR_ORDER] = order_number + info[ATTR_COUNT] = count + info[ATTR_ORDER] = order_number return info @@ -1153,7 +1190,7 @@ def get_items( order_number = [] domains = _process_amazon_forwards(fwds) - main_domains = const.AMAZON_DOMAINS.split(",") + main_domains = AMAZON_DOMAINS.split(",") for main_domain in main_domains: domains.append(main_domain) @@ -1165,7 +1202,7 @@ def get_items( _LOGGER.debug("Amazon email search address: %s", str(email_address)) else: email_address = [] - addresses = const.AMAZON_SHIPMENT_TRACKING + addresses = AMAZON_SHIPMENT_TRACKING for address in addresses: email_address.append(f"{address}@{domain}") _LOGGER.debug("Amazon email search address: %s", str(email_address)) @@ -1221,7 +1258,7 @@ def get_items( ): order_number.append(found[0]) - searches = const.AMAZON_TIME_PATTERN.split(",") + searches = AMAZON_TIME_PATTERN.split(",") for search in searches: _LOGGER.debug("Looking for: %s", search) if search not in email_msg: @@ -1242,7 +1279,7 @@ def get_items( time_format = None new_arrive_date = None - for lang in const.AMAZON_LANGS: + for lang in AMAZON_LANGS: try: locale.setlocale(locale.LC_TIME, lang) except Exception as err: From 080d825133bcf38ed320c01e9a59b837779f87e9 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 3 Dec 2021 09:53:03 -0700 Subject: [PATCH 26/88] Update custom_components/mail_and_packages/helpers.py --- custom_components/mail_and_packages/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 014c504b..cf5b8007 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -17,7 +17,7 @@ import aiohttp import imageio as io -from const import ( +from .const import ( AMAZON_DELIVERED, AMAZON_DELIVERED_SUBJECT, AMAZON_DOMAINS, From efce7cefb6def55d18aed607a94ab5154b32f5e7 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 3 Dec 2021 10:15:53 -0700 Subject: [PATCH 27/88] fix: amazon handle arriving early emails * fixes #596 --- custom_components/mail_and_packages/const.py | 2 +- .../mail_and_packages/helpers.py | 4 +- tests/conftest.py | 28 +- tests/test_emails/amazon_shipped_alt_2.eml | 1560 +++++++++++++++++ tests/test_helpers.py | 10 + 5 files changed, 1601 insertions(+), 3 deletions(-) create mode 100644 tests/test_emails/amazon_shipped_alt_2.eml diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index f1a87a9b..173b40a0 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -82,7 +82,7 @@ AMAZON_HUB_SUBJECT = "ready for pickup from Amazon Hub Locker" AMAZON_HUB_SUBJECT_SEARCH = "(You have a package to pick up)(.*)(\\d{6})" AMAZON_HUB_BODY = "(Your pickup code is )(\\d{6})" -AMAZON_TIME_PATTERN = "will arrive:,estimated delivery date is:,guaranteed delivery date is:,Arriving:,Arriverà:" +AMAZON_TIME_PATTERN = "will arrive:,estimated delivery date is:,guaranteed delivery date is:,Arriving:,Arriverà:,arriving:" AMAZON_EXCEPTION_SUBJECT = "Delivery update:" AMAZON_EXCEPTION_BODY = "running late" AMAZON_EXCEPTION = "amazon_exception" diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index cf5b8007..8efc961e 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -1266,7 +1266,9 @@ def get_items( start = email_msg.find(search) + len(search) end = -1 - if email_msg.find("Track your") != -1: + if email_msg.find("Previously expected:") != -1: + end = email_msg.find("Previously expected:") + elif email_msg.find("Track your") != -1: end = email_msg.find("Track your") elif email_msg.find("Per tracciare il tuo pacco") != -1: end = email_msg.find("Per tracciare il tuo pacco") diff --git a/tests/conftest.py b/tests/conftest.py index 94ee2045..0a534bf7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -404,6 +404,7 @@ def mock_imap_fedex_out_for_delivery(): mock_conn.select.return_value = ("OK", []) yield mock_conn + @pytest.fixture() def mock_imap_fedex_out_for_delivery_2(): """Mock imap class values.""" @@ -426,7 +427,7 @@ def mock_imap_fedex_out_for_delivery_2(): email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) mock_conn.select.return_value = ("OK", []) - yield mock_conn + yield mock_conn @pytest.fixture() @@ -554,6 +555,31 @@ def mock_imap_amazon_shipped_alt(): yield mock_conn +@pytest.fixture() +def mock_imap_amazon_shipped_alt_2(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_amazon_shipped_alt_2: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_amazon_shipped_alt_2.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/amazon_shipped_alt_2.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + + @pytest.fixture() def mock_imap_amazon_shipped_it(): """Mock imap class values.""" diff --git a/tests/test_emails/amazon_shipped_alt_2.eml b/tests/test_emails/amazon_shipped_alt_2.eml new file mode 100644 index 00000000..6cd31419 --- /dev/null +++ b/tests/test_emails/amazon_shipped_alt_2.eml @@ -0,0 +1,1560 @@ +From: "Amazon.com" +Reply-To: no-reply@amazon.com +To: testuser@gmail.com +Subject: Shipped: Now arriving early on Friday, December 3 + (#113-999999-8459426) +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_82986581_1367427296.1638513878364" +Date: Fri, 3 Dec 2021 06:44:38 +0000 + +------=_Part_82986581_1367427296.1638513878364 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +Amazon Shipping Confirmation +https://www.amazon.com?ie=3DUTF8&ref_=3Dscr_home + +____________________________________________________________________ + +Hi Christopher, Your package is arriving earlier than we previously expecte= +d. It=E2=80=99s now arriving: + +Friday, December 3 +Previously expected:December 9 - December 10 + +Track your package: +https://www.amazon.com/gp/your-account/ship-track?ie=3DUTF8&orderId=3D113-9= +999999-8459426&packageIndex=3D0&shipmentId=3DUqzX5lHJm&ref_=3Dscr_pt_tp_t + +JUST ASK +"Alexa, where's my stuff?" +https://www.amazon.com/?ref=3Dsce_wms + + +On the way: +1 item +Order #113-9999999-8459426 + + +An Amazon driver may contact you by text message or call you for help on th= +e day of delivery. =20 + +Ship to: + Christopher + RIVERVIEW, FL + +Shipment total: +$83.60 + + +Return or replace items in Your Orders +https://www.amazon.com/gp/css/order-history?ie=3DUTF8&ref_=3Dscr_yo + +Learn how to recycle your packaging at Amazon Second Chance(https://www.ama= +zon.com/amsc?ref_=3Dascyorn). + + + +____________________________________________________________________ + +Unless otherwise noted, items sold by Amazon.com are subject to sales tax i= +n select states in accordance with the applicable laws of that state. If yo= +ur order contains one or more items from a seller other than Amazon.com, it= + may be subject to state and local sales tax, depending upon the sellers bu= +siness policies and the location of their operations. Learn more about tax = +and seller information: +http://www.amazon.com/gp/help/customer/display.html?ie=3DUTF8&nodeId=3D2020= +29700&ref_-=3Dscr_help_tax + + + +Your invoice can be accessed here: +https://www.amazon.com/gp/css/summary/print.html?ie=3DUTF8&orderID=3D113-99= +99999-8459426&ref_=3Dscr_od_invoice + +This email was sent from a notification-only address that cannot accept inc= +oming email. Please do not reply to this message. +------=_Part_82986581_1367427296.1638513878364 +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + + + + + =20 + =20 + =20 + =20 + =20 + Shipping Confirmation=20 + =20 + =20 + =20 + =20 + =20 +
+ Hi Christopher, your package is on the way! You can track it and check = +out when your package will arrive. +
=20 + =20 + + =20 + =20 + =20 + =20 + +
 =20 + =20 + =20 + =20 + =20 + + =20 + + =20 + =20 + =20 + =20 + +
+ =20 + + =20 + =20 + =20 + =20 + =20 + + =20 + =20 + =20 + +
3D""
=20 + 3D"Amazon.com"=20 +
3D""
=20 + =20 + + =20 + =20 + =20 + +
=20 + =20 + + =20 + =20 + =20 + +
3D""
=20 + =20 + + + =20 + + =20 + =20 + =20 + +
=20 + =20 + + =20 + + =20 + =20 + +
+ =20 + + =20 + + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + + + =20 + =20 + =20 + +
+
+
+ Hi Christopher, Your package is arriving earlier= + than we previously expected. It=E2=80=99s now arriving: +
3D""
+
+ Friday, December 3=20 +
3D""
+
+ Previously expected: +
+
+ December 9 - December 10 +
3D""
+ =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
Track package
JUST ASK
3D""
+
+ "Alexa, where's my stuff?" +
3D""
=20 + =20 + + =20 + =20 + =20 + +
3D""
=20 + =20 + + + + =20 + + + =20 + + + =20 + +
+ =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
3D"delivery" = +=20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
3D""
ON THE WAY
3D""
+
+ 1 item +
3D""
Order #113-9999999-8459426
3D""
An Amazon driver may contact you by text message or call you= + for help on the day of delivery.
3D""
+ =20 + + =20 + =20 + =20 + =20 + =20 + =20 + +
=20 + =20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
3D""
SHIP TO
3D""
+
+ Christopher +
3D""
+
+ RIVERVIEW, FL +
3D""
+ =20 + + =20 + = +=20 + =20 + =20 + =20 + =20 + =20 + +
=20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
3D""
SHIPMENT TOTAL
3D""
+
+ $83.60=20 +
3D""
=20 + =20 + + =20 + =20 + =20 + +
Return or replace items in Your Orders
=20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + +
3D""
Learn how to recycle your packaging at Amazon Second Chance.
=20 + =20 + + =20 + =20 + =20 + +
3D""
=20 + =20 + + =20 + =20 + =20 + + + =20 + =20 + =20 + =20 + +
3D""
=20 + =20 +
+ =20 + =20 + + + + + + + + + + +
+ + + + + + +
Customers Who Bough= +t Items in Your Order Also Bought
+ + + + + + + +
+ + + + + + + + + +
3D"Fill-O-Matic
+ + + + + + + + +

Fill-O-Matic 2000 Extension Bracket<= +/p>

$19.99 +
+ + + + + + + + + +
3D"Camco
+ + + + + + + + +

Camco (40055) RV Brass Inline Water.= +..

$9.00 +
+ =20 + =20 + =20 +
3D""
=20 + =20 + + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 + +
3D""
Unless otherwise noted, items sold by Amazon.com are subject= + to sales tax in select states in accordance with the applicable laws of th= +at state. If your order contains one or more items from a seller other than= + Amazon.com, it may be subject to state and local sales tax, depending upon= + the sellers business policies and the location of their operations. Learn = +more about tax and seller information.<= +/td>=20 +
3D""
Your invoice can be accessed here.
3D""
This email was sent from a notification-only address that ca= +nnot accept incoming email. Please do not reply to this message.
3D""
=20 + =20 + + =20 + =20 + =20 + +
3D""
=20 + =20 + =20 + =20 + =20 +
+   +
=20 + =20 + + =20 + =20 + =20 + +
= +
=20 + =20 + =20 + =20 + =20 + =20 + =20 + =20 +
+                   = +                     = +;                     +
=20 + =20 + + =20 +  =20 + =20 + + =20 + + +------=_Part_82986581_1367427296.1638513878364-- diff --git a/tests/test_helpers.py b/tests/test_helpers.py index f1c8102d..fba41b60 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -758,6 +758,16 @@ async def test_amazon_shipped_order_alt(hass, mock_imap_amazon_shipped_alt): assert result == ["123-1234567-1234567"] +async def test_amazon_shipped_order_alt_2(hass, mock_imap_amazon_shipped_alt_2): + result = get_items(mock_imap_amazon_shipped_alt_2, "order") + assert result == ["113-9999999-8459426"] + with patch("datetime.date") as mock_date: + mock_date.today.return_value = date(2021, 12, 3) + + result = get_items(mock_imap_amazon_shipped_alt_2, "count") + assert result == 1 + + async def test_amazon_shipped_order_alt_timeformat( hass, mock_imap_amazon_shipped_alt_timeformat ): From 52bf0ce28cf5276dd22eb5ad15ed90a22905cc15 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 3 Dec 2021 15:04:46 -0700 Subject: [PATCH 28/88] fix: change to extra_state_attributes Changes for Home Assistant 2021.21.0+ --- custom_components/mail_and_packages/camera.py | 4 ++-- custom_components/mail_and_packages/sensor.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/custom_components/mail_and_packages/camera.py b/custom_components/mail_and_packages/camera.py index 628635cc..cf65d333 100644 --- a/custom_components/mail_and_packages/camera.py +++ b/custom_components/mail_and_packages/camera.py @@ -195,9 +195,9 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the camera state attributes.""" - return {"file_path": self._file_path, CONF_HOST: self._host} + return {"file_path": self._file_path} @property def should_poll(self) -> bool: diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index d35196d3..c3b4e02d 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -113,10 +113,9 @@ def available(self) -> bool: return self.coordinator.last_update_success @property - def device_state_attributes(self) -> Optional[str]: + def extra_state_attributes(self) -> Optional[str]: """Return device specific state attributes.""" attr = {} - attr[ATTR_SERVER] = self._host tracking = f"{self.type.split('_')[0]}_tracking" data = self.coordinator.data From b6473f292eeeacebfba2915b77b06b6576219898 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 3 Jan 2022 14:59:15 -0700 Subject: [PATCH 29/88] feat: make amazon sensor more configurable --- .../mail_and_packages/__init__.py | 28 +++++++++++++++++-- .../mail_and_packages/config_flow.py | 9 +++++- custom_components/mail_and_packages/const.py | 2 ++ .../mail_and_packages/helpers.py | 20 +++++++++++-- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index bc494e79..418bf5f5 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -12,12 +12,14 @@ from .const import ( CONF_ALLOW_EXTERNAL, + CONF_AMAZON_DAYS, CONF_AMAZON_FWDS, CONF_IMAGE_SECURITY, CONF_IMAP_TIMEOUT, CONF_PATH, CONF_SCAN_INTERVAL, COORDINATOR, + DEFAULT_AMAZON_DAYS, DEFAULT_IMAP_TIMEOUT, DOMAIN, ISSUE_URL, @@ -156,7 +158,7 @@ async def async_migrate_entry(hass, config_entry): """Migrate an old config entry.""" version = config_entry.version - # 1 -> 3: Migrate format + # 1 -> 4: Migrate format if version == 1: _LOGGER.debug("Migrating from version %s", version) updated_config = config_entry.data.copy() @@ -178,12 +180,16 @@ async def async_migrate_entry(hass, config_entry): if not config_entry.data[CONF_IMAGE_SECURITY]: updated_config[CONF_IMAGE_SECURITY] = True + # Add default Amazon Days configuration + updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS + if updated_config != config_entry.data: hass.config_entries.async_update_entry(config_entry, data=updated_config) - config_entry.version = 3 + config_entry.version = 4 _LOGGER.debug("Migration to version %s complete", config_entry.version) + # 2 -> 4 if version == 2: _LOGGER.debug("Migrating from version %s", version) updated_config = config_entry.data.copy() @@ -195,10 +201,26 @@ async def async_migrate_entry(hass, config_entry): if not config_entry.data[CONF_IMAGE_SECURITY]: updated_config[CONF_IMAGE_SECURITY] = True + # Add default Amazon Days configuration + updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS + + if updated_config != config_entry.data: + hass.config_entries.async_update_entry(config_entry, data=updated_config) + + config_entry.version = 4 + _LOGGER.debug("Migration to version %s complete", config_entry.version) + + if version == 3: + _LOGGER.debug("Migrating from version %s", version) + updated_config = config_entry.data.copy() + + # Add default Amazon Days configuration + updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS + if updated_config != config_entry.data: hass.config_entries.async_update_entry(config_entry, data=updated_config) - config_entry.version = 3 + config_entry.version = 4 _LOGGER.debug("Migration to version %s complete", config_entry.version) return True diff --git a/custom_components/mail_and_packages/config_flow.py b/custom_components/mail_and_packages/config_flow.py index 735affea..79b1d731 100644 --- a/custom_components/mail_and_packages/config_flow.py +++ b/custom_components/mail_and_packages/config_flow.py @@ -19,6 +19,7 @@ from .const import ( CONF_ALLOW_EXTERNAL, CONF_AMAZON_FWDS, + CONF_AMAZON_DAYS, CONF_CUSTOM_IMG, CONF_CUSTOM_IMG_FILE, CONF_DURATION, @@ -30,6 +31,7 @@ CONF_SCAN_INTERVAL, DEFAULT_ALLOW_EXTERNAL, DEFAULT_AMAZON_FWDS, + DEFAULT_AMAZON_DAYS, DEFAULT_CUSTOM_IMG, DEFAULT_CUSTOM_IMG_FILE, DEFAULT_FOLDER, @@ -188,6 +190,9 @@ def _get_default(key: str, fallback_default: Any = None) -> None: vol.Optional( CONF_AMAZON_FWDS, default=_get_default(CONF_AMAZON_FWDS, "") ): str, + vol.Optional( + CONF_AMAZON_DAYS, default=_get_default(CONF_AMAZON_DAYS) + ): int, vol.Optional( CONF_SCAN_INTERVAL, default=_get_default(CONF_SCAN_INTERVAL) ): vol.All(vol.Coerce(int)), @@ -233,7 +238,7 @@ def _get_default(key: str, fallback_default: Any = None) -> None: class MailAndPackagesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for Mail and Packages.""" - VERSION = 3 + VERSION = 4 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): @@ -305,6 +310,7 @@ async def _show_config_2(self, user_input): CONF_IMAGE_SECURITY: DEFAULT_IMAGE_SECURITY, CONF_IMAP_TIMEOUT: DEFAULT_IMAP_TIMEOUT, CONF_AMAZON_FWDS: DEFAULT_AMAZON_FWDS, + CONF_AMAZON_DAYS: DEFAULT_AMAZON_DAYS, CONF_GENERATE_MP4: False, CONF_ALLOW_EXTERNAL: DEFAULT_ALLOW_EXTERNAL, CONF_CUSTOM_IMG: DEFAULT_CUSTOM_IMG, @@ -417,6 +423,7 @@ async def _show_step_options_2(self, user_input): CONF_IMAP_TIMEOUT: self._data.get(CONF_IMAP_TIMEOUT) or DEFAULT_IMAP_TIMEOUT, CONF_AMAZON_FWDS: self._data.get(CONF_AMAZON_FWDS) or DEFAULT_AMAZON_FWDS, + CONF_AMAZON_DAYS: self._data.get(CONF_AMAZON_DAYS) or DEFAULT_AMAZON_DAYS, CONF_GENERATE_MP4: self._data.get(CONF_GENERATE_MP4), CONF_ALLOW_EXTERNAL: self._data.get(CONF_ALLOW_EXTERNAL), CONF_RESOURCES: self._data.get(CONF_RESOURCES), diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 173b40a0..501aadb7 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -48,6 +48,7 @@ CONF_IMAP_TIMEOUT = "imap_timeout" CONF_GENERATE_MP4 = "generate_mp4" CONF_AMAZON_FWDS = "amazon_fwds" +CONF_AMAZON_DAYS = "amazon_days" # Defaults DEFAULT_CAMERA_NAME = "Mail USPS Camera" @@ -64,6 +65,7 @@ DEFAULT_ALLOW_EXTERNAL = False DEFAULT_CUSTOM_IMG = False DEFAULT_CUSTOM_IMG_FILE = "custom_components/mail_and_packages/images/mail_none.gif" +DEFAULT_AMAZON_DAYS = 3 # Amazon AMAZON_DOMAINS = "amazon.com,amazon.ca,amazon.co.uk,amazon.in,amazon.de,amazon.it" diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 8efc961e..bd8f4810 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -51,6 +51,7 @@ ATTR_TRACKING, ATTR_USPS_MAIL, CONF_ALLOW_EXTERNAL, + CONF_AMAZON_DAYS, CONF_AMAZON_FWDS, CONF_CUSTOM_IMG, CONF_CUSTOM_IMG_FILE, @@ -58,6 +59,7 @@ CONF_FOLDER, CONF_GENERATE_MP4, CONF_PATH, + DEFAULT_AMAZON_DAYS, OVERLAY, SENSOR_DATA, SENSOR_TYPES, @@ -332,6 +334,7 @@ def fetch( amazon_fwds = config.get(CONF_AMAZON_FWDS) image_name = data[ATTR_IMAGE_NAME] amazon_image_name = data[ATTR_AMAZON_IMAGE] + amazon_days = config.get(CONF_AMAZON_DAYS) if config.get(CONF_CUSTOM_IMG): nomail = config.get(CONF_CUSTOM_IMG_FILE) @@ -353,8 +356,18 @@ def fetch( nomail, ) elif sensor == AMAZON_PACKAGES: - count[sensor] = get_items(account, ATTR_COUNT, amazon_fwds) - count[AMAZON_ORDER] = get_items(account, ATTR_ORDER) + count[sensor] = get_items( + account=account, + param=ATTR_COUNT, + fwds=amazon_fwds, + days=amazon_days, + ) + count[AMAZON_ORDER] = get_items( + account=account, + param=ATTR_ORDER, + fwds=amazon_fwds, + days=amazon_days, + ) elif sensor == AMAZON_HUB: value = amazon_hub(account, amazon_fwds) count[sensor] = value[ATTR_COUNT] @@ -1175,6 +1188,7 @@ def get_items( account: Type[imaplib.IMAP4_SSL], param: str = None, fwds: Optional[str] = None, + days: int = DEFAULT_AMAZON_DAYS, ) -> Union[List[str], int]: """Parse Amazon emails for delivery date and order number. @@ -1184,7 +1198,7 @@ def get_items( _LOGGER.debug("Attempting to find Amazon email with item list ...") # Limit to past 3 days (plan to make this configurable) - past_date = datetime.date.today() - datetime.timedelta(days=3) + past_date = datetime.date.today() - datetime.timedelta(days=days) tfmt = past_date.strftime("%d-%b-%Y") deliveries_today = [] order_number = [] From 7ae932d03026d1a865619bb70f0c8f13d967762c Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 3 Jan 2022 15:34:52 -0700 Subject: [PATCH 30/88] tests: fix tets related to amazon days --- .../mail_and_packages/__init__.py | 4 +- .../mail_and_packages/config_flow.py | 4 +- .../mail_and_packages/helpers.py | 12 +++--- custom_components/mail_and_packages/sensor.py | 1 - tests/const.py | 5 +++ tests/test_config_flow.py | 27 +++++++++++++ tests/test_sensor.py | 38 +------------------ 7 files changed, 42 insertions(+), 49 deletions(-) diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index 418bf5f5..78e28c7f 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -181,7 +181,7 @@ async def async_migrate_entry(hass, config_entry): updated_config[CONF_IMAGE_SECURITY] = True # Add default Amazon Days configuration - updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS + updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS if updated_config != config_entry.data: hass.config_entries.async_update_entry(config_entry, data=updated_config) @@ -202,7 +202,7 @@ async def async_migrate_entry(hass, config_entry): updated_config[CONF_IMAGE_SECURITY] = True # Add default Amazon Days configuration - updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS + updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS if updated_config != config_entry.data: hass.config_entries.async_update_entry(config_entry, data=updated_config) diff --git a/custom_components/mail_and_packages/config_flow.py b/custom_components/mail_and_packages/config_flow.py index 79b1d731..f45e1bf8 100644 --- a/custom_components/mail_and_packages/config_flow.py +++ b/custom_components/mail_and_packages/config_flow.py @@ -190,9 +190,7 @@ def _get_default(key: str, fallback_default: Any = None) -> None: vol.Optional( CONF_AMAZON_FWDS, default=_get_default(CONF_AMAZON_FWDS, "") ): str, - vol.Optional( - CONF_AMAZON_DAYS, default=_get_default(CONF_AMAZON_DAYS) - ): int, + vol.Optional(CONF_AMAZON_DAYS, default=_get_default(CONF_AMAZON_DAYS)): int, vol.Optional( CONF_SCAN_INTERVAL, default=_get_default(CONF_SCAN_INTERVAL) ): vol.All(vol.Coerce(int)), diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index bd8f4810..91b79ca6 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -357,15 +357,15 @@ def fetch( ) elif sensor == AMAZON_PACKAGES: count[sensor] = get_items( - account=account, - param=ATTR_COUNT, - fwds=amazon_fwds, + account=account, + param=ATTR_COUNT, + fwds=amazon_fwds, days=amazon_days, ) count[AMAZON_ORDER] = get_items( - account=account, - param=ATTR_ORDER, - fwds=amazon_fwds, + account=account, + param=ATTR_ORDER, + fwds=amazon_fwds, days=amazon_days, ) elif sensor == AMAZON_HUB: diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index c3b4e02d..16ca6999 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -20,7 +20,6 @@ ATTR_IMAGE_NAME, ATTR_IMAGE_PATH, ATTR_ORDER, - ATTR_SERVER, ATTR_TRACKING_NUM, CONF_PATH, COORDINATOR, diff --git a/tests/const.py b/tests/const.py index 3e69aa19..fe856e38 100644 --- a/tests/const.py +++ b/tests/const.py @@ -39,6 +39,7 @@ } FAKE_CONFIG_DATA = { + "amazon_days": 3, "amazon_fwds": "fakeuser@fake.email, fakeuser2@fake.email", "custom_img": False, "folder": '"INBOX"', @@ -89,6 +90,7 @@ FAKE_CONFIG_DATA_EXTERNAL = { "allow_external": True, + "amazon_days": 3, "amazon_fwds": "fakeuser@fake.email, fakeuser2@fake.email", "custom_img": False, "folder": '"INBOX"', @@ -138,6 +140,7 @@ FAKE_CONFIG_DATA_CORRECTED_EXTERNAL = { "allow_external": True, + "amazon_days": 3, "amazon_fwds": ["fakeuser@fake.email", "fakeuser2@fake.email"], "custom_img": False, "folder": '"INBOX"', @@ -187,6 +190,7 @@ FAKE_CONFIG_DATA_CORRECTED = { "allow_external": False, + "amazon_days": 3, "amazon_fwds": ["fakeuser@fake.email", "fakeuser2@fake.email"], "custom_img": False, "folder": '"INBOX"', @@ -282,6 +286,7 @@ } FAKE_CONFIG_DATA_NO_RND = { + "amazon_days": 3, "amazon_fwds": ["fakeuser@fake.email"], "custom_img": False, "folder": '"INBOX"', diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index e885fb49..1760a97a 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -31,6 +31,7 @@ "config_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "fakeuser@test.email,fakeuser2@test.email", "custom_img": True, "folder": '"INBOX"', @@ -69,6 +70,7 @@ "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": ["fakeuser@test.email", "fakeuser2@test.email"], "custom_img": True, "custom_img_file": "images/test.gif", @@ -181,6 +183,7 @@ async def test_form( "config_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "fakeuser@test.email,fakeuser2@test.email", "custom_img": True, "folder": '"INBOX"', @@ -219,6 +222,7 @@ async def test_form( "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": ["fakeuser@test.email", "fakeuser2@test.email"], "custom_img": True, "custom_img_file": "images/test.gif", @@ -369,6 +373,7 @@ async def test_form_connection_error(input_1, step_id_2, hass, mock_imap): "config_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "folder": '"INBOX"', "generate_mp4": True, @@ -401,6 +406,7 @@ async def test_form_connection_error(input_1, step_id_2, hass, mock_imap): }, "imap.test.email", { + "amazon_days": 3, "amazon_fwds": [], "host": "imap.test.email", "port": 993, @@ -492,6 +498,7 @@ async def test_form_invalid_ffmpeg( { "allow_external": False, "custom_img": False, + "amazon_days": 3, "amazon_fwds": "", "folder": '"INBOX"', "generate_mp4": False, @@ -526,6 +533,7 @@ async def test_form_invalid_ffmpeg( { "allow_external": False, "custom_img": False, + "amazon_days": 3, "amazon_fwds": [], "host": "imap.test.email", "port": 993, @@ -625,6 +633,7 @@ async def test_form_index_error( "config_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "custom_img": False, "folder": '"INBOX"', @@ -659,6 +668,7 @@ async def test_form_index_error( "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": [], "custom_img": False, "host": "imap.test.email", @@ -759,6 +769,7 @@ async def test_form_index_error_2( "config_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "folder": '"INBOX"', "generate_mp4": False, @@ -793,6 +804,7 @@ async def test_form_index_error_2( { "allow_external": False, "custom_img": False, + "amazon_days": 3, "amazon_fwds": [], "host": "imap.test.email", "port": 993, @@ -919,6 +931,7 @@ async def test_imap_login_error(mock_imap_login_error, caplog): "options_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "custom_img": True, "folder": '"INBOX"', @@ -957,6 +970,7 @@ async def test_imap_login_error(mock_imap_login_error, caplog): "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": [], "custom_img": True, "custom_img_file": "images/test.gif", @@ -1083,6 +1097,7 @@ async def test_options_flow( "options_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "custom_img": True, "folder": '"INBOX"', @@ -1121,6 +1136,7 @@ async def test_options_flow( "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": [], "custom_img": True, "custom_img_file": "images/test.gif", @@ -1300,6 +1316,7 @@ async def test_options_flow_connection_error( "options_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "custom_img": False, "folder": '"INBOX"', @@ -1334,6 +1351,7 @@ async def test_options_flow_connection_error( "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": [], "custom_img": False, "host": "imap.test.email", @@ -1436,6 +1454,7 @@ async def test_options_flow_invalid_ffmpeg( "options_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "custom_img": False, "folder": '"INBOX"', @@ -1470,6 +1489,7 @@ async def test_options_flow_invalid_ffmpeg( "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": [], "custom_img": False, "host": "imap.test.email", @@ -1573,6 +1593,7 @@ async def test_options_flow_index_error( "options_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "custom_img": False, "folder": '"INBOX"', @@ -1607,6 +1628,7 @@ async def test_options_flow_index_error( "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": [], "custom_img": False, "host": "imap.test.email", @@ -1710,6 +1732,7 @@ async def test_options_flow_index_error_2( "options_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "custom_img": False, "folder": '"INBOX"', @@ -1744,6 +1767,7 @@ async def test_options_flow_index_error_2( "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": [], "custom_img": False, "host": "imap.test.email", @@ -1880,6 +1904,7 @@ async def test_options_flow_mailbox_format2( "imap.test.email", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": ['""'], "custom_img": False, "host": "imap.test.email", @@ -1992,6 +2017,7 @@ async def test_options_flow_bad( "config_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "testemail@amazon.com", "custom_img": False, "folder": '"INBOX"', @@ -2080,6 +2106,7 @@ async def test_form_amazon_error( "config_2", { "allow_external": False, + "amazon_days": 3, "amazon_fwds": "", "custom_img": False, "folder": '"INBOX"', diff --git a/tests/test_sensor.py b/tests/test_sensor.py index da5cb7e2..22c925d1 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -28,122 +28,86 @@ async def test_sensor(hass, mock_update): state = hass.states.get("sensor.mail_usps_mail") assert state assert state.state == "6" - assert state.attributes["server"] == "imap.test.email" + assert state.attributes["image"] == "mail_today.gif" state = hass.states.get("sensor.mail_usps_delivered") assert state assert state.state == "3" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_usps_delivering") assert state assert state.state == "3" assert state.attributes["tracking_#"] == ["92123456789012345"] - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_usps_packages") assert state assert state.state == "3" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_ups_delivered") assert state assert state.state == "1" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_ups_delivering") assert state assert state.state == "1" assert state.attributes["tracking_#"] == ["1Z123456789"] - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_ups_packages") assert state assert state.state == "1" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_fedex_delivered") assert state assert state.state == "0" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_fedex_delivering") assert state assert state.state == "2" assert state.attributes["tracking_#"] == ["1234567890"] - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_fedex_packages") assert state assert state.state == "2" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_fedex_packages") assert state assert state.state == "2" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_amazon_packages") assert state assert state.state == "7" assert state.attributes["order"] == ["#123-4567890"] - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_amazon_packages_delivered") assert state assert state.state == "2" - assert state.attributes["server"] == "imap.test.email" - - # state = hass.states.get("sensor.mail_amazon_hub") - # assert state - # assert state.state == "2" - # assert state.attributes["code"] == ["1234567890"] - - # state = hass.states.get("sensor.mail_capost_delivered") - # assert state - # assert state.state == "1" - - # state = hass.states.get("sensor.mail_capost_delivering") - # assert state - # assert state.state == "1" - - # state = hass.states.get("sensor.mail_capost_packages") - # assert state - # assert state.state == "2" state = hass.states.get("sensor.mail_dhl_delivered") assert state assert state.state == "0" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_dhl_delivering") assert state assert state.state == "1" assert state.attributes["tracking_#"] == ["1234567890"] - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_dhl_packages") assert state assert state.state == "2" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_auspost_delivered") assert state assert state.state == "2" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_auspost_delivering") assert state assert state.state == "1" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_auspost_packages") assert state assert state.state == "3" - assert state.attributes["server"] == "imap.test.email" state = hass.states.get("sensor.mail_packages_delivered") assert state assert state.state == "7" - assert state.attributes["server"] == "imap.test.email" From 1348be0f309115f7f71718d084707fef3dc2677f Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 4 Jan 2022 09:58:07 -0700 Subject: [PATCH 31/88] add text to config flow translation files --- custom_components/mail_and_packages/strings.json | 2 ++ custom_components/mail_and_packages/translations/en.json | 2 ++ 2 files changed, 4 insertions(+) diff --git a/custom_components/mail_and_packages/strings.json b/custom_components/mail_and_packages/strings.json index 0017c530..83cbe9a0 100644 --- a/custom_components/mail_and_packages/strings.json +++ b/custom_components/mail_and_packages/strings.json @@ -35,6 +35,7 @@ "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "generate_mp4": "Create mp4 from images", "amazon_fwds": "Amazon fowarded email addresses", + "amazon_days": "Days back to check for Amazon emails", "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" }, @@ -82,6 +83,7 @@ "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "generate_mp4": "Create mp4 from images", "amazon_fwds": "Amazon fowarded email addresses", + "amazon_days": "Days back to check for Amazon emails", "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" }, diff --git a/custom_components/mail_and_packages/translations/en.json b/custom_components/mail_and_packages/translations/en.json index 9812da9a..c96e51a3 100644 --- a/custom_components/mail_and_packages/translations/en.json +++ b/custom_components/mail_and_packages/translations/en.json @@ -35,6 +35,7 @@ "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "generate_mp4": "Create mp4 from images", "amazon_fwds": "Amazon fowarded email addresses", + "amazon_days": "Days back to check for Amazon emails", "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" }, @@ -82,6 +83,7 @@ "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "generate_mp4": "Create mp4 from images", "amazon_fwds": "Amazon fowarded email addresses", + "amazon_days": "Days back to check for Amazon emails", "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" }, From 73f96a9a7fac40ca43bab306fe7fcb0f2518c3d6 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 6 Jan 2022 08:12:02 -0700 Subject: [PATCH 32/88] docs: update contributing info --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3fb5139e..5d37c69b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ Github is used to host code, to track issues and feature requests, as well as ac Pull requests are the best way to propose changes to the codebase. -1. Fork the repo and create your branch from `master`. +1. Fork the repo and create your branch from `dev`. 2. If you've changed something, update the documentation. 3. Make sure your code lints (using black). 4. Test you contribution. From ae279c19a241973bc9b9983c4aa24d3e0a2e25b4 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 6 Jan 2022 10:03:24 -0700 Subject: [PATCH 33/88] fix: make mail_updated a timestamp class --- custom_components/mail_and_packages/const.py | 3 ++- custom_components/mail_and_packages/helpers.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 501aadb7..26fa561b 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -3,7 +3,7 @@ from typing import Final -from homeassistant.components.sensor import SensorEntityDescription +from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC DOMAIN = "mail_and_packages" @@ -221,6 +221,7 @@ icon="mdi:update", key="mail_updated", entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + device_class=SensorDeviceClass.TIMESTAMP, ), "usps_mail": SensorEntityDescription( name="Mail USPS Mail", diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 91b79ca6..725cfead 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -12,6 +12,7 @@ import subprocess # nosec import uuid from email.header import decode_header +from homeassistant.util import dt as dt_util from shutil import copyfile, copytree, which from typing import Any, List, Optional, Type, Union @@ -401,7 +402,7 @@ def fetch( total += fetch(hass, config, account, data, delivering) count[sensor] = max(0, total) elif sensor == "mail_updated": - count[sensor] = update_time() + count[sensor] = dt_util.parse_datetime(update_time()) else: count[sensor] = get_count( account, sensor, False, img_out_path, hass, amazon_image_name From e47cf3f6e15b62117a8b107a55c557c909e5e345 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 6 Jan 2022 13:26:19 -0700 Subject: [PATCH 34/88] tests: update tests for new timestamp --- custom_components/mail_and_packages/helpers.py | 6 ++++-- tests/conftest.py | 5 ++++- tests/const.py | 2 +- tests/test_helpers.py | 7 ++++--- tests/test_sensor.py | 2 +- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 725cfead..227b3c7b 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -1,6 +1,7 @@ """ Helper functions for Mail and Packages """ import datetime +from datetime import timezone import email import hashlib import imaplib @@ -473,8 +474,9 @@ def update_time() -> str: Returns current timestamp as string """ - updated = datetime.datetime.now().strftime("%b-%d-%Y %I:%M %p") - + #updated = datetime.datetime.now().strftime("%b-%d-%Y %I:%M %p") + updated = datetime.datetime.now(timezone.utc).isoformat(timespec='minutes') + return updated diff --git a/tests/conftest.py b/tests/conftest.py index 0a534bf7..a455631f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,6 @@ """ Fixtures for Mail and Packages tests. """ +import datetime +from datetime import timezone import errno import imaplib import time @@ -853,7 +855,8 @@ def mock_update_time(): with patch( "custom_components.mail_and_packages.helpers.update_time" ) as mock_update_time: - mock_update_time.return_value = "Sep-23-2020 10:28 AM" + mock_update_time.return_value = datetime.datetime(2022, 1, 6, 12, 14, 38, tzinfo=timezone.utc).isoformat(timespec='minutes') + # mock_update_time.return_value = "2022-01-06T12:14:38+00:00" yield mock_update_time diff --git a/tests/const.py b/tests/const.py index fe856e38..3e6ee019 100644 --- a/tests/const.py +++ b/tests/const.py @@ -375,7 +375,7 @@ FAKE_UPDATE_DATA = { "image_name": "mail_today.gif", - "mail_updated": "Sep-18-2020 06:29 PM", + "mail_updated": "2022-01-06T12:14:38+00:00", "usps_mail": 6, "usps_delivered": 3, "usps_delivering": 3, diff --git a/tests/test_helpers.py b/tests/test_helpers.py index fba41b60..954bc213 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,5 +1,6 @@ """Tests for helpers module.""" import datetime +from datetime import timezone import errno from datetime import date from unittest import mock @@ -50,7 +51,7 @@ async def test_get_formatted_date(): async def test_update_time(): - assert update_time() == datetime.datetime.now().strftime("%b-%d-%Y %I:%M %p") + assert update_time() == datetime.datetime.now(timezone.utc).isoformat(timespec='minutes') async def test_cleanup_images(mock_listdir, mock_osremove): @@ -112,7 +113,7 @@ async def test_process_emails( state = hass.states.get(MAIL_IMAGE_URL_ENTITY) assert state.state == "http://127.0.0.1:8123/local/mail_and_packages/testfile.gif" result = process_emails(hass, config) - assert result["mail_updated"] == "Sep-23-2020 10:28 AM" + assert result["mail_updated"] == datetime.datetime(2022, 1, 6, 12, 14, tzinfo=datetime.timezone.utc) assert result["zpackages_delivered"] == 0 assert result["zpackages_transit"] == 0 assert result["amazon_delivered"] == 0 @@ -160,7 +161,7 @@ async def test_process_emails_external( == "http://really.fake.host.net:8123/local/mail_and_packages/testfile.gif" ) result = process_emails(hass, config) - assert result["mail_updated"] == "Sep-23-2020 10:28 AM" + assert result["mail_updated"] == datetime.datetime(2022, 1, 6, 12, 14, tzinfo=datetime.timezone.utc) assert result["zpackages_delivered"] == 0 assert result["zpackages_transit"] == 0 assert result["amazon_delivered"] == 0 diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 22c925d1..5fff3548 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -22,7 +22,7 @@ async def test_sensor(hass, mock_update): # Check for mail_updated sensor reporting value from test data state = hass.states.get("sensor.mail_updated") assert state - assert state.state == "Sep-18-2020 06:29 PM" + assert state.state == "2022-01-06T12:14:38+00:00" # Make sure the rest of the sensors are importing our test data state = hass.states.get("sensor.mail_usps_mail") From 69268088fd4fcda70bb53e5b2d32b01ce71680ab Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 12 Jan 2022 14:39:04 -0700 Subject: [PATCH 35/88] fix: strip > from forwarded amazon date capture --- custom_components/mail_and_packages/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 227b3c7b..953fdbbc 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -1290,7 +1290,7 @@ def get_items( elif email_msg.find("Per tracciare il tuo pacco") != -1: end = email_msg.find("Per tracciare il tuo pacco") - arrive_date = email_msg[start:end].strip() + arrive_date = email_msg[start:end].strip().replace(">", "") arrive_date = arrive_date.split(" ") arrive_date = arrive_date[0:3] # arrive_date[2] = arrive_date[2][:3] From 3aa30415ffe0ab34bc55f0e96aeeb154ac323e31 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 12 Jan 2022 17:56:39 -0700 Subject: [PATCH 36/88] tests: add test for updated code --- .../mail_and_packages/helpers.py | 7 +- tests/conftest.py | 24 ++++ tests/test_emails/amazon_fwd.eml | 130 ++++++++++++++++++ tests/test_helpers.py | 5 + 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 tests/test_emails/amazon_fwd.eml diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 953fdbbc..7d92c97d 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -1267,6 +1267,8 @@ def get_items( continue email_msg = email_msg.decode("utf-8", "ignore") + _LOGGER.debug("RAW EMAIL: %s", email_msg) + # Check message body for order number if ( (found := pattern.findall(email_msg)) @@ -1289,8 +1291,11 @@ def get_items( end = email_msg.find("Track your") elif email_msg.find("Per tracciare il tuo pacco") != -1: end = email_msg.find("Per tracciare il tuo pacco") + elif email_msg.find("View or manage order") != -1: + end = email_msg.find("View or manage order") - arrive_date = email_msg[start:end].strip().replace(">", "") + arrive_date = email_msg[start:end].replace(">", "").strip() + _LOGGER.debug("First pass: %s", arrive_date) arrive_date = arrive_date.split(" ") arrive_date = arrive_date[0:3] # arrive_date[2] = arrive_date[2][:3] diff --git a/tests/conftest.py b/tests/conftest.py index a455631f..baf2026c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1229,6 +1229,30 @@ def mock_imap_search_error_none(): mock_conn.select.return_value = ("OK", []) yield mock_conn +@pytest.fixture() +def mock_imap_amazon_fwd(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_amazon_fwd: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_amazon_fwd.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/amazon_fwd.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + @pytest.fixture(autouse=True) def auto_enable_custom_integrations(enable_custom_integrations): diff --git a/tests/test_emails/amazon_fwd.eml b/tests/test_emails/amazon_fwd.eml new file mode 100644 index 00000000..497c370e --- /dev/null +++ b/tests/test_emails/amazon_fwd.eml @@ -0,0 +1,130 @@ +From: Test User +Content-Type: multipart/alternative; + boundary="Apple-Mail=_927437CD-D5BC-4014-BBB7-F16723EF2FAA" +Mime-Version: 1.0 (Mac OS X Mail 15.0 \(3693.40.0.1.81\)) +Subject: Fwd: Your Amazon.com order #123-1234567-1234567 +To: Test User +Date: Mon, 10 Jan 2022 16:25:17 -0500 + + +--Apple-Mail=_927437CD-D5BC-4014-BBB7-F16723EF2FAA +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=utf-8 + + + +> Begin forwarded message: +>=20 +> From: "Amazon.com" +> Subject: Your Amazon.com order #123-1234567-1234567 +> Date: January 7, 2022 at 10:23:16 AM EST +> To: testuser@email.com +> Reply-To: no-reply@amazon.com +>=20 +>=20 +>=20 +>=20 +> = +=09= + +> Order Confirmation +>=20 +> Hello TestUser, +>=20 +> Thank you for shopping with us. We=E2=80=99ll send a confirmation when = +your item ships.=20 +>=20 +> Details=20 +> Order #123-1234567-1234567 = + +> Arriving:=20 +> Tuesday, January 11=20 +>=20 +>=20 +> View or manage order = +=09 +> Ship to:=20 +> Mark=20 +> Hometown, VA=20 +>=20 +> Order Total:=20 +> $80.61 +>=20 +>=20 +> We hope to see you again soon.=20 +>=20 +> Amazon.com +> Top picks for you +> =09 +> USB Extension Cable 10FT Type A Male... +>=20 +> $8.99 +> = + =09 +> Anker 4-Port USB 3.0 Hub, Ultra-Slim... +>=20 +> $16.99 +> = + +> By placing your order, you agree to Amazon.com = +=E2=80=99s Privacy Notice = + = +and Conditions of Use = +. = +Unless otherwise noted, items sold by Amazon.com = +are subject to sales tax in select states in accordance with the = +applicable laws of that state. If your order contains one or more items = +from a seller other than Amazon.com , it may be = +subject to state and local sales tax, depending upon the seller's = +business policies and the location of their operations. Learn more about = +tax and seller information = +.=20= + +>=20 +> This email was sent from a notification-only address that cannot = +accept incoming email. Please do not reply to this message. +>=20 +>=20 + + +--Apple-Mail=_927437CD-D5BC-4014-BBB7-F16723EF2FAA diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 954bc213..493f7d68 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1098,3 +1098,8 @@ async def test_email_search_none(mock_imap_search_error_none, caplog): mock_imap_search_error_none, "fake@eamil.address", "01-Jan-20" ) assert result == ("OK", [b""]) + +async def test_amazon_shipped_fwd(hass, mock_imap_amazon_fwd,caplog): + result = get_items(mock_imap_amazon_fwd, "order") + assert result == ["123-1234567-1234567"] + assert "Arrive Date: Tuesday, January 11" in caplog.text \ No newline at end of file From c7fac627d7435188c1c8de04d4514e539c98b1d6 Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Thu, 13 Jan 2022 08:46:50 +0100 Subject: [PATCH 37/88] Add Poczta Polska, InPost.pl, DPD Poland, DHL, GLS and Amazon.pl --- custom_components/mail_and_packages/const.py | 217 +++++- tests/conftest.py | 50 ++ tests/const.py | 60 ++ tests/test_config_flow.py | 140 ++++ tests/test_emails/inpostpl_delivered.eml | 725 ++++++++++++++++++ .../test_emails/inpostpl_out_for_delivery.eml | 0 .../pocztapolska_out_for_delivery.eml | 25 + tests/test_sensor.py | 20 + 8 files changed, 1228 insertions(+), 9 deletions(-) create mode 100644 tests/test_emails/inpostpl_delivered.eml create mode 100644 tests/test_emails/inpostpl_out_for_delivery.eml create mode 100644 tests/test_emails/pocztapolska_out_for_delivery.eml diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 26fa561b..9b7291c3 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -68,8 +68,10 @@ DEFAULT_AMAZON_DAYS = 3 # Amazon -AMAZON_DOMAINS = "amazon.com,amazon.ca,amazon.co.uk,amazon.in,amazon.de,amazon.it" -AMAZON_DELIVERED_SUBJECT = ["Delivered: Your", "Consegna effettuata:"] +AMAZON_DOMAINS = ( + "amazon.com,amazon.ca,amazon.co.uk,amazon.in,amazon.de,amazon.it,amazon.pl" +) +AMAZON_DELIVERED_SUBJECT = ["Delivered: Your", "Consegna effettuata:", "Dostarczono: "] AMAZON_SHIPMENT_TRACKING = ["shipment-tracking", "conferma-spedizione"] AMAZON_EMAIL = "order-update@" AMAZON_PACKAGES = "amazon_packages" @@ -84,7 +86,7 @@ AMAZON_HUB_SUBJECT = "ready for pickup from Amazon Hub Locker" AMAZON_HUB_SUBJECT_SEARCH = "(You have a package to pick up)(.*)(\\d{6})" AMAZON_HUB_BODY = "(Your pickup code is )(\\d{6})" -AMAZON_TIME_PATTERN = "will arrive:,estimated delivery date is:,guaranteed delivery date is:,Arriving:,Arriverà:,arriving:" +AMAZON_TIME_PATTERN = "will arrive:,estimated delivery date is:,guaranteed delivery date is:,Arriving:,Arriverà:,arriving:,Dostawa:" AMAZON_EXCEPTION_SUBJECT = "Delivery update:" AMAZON_EXCEPTION_BODY = "running late" AMAZON_EXCEPTION = "amazon_exception" @@ -94,6 +96,7 @@ # Sensor Data SENSOR_DATA = { + # USPS "usps_delivered": { "email": ["auto-reply@usps.com"], "subject": ["Item Delivered"], @@ -117,6 +120,7 @@ ], "subject": ["Your Daily Digest"], }, + # UPS "ups_delivered": { "email": ["mcinfo@ups.com"], "subject": [ @@ -138,6 +142,7 @@ }, "ups_packages": {}, "ups_tracking": {"pattern": ["1Z?[0-9A-Z]{16}"]}, + # FedEx "fedex_delivered": { "email": ["TrackingUpdates@fedex.com", "fedexcanada@fedex.com"], "subject": [ @@ -156,6 +161,7 @@ }, "fedex_packages": {}, "fedex_tracking": {"pattern": ["\\d{12,20}"]}, + # Canada Post "capost_delivered": { "email": ["donotreply@canadapost.postescanada.ca"], "subject": [ @@ -165,23 +171,37 @@ "capost_delivering": {}, "capost_packages": {}, "capost_tracking": {}, + # DHL "dhl_delivered": { - "email": ["donotreply_odd@dhl.com", "NoReply.ODD@dhl.com", "noreply@dhl.de"], + "email": [ + "donotreply_odd@dhl.com", + "NoReply.ODD@dhl.com", + "noreply@dhl.de", + "pl.no.reply@dhl.com", + ], "subject": [ "DHL On Demand Delivery", + "Powiadomienie o przesyłce", ], - "body": ["has been delivered"], + "body": ["has been delivered", "została doręczona"], }, "dhl_delivering": { - "email": ["donotreply_odd@dhl.com", "NoReply.ODD@dhl.com", "noreply@dhl.de"], + "email": [ + "donotreply_odd@dhl.com", + "NoReply.ODD@dhl.com", + "noreply@dhl.de", + "pl.no.reply@dhl.com", + ], "subject": [ "DHL On Demand Delivery", "paket kommt heute", + "Powiadomienie o przesyłce", ], - "body": ["scheduled for delivery TODAY"], + "body": ["scheduled for delivery TODAY", "zostanie dziś do Państwa doręczona"], }, "dhl_packages": {}, - "dhl_tracking": {"pattern": ["number \\d{10} from"]}, + "dhl_tracking": {"pattern": ["\\d{10}"]}, + # Hermes.co.uk "hermes_delivered": { "email": ["donotreply@myhermes.co.uk"], "subject": ["Hermes has successfully delivered your"], @@ -192,6 +212,7 @@ }, "hermes_packages": {}, "hermes_tracking": {"pattern": ["\\d{16}"]}, + # Royal Mail "royal_delivered": { "email": ["no-reply@royalmail.com"], "subject": ["has been delivered"], @@ -202,6 +223,93 @@ }, "royal_packages": {}, "royal_tracking": {"pattern": ["[A-Za-z]{2}[0-9]{9}GB"]}, + # Poczta Polska SA + "pocztapolska_delivered": {}, + "pocztapolska_delivering": { + "email": ["informacja@poczta-polska.pl", "powiadomienia@allegromail.pl"], + "subject": ["Poczta Polska S.A. eINFO"], + }, + "pocztapolska_packages": {}, + "pocztapolska_tracking": { + # http://emonitoring.poczta-polska.pl/?numer=00359007738913296666 + "pattern": ["\\d{20}"] + }, + # InPost.pl + "inpostpl_delivered": { + "email": ["powiadomienia@inpost.pl", "powiadomienia@allegromail.pl"], + "subject": [ + "InPost - Potwierdzenie odbioru przesyłki", + "InPost - Paczka umieszczona w Paczkomacie", + ], + }, + "inpostpl_delivering": { + "email": ["powiadomienia@inpost.pl"], + "subject": ["paczka jest w drodze", "jest już prawie u Ciebie"], + }, + "inpostpl_packages": {}, + "inpostpl_tracking": { + # https://inpost.pl/sledzenie-przesylek?number=520113017830399002575123 + "pattern": ["\\d{24}"] + }, + # DPD Poland + "dpdcompl_delivered": { + "email": [ + "KurierDPD0@dpd.com.pl", + "KurierDPD1@dpd.com.pl", + "KurierDPD2@dpd.com.pl", + "KurierDPD3@dpd.com.pl", + "KurierDPD4@dpd.com.pl", + "KurierDPD5@dpd.com.pl", + "KurierDPD6@dpd.com.pl", + "KurierDPD7@dpd.com.pl", + "KurierDPD8@dpd.com.pl", + "KurierDPD9@dpd.com.pl", + "KurierDPD10@dpd.com.pl", + ], + "subject": [ + "została doręczona", + ], + }, + "dpdcompl_delivering": { + "email": [ + "KurierDPD0@dpd.com.pl", + "KurierDPD1@dpd.com.pl", + "KurierDPD2@dpd.com.pl", + "KurierDPD3@dpd.com.pl", + "KurierDPD4@dpd.com.pl", + "KurierDPD5@dpd.com.pl", + "KurierDPD6@dpd.com.pl", + "KurierDPD7@dpd.com.pl", + "KurierDPD8@dpd.com.pl", + "KurierDPD9@dpd.com.pl", + "KurierDPD10@dpd.com.pl", + ], + "subject": ["Bezpieczne doręczenie"], + "body": ["Dziś doręczamy"], + }, + "dpdcompl_packages": {}, + "dpdcompl_tracking": { + # https://tracktrace.dpd.com.pl/parcelDetails?p1=13490015284111 + "pattern": ["\\d{13}[A-Z0-9]{1,2}"], + }, + # GLS + "gls_delivered": { + "email": ["noreply@gls-group.eu"], + "subject": [ + "informacja o dostawie", + ], + "body": ["została dzisiaj dostarczona"], + }, + "gls_delivering": { + "email": ["noreply@gls-group.eu"], + "subject": ["paczka w drodze"], + }, + "gls_packages": {}, + "gls_tracking": { + # https://gls-group.eu/GROUP/en/parcel-tracking?match=51687952111 + "pattern": ["\\d{11}"] + }, + # Australia Post "auspost_delivered": { "email": ["noreply@notifications.auspost.com.au"], "subject": ["Your shipment has been delivered"], @@ -223,6 +331,7 @@ entity_category=ENTITY_CATEGORY_DIAGNOSTIC, device_class=SensorDeviceClass.TIMESTAMP, ), + # USPS "usps_mail": SensorEntityDescription( name="Mail USPS Mail", native_unit_of_measurement="piece(s)", @@ -253,6 +362,7 @@ icon="mdi:package-variant-closed", key="usps_packages", ), + # UPS "ups_delivered": SensorEntityDescription( name="Mail UPS Delivered", native_unit_of_measurement="package(s)", @@ -277,6 +387,7 @@ icon="mdi:package-variant-closed", key="ups_packages", ), + # FedEx "fedex_delivered": SensorEntityDescription( name="Mail FedEx Delivered", native_unit_of_measurement="package(s)", @@ -295,6 +406,7 @@ icon="mdi:package-variant-closed", key="fedex_packages", ), + # Amazon "amazon_packages": SensorEntityDescription( name="Mail Amazon Packages", native_unit_of_measurement="package(s)", @@ -319,6 +431,7 @@ icon="mdi:package", key="amazon_hub", ), + # Canada Post "capost_delivered": SensorEntityDescription( name="Mail Canada Post Delivered", native_unit_of_measurement="package(s)", @@ -337,6 +450,7 @@ icon="mdi:package-variant-closed", key="capost_packages", ), + # DHL "dhl_delivered": SensorEntityDescription( name="Mail DHL Delivered", native_unit_of_measurement="package(s)", @@ -355,6 +469,7 @@ icon="mdi:package-variant-closed", key="dhl_packages", ), + # Hermes "hermes_delivered": SensorEntityDescription( name="Mail Hermes Delivered", native_unit_of_measurement="package(s)", @@ -373,6 +488,7 @@ icon="mdi:package-variant-closed", key="hermes_packages", ), + # Royal Mail "royal_delivered": SensorEntityDescription( name="Mail Royal Mail Delivered", native_unit_of_measurement="package(s)", @@ -391,6 +507,7 @@ icon="mdi:package-variant-closed", key="royal_packages", ), + # Australia Post "auspost_delivered": SensorEntityDescription( name="Mail AusPost Delivered", native_unit_of_measurement="package(s)", @@ -409,6 +526,76 @@ icon="mdi:package-variant-closed", key="auspost_packages", ), + # Poczta Polska SA + "pocztapolska_delivering": SensorEntityDescription( + name="Poczta Polska SA Packages Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="pocztapolska_delivering", + ), + "pocztapolska_packages": SensorEntityDescription( + name="Poczta Polska SA Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="pocztapolska_packages", + ), + "inpostpl_delivering": SensorEntityDescription( + name="InPost.pl Packages Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="inpostpl_delivering", + ), + # InPost.pl + "inpostpl_delivered": SensorEntityDescription( + name="InPost.pl Packages Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant", + key="inpostpl_delivered", + ), + "inpostpl_packages": SensorEntityDescription( + name="InPost.pl Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="inpostpl_packages", + ), + # DPD Poland + "dpdcompl_delivering": SensorEntityDescription( + name="DPD.com.pl Packages Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="dpdcompl_delivering", + ), + "dpdcompl_delivered": SensorEntityDescription( + name="DPD.com.pl Packages Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant", + key="dpdcompl_delivered", + ), + "dpdcompl_packages": SensorEntityDescription( + name="DPD.com.pl Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="dpdcompl_packages", + ), + # GLS + "gls_delivering": SensorEntityDescription( + name="GLS Packages Delivering", + native_unit_of_measurement="package(s)", + icon="mdi:truck-delivery", + key="gls_delivering", + ), + "gls_delivered": SensorEntityDescription( + name="GLS Packages Delivered", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant", + key="dpdcompl_delivered", + ), + "gls_packages": SensorEntityDescription( + name="GLS Packages", + native_unit_of_measurement="package(s)", + icon="mdi:package-variant-closed", + key="gls_packages", + ), ### # !!! Insert new sensors above these two !!! ### @@ -453,4 +640,16 @@ SENSOR_ICON = 2 # For sensors with delivering and delivered statuses -SHIPPERS = ["capost", "dhl", "fedex", "ups", "usps", "hermes", "royal", "auspost"] +SHIPPERS = [ + "capost", + "dhl", + "fedex", + "ups", + "usps", + "hermes", + "royal", + "auspost", + "inpostpl", + "dpdcompl", + "gls", +] diff --git a/tests/conftest.py b/tests/conftest.py index 5a0e470d..447dfbde 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1210,6 +1210,56 @@ def mock_imap_auspost_delivered(): yield mock_conn +@pytest.fixture() +def mock_imap_inpostpl_out_for_delivery(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_inpostpl_out_for_delivery: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_inpostpl_out_for_delivery.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/inpostpl_out_for_delivery.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + + +@pytest.fixture() +def mock_imap_inpostpl_delivered(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_inpostpl_delivered: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_inpostpl_delivered.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/inpostpl_delivered.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + + @pytest.fixture() def mock_imap_search_error_none(): """Mock imap class values.""" diff --git a/tests/const.py b/tests/const.py index 3e6ee019..e9eae482 100644 --- a/tests/const.py +++ b/tests/const.py @@ -33,6 +33,11 @@ "auspost_delivered", "auspost_packages", "auspost_delivering", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -83,6 +88,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -133,6 +143,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -183,6 +198,11 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -234,6 +254,11 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -280,6 +305,11 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -325,6 +355,11 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -368,6 +403,11 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -406,6 +446,11 @@ "auspost_delivered": 2, "auspost_delivering": 1, "auspost_packages": 3, + "pocztapolska_delivering": 1, + "pocztapolska_packages": 1, + "inpostpl_delivered": 2, + "inpostpl_delivering": 1, + "inpostpl_packages": 3, } FAKE_CONFIG_DATA_MISSING_TIMEOUT = { @@ -450,6 +495,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -499,6 +549,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -549,6 +604,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 1760a97a..4a2ec2a9 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -61,6 +61,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "config_3", @@ -105,6 +110,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -213,6 +223,11 @@ async def test_form( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "config_3", @@ -257,6 +272,11 @@ async def test_form( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -402,6 +422,11 @@ async def test_form_connection_error(input_1, step_id_2, hass, mock_imap): "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -439,6 +464,11 @@ async def test_form_connection_error(input_1, step_id_2, hass, mock_imap): "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -527,6 +557,11 @@ async def test_form_invalid_ffmpeg( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -566,6 +601,11 @@ async def test_form_invalid_ffmpeg( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -663,6 +703,11 @@ async def test_form_index_error( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -702,6 +747,11 @@ async def test_form_index_error( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -798,6 +848,11 @@ async def test_form_index_error_2( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -837,6 +892,11 @@ async def test_form_index_error_2( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -961,6 +1021,11 @@ async def test_imap_login_error(mock_imap_login_error, caplog): "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "options_3", @@ -1008,6 +1073,11 @@ async def test_imap_login_error(mock_imap_login_error, caplog): "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -1127,6 +1197,11 @@ async def test_options_flow( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "options_3", @@ -1174,6 +1249,11 @@ async def test_options_flow( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -1346,6 +1426,11 @@ async def test_options_flow_connection_error( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -1385,6 +1470,11 @@ async def test_options_flow_connection_error( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -1484,6 +1574,11 @@ async def test_options_flow_invalid_ffmpeg( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -1523,6 +1618,11 @@ async def test_options_flow_invalid_ffmpeg( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -1623,6 +1723,11 @@ async def test_options_flow_index_error( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -1662,6 +1767,11 @@ async def test_options_flow_index_error( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -1762,6 +1872,11 @@ async def test_options_flow_index_error_2( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -1801,6 +1916,11 @@ async def test_options_flow_index_error_2( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -1899,6 +2019,11 @@ async def test_options_flow_mailbox_format2( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, "imap.test.email", @@ -1941,6 +2066,11 @@ async def test_options_flow_mailbox_format2( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -2047,6 +2177,11 @@ async def test_options_flow_bad( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), @@ -2136,6 +2271,11 @@ async def test_form_amazon_error( "auspost_delivered", "auspost_delivering", "auspost_packages", + "pocztapolska_delivering", + "pocztapolska_packages", + "inpostpl_delivered", + "inpostpl_delivering", + "inpostpl_packages", ], }, ), diff --git a/tests/test_emails/inpostpl_delivered.eml b/tests/test_emails/inpostpl_delivered.eml new file mode 100644 index 00000000..50138412 --- /dev/null +++ b/tests/test_emails/inpostpl_delivered.eml @@ -0,0 +1,725 @@ + +------=_Part_18559691_431614451.1641551203581 +Content-Type: text/html;charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + + + + + + + + + + + + + + + + + + + + + InPost - Paczka umieszczona w Paczkomacie + + + + + =20 + + +
+
+ + =20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +=3D"InPost +
+ + + + + + +
+ 3D"InPost" + + + + + + + + + +
+ Przysz=C5=82o! +
+ +
+
+
+ + + + +
+ Pora na Tw=C3=B3j ruch! + Paczka od:
+John Doe
+czeka w Paczkomacie
+ABC33A
+Nazwa i Orientacyjny Adres Paczkomatu

+ Paczka pozostanie w Paczkomacie przez 48 godzin. + Z uwagi na obecn=C4=85 sytuacj=C4=99 zwi=C4=85zan=C4=85 z zagro=C5=BCeniem= + epidemicznym, je=C5=9Bli nie odbierzesz jej w tym czasie, zostanie zwr=C3= +=B3cona do Nadawcy.

+Widzimy si=C4=99?
<= +/td> +
+
+ + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+ Poka=C5=BC na mapie + + 3D"" +
+
+
+
+
+ + + + + + + + + + + + +
+
+ + + + + + + +
+ + + + + + + + +
+ Czas na odbi=C3=B3r:
+ = + 48 godzin = +
3D""
Hal= +oo, Haloo, Ja tu czekam!
+
+
+ + + + + + + + + + + + +
+
+ + + + + + + + + + + +
+ Aby odebra=C4=87 paczk=C4=99, zeskanuj kod QR, przyk=C5= +=82adaj=C4=85c go do czytnika w Paczkomacie: +
3D"kod=
+
+ + + + + + + + + + + +
lub wpisz poni=C5=BCsze dane= +:
+ Numer telefonu
+ 666777= +888

+ Kod odbioru
+ 777888= + +
+
+ + + + + + + + + + + + +
+ + + + + + + +
+ Numer paczki: +
+ 609999999990999918999993 = +
+
+
+ + + + + + +
+ 3D"InPost" + + + + + + + + + + + +
+ Oszcz=C4=99d=C5= +=BA CO2 + =C5=9Bwiatu, zamawiaj do Paczkomatu +
+ +
+
+
+ + + + + + + + + +
+ + + + + + + + + + + + + +
+ Odbieraj paczki wygodniej za pomoc=C4=85 apl= +ikacji InPost +
+ + + + + +
+ 3D"Google + + 3D"App +
+
+
+
+ + + + + + + + + + + + +
+
+ + + + + + + + + +
+ + + + + + + + + + + + + +
+ Inne Paczkomaty w pobli=C5=BCu + Je=C5=9Bli chcesz, mo=C5=BCesz odebra= +=C4=87 nast=C4=99pn=C4=85 paczk=C4=99 tam, gdzie Ci wygodniej. +
+ + = + + + + + = + + + + + = + + + + + +
+ 1.64km + + ABC08M
Some Other= +Place
+ 1.88km + + ABC157M
Also= +A Different Place
3D""Poka=C5=BC= + na mapie
+
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Masz pytani= +a?
=C5=9Amia=C5=82o!
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ N= +apisz wiadomo=C5=9B=C4=87 +
722 4= +44 000
74 66= +0 00 00
+
+ + + + + + + + + + + + +
+ + + + + + + + + +
+
+ + + + + + + + + + +
+
+ Uwa=C5=BCaj na fa=C5= +=82szywe wiadomo=C5=9Bci +
Wiadomo=C5=9B=C4=87 wygenerowana aut= +omatycznie, prosimy na ni=C4=85 nie odpowiada=C4=87. + Regulamin<= +/a> +=09=09=09=09=09=09=09=09

+=09=09=09=09=09=09=09=09Administratorem danych osobowych jest InPost Sp. z = +o. o. z siedzib=C4=85 ul. Wielicka 28 30-552 Krak=C3=B3w. Wi=C4=99cej infor= +macji na
inpost.pl +
+
+ 3D"InPost +
+
+
+
+ + + +------=_Part_18559691_431614451.1641551203581-- diff --git a/tests/test_emails/inpostpl_out_for_delivery.eml b/tests/test_emails/inpostpl_out_for_delivery.eml new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_emails/pocztapolska_out_for_delivery.eml b/tests/test_emails/pocztapolska_out_for_delivery.eml new file mode 100644 index 00000000..71c201cf --- /dev/null +++ b/tests/test_emails/pocztapolska_out_for_delivery.eml @@ -0,0 +1,25 @@ + +Dzień dobry, + +Dziś doręczymy Twoją przesyłkę 00111222333444555666. + +Uzgodnij bezpieczną dostawę. Kurier tel. 555666777. + +Odbierz za pomoca kodu odbioru: 433566 + +Przesyłki (z zastrzeżeniem przesyłek z dokumentami zwrotnymi) możesz odebrać bez pokwitowania. Okaż kurierowi z bezpiecznej odległości dokument tożsamości np. dowód osobisty, paszport, prawo jazdy. Kurier spisze 4 ostatnie cyfry numeru dokumentu. + +W przypadku przesyłek pobraniowych polecamy płatność kartą. + +Szczegółowe informacje o przesyłce: http://emonitoring.poczta-polska.pl/?numer=00111222333444555666 + +Polecamy zakupy on-line i bezpieczną formę dostawy pod adres realizowaną przez Pocztę Polską. + +Szczegółowe informacje dotyczące specjalnych procedur: +www.poczta-polska.pl; www.pocztex.pl; www.facebook.com/pocztapolska. + +Niniejsze powiadomienie zostało wygenerowane automatycznie. + +Pozdrawiamy, + +Poczta Polska S.A. diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 5fff3548..52511315 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -108,6 +108,26 @@ async def test_sensor(hass, mock_update): assert state assert state.state == "3" + state = hass.states.get("sensor.mail_pocztapolska_delivering") + assert state + assert state.state == "1" + + state = hass.states.get("sensor.mail_pocztapolska_packages") + assert state + assert state.state == "1" + + state = hass.states.get("sensor.mail_inpostpl_delivered") + assert state + assert state.state == "2" + + state = hass.states.get("sensor.mail_inpostpl_delivering") + assert state + assert state.state == "1" + + state = hass.states.get("sensor.mail_inpostpl_packages") + assert state + assert state.state == "3" + state = hass.states.get("sensor.mail_packages_delivered") assert state assert state.state == "7" From c361dd69107cfa0a350268d839ea57505f74fcb4 Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Wed, 19 Jan 2022 01:53:32 +0100 Subject: [PATCH 38/88] Add lots of tests, dev container --- .devcontainer/README.md | 60 +++ .devcontainer/configuration.yaml | 9 + .devcontainer/devcontainer.json | 31 ++ .vscode/launch.json | 35 ++ .vscode/tasks.json | 29 ++ custom_components/mail_and_packages/const.py | 116 +++--- .../mail_and_packages/helpers.py | 18 +- custom_components/mail_and_packages/sensor.py | 3 +- tests/conftest.py | 66 ++- tests/const.py | 196 ++++++--- tests/test_config_flow.py | 280 ++++++------- tests/test_emails/dpd_com_pl_delivering.eml | 379 ++++++++++++++++++ tests/test_emails/gls_delivered.eml | 75 ++++ tests/test_emails/gls_delivering.eml | 83 ++++ ..._delivered.eml => inpost_pl_delivered.eml} | 4 +- ...ery.eml => inpost_pl_out_for_delivery.eml} | 0 ...ivery.eml => poczta_polska_delivering.eml} | 16 +- tests/test_helpers.py | 21 +- tests/test_init.py | 14 +- tests/test_sensor.py | 34 +- 20 files changed, 1178 insertions(+), 291 deletions(-) create mode 100644 .devcontainer/README.md create mode 100644 .devcontainer/configuration.yaml create mode 100644 .devcontainer/devcontainer.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 tests/test_emails/dpd_com_pl_delivering.eml create mode 100644 tests/test_emails/gls_delivered.eml create mode 100644 tests/test_emails/gls_delivering.eml rename tests/test_emails/{inpostpl_delivered.eml => inpost_pl_delivered.eml} (99%) rename tests/test_emails/{inpostpl_out_for_delivery.eml => inpost_pl_out_for_delivery.eml} (100%) rename tests/test_emails/{pocztapolska_out_for_delivery.eml => poczta_polska_delivering.eml} (69%) diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 00000000..e304a9a5 --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,60 @@ +## Developing with Visual Studio Code + devcontainer + +The easiest way to get started with custom integration development is to use Visual Studio Code with devcontainers. This approach will create a preconfigured development environment with all the tools you need. + +In the container you will have a dedicated Home Assistant core instance running with your custom component code. You can configure this instance by updating the `./devcontainer/configuration.yaml` file. + +**Prerequisites** + +- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- Docker + - For Linux, macOS, or Windows 10 Pro/Enterprise/Education use the [current release version of Docker](https://docs.docker.com/install/) + - Windows 10 Home requires [WSL 2](https://docs.microsoft.com/windows/wsl/wsl2-install) and the current Edge version of Docker Desktop (see instructions [here](https://docs.docker.com/docker-for-windows/wsl-tech-preview/)). This can also be used for Windows Pro/Enterprise/Education. +- [Visual Studio code](https://code.visualstudio.com/) +- [Remote - Containers (VSC Extension)][extension-link] + +[More info about requirements and devcontainer in general](https://code.visualstudio.com/docs/remote/containers#_getting-started) + +[extension-link]: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers + +**Getting started:** + +1. Fork the repository. +2. Clone the repository to your computer. +3. Open the repository using Visual Studio code. + +When you open this repository with Visual Studio code you are asked to "Reopen in Container", this will start the build of the container. + +_If you don't see this notification, open the command palette and select `Remote-Containers: Reopen Folder in Container`._ + +### Tasks + +The devcontainer comes with some useful tasks to help you with development, you can start these tasks by opening the command palette and select `Tasks: Run Task` then select the task you want to run. + +When a task is currently running (like `Run Home Assistant on port 9123` for the docs), it can be restarted by opening the command palette and selecting `Tasks: Restart Running Task`, then select the task you want to restart. + +The available tasks are: + +Task | Description +-- | -- +Run Home Assistant on port 9123 | Launch Home Assistant with your custom component code and the configuration defined in `.devcontainer/configuration.yaml`. +Run Home Assistant configuration against /config | Check the configuration. +Upgrade Home Assistant to latest dev | Upgrade the Home Assistant core version in the container to the latest version of the `dev` branch. +Install a specific version of Home Assistant | Install a specific version of Home Assistant core in the container. + +### Step by Step debugging + +With the development container, +you can test your custom component in Home Assistant with step by step debugging. + +You need to modify the `configuration.yaml` file in `.devcontainer` folder +by uncommenting the line: + +```yaml +# debugpy: +``` + +Then launch the task `Run Home Assistant on port 9123`, and launch the debugger +with the existing debugging configuration `Python: Attach Local`. + +For more information, look at [the Remote Python Debugger integration documentation](https://www.home-assistant.io/integrations/debugpy/). diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml new file mode 100644 index 00000000..3f5b5aef --- /dev/null +++ b/.devcontainer/configuration.yaml @@ -0,0 +1,9 @@ +default_config: + +logger: + default: info + logs: + custom_components.mail_and_packages: debug + +# If you need to debug uncomment the line below (doc: https://www.home-assistant.io/integrations/debugpy/) +debugpy: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..f36c2ce1 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// See https://aka.ms/vscode-remote/devcontainer.json for format details. +{ + "image": "ludeeus/container:integration-debian", + "name": "Mail And Packages integration development", + "context": "..", + "appPort": [ + "9123:8123", + "5678:5678" + ], + "postCreateCommand": "container install", + "extensions": [ + "ms-python.python", + "github.vscode-pull-request-github", + "ryanluker.vscode-coverage-gutters", + "ms-python.vscode-pylance" + ], + "settings": { + "files.eol": "\n", + "editor.tabSize": 4, + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/bin/python3", + "python.analysis.autoSearchPaths": false, + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "python.formatting.provider": "black", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true + } +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..555a62b8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + // Example of attaching to local debug server + "name": "Python: Attach Local", + "type": "python", + "request": "attach", + "port": 5678, + "host": "localhost", + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "." + } + ] + }, + { + // Example of attaching to my production server + "name": "Python: Attach Remote", + "type": "python", + "request": "attach", + "port": 5678, + "host": "homeassistant.local", + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/usr/src/homeassistant" + } + ] + } + ] + } + \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..7ab4ba81 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,29 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run Home Assistant on port 9123", + "type": "shell", + "command": "container start", + "problemMatcher": [] + }, + { + "label": "Run Home Assistant configuration against /config", + "type": "shell", + "command": "container check", + "problemMatcher": [] + }, + { + "label": "Upgrade Home Assistant to latest dev", + "type": "shell", + "command": "container install", + "problemMatcher": [] + }, + { + "label": "Install a specific version of Home Assistant", + "type": "shell", + "command": "container set-version", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 9b7291c3..f9a48149 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -224,35 +224,35 @@ "royal_packages": {}, "royal_tracking": {"pattern": ["[A-Za-z]{2}[0-9]{9}GB"]}, # Poczta Polska SA - "pocztapolska_delivered": {}, - "pocztapolska_delivering": { + "poczta_polska_delivered": {}, + "poczta_polska_delivering": { "email": ["informacja@poczta-polska.pl", "powiadomienia@allegromail.pl"], "subject": ["Poczta Polska S.A. eINFO"], }, - "pocztapolska_packages": {}, - "pocztapolska_tracking": { + "poczta_polska_packages": {}, + "poczta_polska_tracking": { # http://emonitoring.poczta-polska.pl/?numer=00359007738913296666 "pattern": ["\\d{20}"] }, # InPost.pl - "inpostpl_delivered": { + "inpost_pl_delivered": { "email": ["powiadomienia@inpost.pl", "powiadomienia@allegromail.pl"], "subject": [ "InPost - Potwierdzenie odbioru przesyłki", "InPost - Paczka umieszczona w Paczkomacie", ], }, - "inpostpl_delivering": { - "email": ["powiadomienia@inpost.pl"], + "inpost_pl_delivering": { + "email": ["powiadomienia@inpost.pl", "powiadomienia@allegromail.pl"], "subject": ["paczka jest w drodze", "jest już prawie u Ciebie"], }, - "inpostpl_packages": {}, - "inpostpl_tracking": { + "inpost_pl_packages": {}, + "inpost_pl_tracking": { # https://inpost.pl/sledzenie-przesylek?number=520113017830399002575123 "pattern": ["\\d{24}"] }, # DPD Poland - "dpdcompl_delivered": { + "dpd_com_pl_delivered": { "email": [ "KurierDPD0@dpd.com.pl", "KurierDPD1@dpd.com.pl", @@ -265,12 +265,13 @@ "KurierDPD8@dpd.com.pl", "KurierDPD9@dpd.com.pl", "KurierDPD10@dpd.com.pl", + "powiadomienia@allegromail.pl", ], "subject": [ "została doręczona", ], }, - "dpdcompl_delivering": { + "dpd_com_pl_delivering": { "email": [ "KurierDPD0@dpd.com.pl", "KurierDPD1@dpd.com.pl", @@ -283,26 +284,38 @@ "KurierDPD8@dpd.com.pl", "KurierDPD9@dpd.com.pl", "KurierDPD10@dpd.com.pl", + "powiadomienia@allegromail.pl", + ], + "subject": [ + "Bezpieczne_dor=C4=99czenie_Twojej_paczki", + "Bezpieczne doręczenie", + "przesyłka została nadana", ], - "subject": ["Bezpieczne doręczenie"], - "body": ["Dziś doręczamy"], + "body": ["Dzi=C5=9B dor=C4=99czamy", "DPD Polska"], }, - "dpdcompl_packages": {}, - "dpdcompl_tracking": { + "dpd_com_pl_packages": {}, + "dpd_com_pl_tracking": { # https://tracktrace.dpd.com.pl/parcelDetails?p1=13490015284111 "pattern": ["\\d{13}[A-Z0-9]{1,2}"], }, # GLS "gls_delivered": { - "email": ["noreply@gls-group.eu"], + "email": [ + "noreply@gls-group.eu", + "powiadomienia@allegromail.pl", + ], "subject": [ "informacja o dostawie", ], "body": ["została dzisiaj dostarczona"], }, "gls_delivering": { - "email": ["noreply@gls-group.eu"], + "email": [ + "noreply@gls-group.eu", + "powiadomienia@allegromail.pl", + ], "subject": ["paczka w drodze"], + "body": ["Zespół GLS"], }, "gls_packages": {}, "gls_tracking": { @@ -527,71 +540,77 @@ key="auspost_packages", ), # Poczta Polska SA - "pocztapolska_delivering": SensorEntityDescription( - name="Poczta Polska SA Packages Delivering", + # "poczta_polska_delivered": SensorEntityDescription( + # name="Poczta Polska Delivered", + # native_unit_of_measurement="package(s)", + # icon="mdi:package-variant", + # key="poczta_polska_delivered", + # ), + "poczta_polska_delivering": SensorEntityDescription( + name="Mail Poczta Polska Delivering", native_unit_of_measurement="package(s)", icon="mdi:truck-delivery", - key="pocztapolska_delivering", + key="poczta_polska_delivering", ), - "pocztapolska_packages": SensorEntityDescription( - name="Poczta Polska SA Packages", + "poczta_polska_packages": SensorEntityDescription( + name="Mail Poczta Polska Packages", native_unit_of_measurement="package(s)", icon="mdi:package-variant-closed", - key="pocztapolska_packages", + key="poczta_polska_packages", ), - "inpostpl_delivering": SensorEntityDescription( - name="InPost.pl Packages Delivering", + # InPost.pl + "inpost_pl_delivering": SensorEntityDescription( + name="Mail InPost.pl Delivering", native_unit_of_measurement="package(s)", icon="mdi:truck-delivery", - key="inpostpl_delivering", + key="inpost_pl_delivering", ), - # InPost.pl - "inpostpl_delivered": SensorEntityDescription( - name="InPost.pl Packages Delivered", + "inpost_pl_delivered": SensorEntityDescription( + name="Mail InPost.pl Delivered", native_unit_of_measurement="package(s)", icon="mdi:package-variant", - key="inpostpl_delivered", + key="inpost_pl_delivered", ), - "inpostpl_packages": SensorEntityDescription( - name="InPost.pl Packages", + "inpost_pl_packages": SensorEntityDescription( + name="Mail InPost.pl Packages", native_unit_of_measurement="package(s)", icon="mdi:package-variant-closed", - key="inpostpl_packages", + key="inpost_pl_packages", ), # DPD Poland - "dpdcompl_delivering": SensorEntityDescription( - name="DPD.com.pl Packages Delivering", + "dpd_com_pl_delivering": SensorEntityDescription( + name="Mail DPD.com.pl Delivering", native_unit_of_measurement="package(s)", icon="mdi:truck-delivery", - key="dpdcompl_delivering", + key="dpd_com_pl_delivering", ), - "dpdcompl_delivered": SensorEntityDescription( - name="DPD.com.pl Packages Delivered", + "dpd_com_pl_delivered": SensorEntityDescription( + name="Mail DPD.com.pl Delivered", native_unit_of_measurement="package(s)", icon="mdi:package-variant", - key="dpdcompl_delivered", + key="dpd_com_pl_delivered", ), - "dpdcompl_packages": SensorEntityDescription( - name="DPD.com.pl Packages", + "dpd_com_pl_packages": SensorEntityDescription( + name="Mail DPD.com.pl Packages", native_unit_of_measurement="package(s)", icon="mdi:package-variant-closed", - key="dpdcompl_packages", + key="dpd_com_pl_packages", ), # GLS "gls_delivering": SensorEntityDescription( - name="GLS Packages Delivering", + name="Mail GLS Delivering", native_unit_of_measurement="package(s)", icon="mdi:truck-delivery", key="gls_delivering", ), "gls_delivered": SensorEntityDescription( - name="GLS Packages Delivered", + name="Mail GLS Delivered", native_unit_of_measurement="package(s)", icon="mdi:package-variant", - key="dpdcompl_delivered", + key="dpd_com_pl_delivered", ), "gls_packages": SensorEntityDescription( - name="GLS Packages", + name="Mail GLS Packages", native_unit_of_measurement="package(s)", icon="mdi:package-variant-closed", key="gls_packages", @@ -649,7 +668,8 @@ "hermes", "royal", "auspost", - "inpostpl", - "dpdcompl", + "poczta_polska", + "inpost_pl", + "dpd_com_pl", "gls", ] diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 11dbb3cf..d067eecc 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -380,12 +380,12 @@ def fetch( count[sensor] = info[ATTR_COUNT] count[AMAZON_EXCEPTION_ORDER] = info[ATTR_ORDER] elif "_packages" in sensor: - prefix = sensor.split("_")[0] + prefix = sensor.replace("_packages", "") delivering = fetch(hass, config, account, data, f"{prefix}_delivering") delivered = fetch(hass, config, account, data, f"{prefix}_delivered") count[sensor] = delivering + delivered elif "_delivering" in sensor: - prefix = sensor.split("_")[0] + prefix = sensor.replace("_delivering", "") delivered = fetch(hass, config, account, data, f"{prefix}_delivered") info = get_count(account, sensor, True) count[sensor] = max(0, info[ATTR_COUNT] - delivered) @@ -442,7 +442,6 @@ def login( def selectfolder(account: Type[imaplib.IMAP4_SSL], folder: str) -> bool: - """Select folder inside the mailbox""" try: account.list() @@ -857,8 +856,13 @@ def get_count( ) found.append(data[0]) - if ATTR_PATTERN in SENSOR_DATA[f"{sensor_type.split('_')[0]}_tracking"].keys(): - track = SENSOR_DATA[f"{sensor_type.split('_')[0]}_tracking"][ATTR_PATTERN][0] + if ( + ATTR_PATTERN + in SENSOR_DATA[f"{'_'.join(sensor_type.split('_')[:-1])}_tracking"].keys() + ): + track = SENSOR_DATA[f"{'_'.join(sensor_type.split('_')[:-1])}_tracking"][ + ATTR_PATTERN + ][0] if track is not None and get_tracking_num and count > 0: for sdata in found: @@ -1125,7 +1129,9 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> # Get combo number from message body try: - email_msg = quopri.decodestring(str(msg.get_payload(0))) + email_msg = quopri.decodestring( + str(msg.get_payload(0)) + ) # msg.get_payload(0).encode('utf-8') except Exception as err: _LOGGER.debug("Problem decoding email message: %s", str(err)) continue diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index 16ca6999..de290569 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -115,7 +115,7 @@ def available(self) -> bool: def extra_state_attributes(self) -> Optional[str]: """Return device specific state attributes.""" attr = {} - tracking = f"{self.type.split('_')[0]}_tracking" + tracking = f"{'_'.join(self.type.split('_')[:-1])}_tracking" data = self.coordinator.data # Catch no data entries @@ -131,6 +131,7 @@ def extra_state_attributes(self) -> Optional[str]: attr[ATTR_IMAGE] = data[ATTR_IMAGE_NAME] elif "_delivering" in self.type and tracking in self.data.keys(): attr[ATTR_TRACKING_NUM] = data[tracking] + # TODO: Add Tracking URL when applicable return attr diff --git a/tests/conftest.py b/tests/conftest.py index 447dfbde..6b7ed541 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1211,13 +1211,13 @@ def mock_imap_auspost_delivered(): @pytest.fixture() -def mock_imap_inpostpl_out_for_delivery(): +def mock_imap_poczta_polska_delivering(): """Mock imap class values.""" with patch( "custom_components.mail_and_packages.helpers.imaplib" - ) as mock_imap_inpostpl_out_for_delivery: + ) as mock_imap_poczta_polska_delivering: mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) - mock_imap_inpostpl_out_for_delivery.IMAP4_SSL.return_value = mock_conn + mock_imap_poczta_polska_delivering.IMAP4_SSL.return_value = mock_conn mock_conn.login.return_value = ( "OK", @@ -1228,7 +1228,7 @@ def mock_imap_inpostpl_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) - f = open("tests/test_emails/inpostpl_out_for_delivery.eml", "r") + f = open("tests/test_emails/poczta_polska_delivering.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) mock_conn.select.return_value = ("OK", []) @@ -1236,13 +1236,13 @@ def mock_imap_inpostpl_out_for_delivery(): @pytest.fixture() -def mock_imap_inpostpl_delivered(): +def mock_imap_inpost_pl_out_for_delivery(): """Mock imap class values.""" with patch( "custom_components.mail_and_packages.helpers.imaplib" - ) as mock_imap_inpostpl_delivered: + ) as mock_imap_inpost_pl_out_for_delivery: mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) - mock_imap_inpostpl_delivered.IMAP4_SSL.return_value = mock_conn + mock_imap_inpost_pl_out_for_delivery.IMAP4_SSL.return_value = mock_conn mock_conn.login.return_value = ( "OK", @@ -1253,7 +1253,57 @@ def mock_imap_inpostpl_delivered(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) - f = open("tests/test_emails/inpostpl_delivered.eml", "r") + f = open("tests/test_emails/inpost_pl_out_for_delivery.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + + +@pytest.fixture() +def mock_imap_inpost_pl_delivered(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_inpost_pl_delivered: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_inpost_pl_delivered.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/inpost_pl_delivered.eml", "r") + email_file = f.read() + mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) + mock_conn.select.return_value = ("OK", []) + yield mock_conn + + +@pytest.fixture() +def mock_imap_dpd_com_pl_delivering(): + """Mock imap class values.""" + with patch( + "custom_components.mail_and_packages.helpers.imaplib" + ) as mock_imap_dpd_com_pl_delivering: + mock_conn = mock.Mock(spec=imaplib.IMAP4_SSL) + mock_imap_dpd_com_pl_delivering.IMAP4_SSL.return_value = mock_conn + + mock_conn.login.return_value = ( + "OK", + [b"user@fake.email authenticated (Success)"], + ) + mock_conn.list.return_value = ( + "OK", + [b'(\\HasNoChildren) "/" "INBOX"'], + ) + mock_conn.search.return_value = ("OK", [b"1"]) + f = open("tests/test_emails/dpd_com_pl_delivering.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) mock_conn.select.return_value = ("OK", []) diff --git a/tests/const.py b/tests/const.py index e9eae482..65ceb1f3 100644 --- a/tests/const.py +++ b/tests/const.py @@ -33,11 +33,17 @@ "auspost_delivered", "auspost_packages", "auspost_delivering", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -88,11 +94,17 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -143,11 +155,17 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -181,12 +199,23 @@ "dhl_delivered", "dhl_delivering", "dhl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", "fedex_delivered", "fedex_delivering", "fedex_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", "hermes_delivered", "hermes_delivering", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", "mail_updated", + "poczta_polska_delivering", + "poczta_polska_packages", "royal_delivered", "royal_delivering", "ups_delivered", @@ -198,11 +227,6 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -237,12 +261,23 @@ "dhl_delivered", "dhl_delivering", "dhl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", "fedex_delivered", "fedex_delivering", "fedex_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", "hermes_delivered", "hermes_delivering", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", "mail_updated", + "poczta_polska_delivering", + "poczta_polska_packages", "royal_delivered", "royal_delivering", "ups_delivered", @@ -254,11 +289,6 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -277,6 +307,7 @@ "port": 993, "resources": [ "amazon_delivered", + "amazon_exception", "amazon_hub", "amazon_packages", "auspost_delivered", @@ -288,12 +319,23 @@ "dhl_delivered", "dhl_delivering", "dhl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", "fedex_delivered", "fedex_delivering", "fedex_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", "hermes_delivered", "hermes_delivering", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", "mail_updated", + "poczta_polska_delivering", + "poczta_polska_packages", "royal_delivered", "royal_delivering", "ups_delivered", @@ -305,11 +347,6 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -353,13 +390,19 @@ "usps_delivering", "usps_mail", "usps_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", "zpackages_delivered", "zpackages_transit", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -403,11 +446,17 @@ "usps_packages", "zpackages_delivered", "zpackages_transit", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -446,11 +495,20 @@ "auspost_delivered": 2, "auspost_delivering": 1, "auspost_packages": 3, - "pocztapolska_delivering": 1, - "pocztapolska_packages": 1, - "inpostpl_delivered": 2, - "inpostpl_delivering": 1, - "inpostpl_packages": 3, + "poczta_polska_delivering": 1, + "poczta_polska_packages": 1, + "inpost_pl_delivered": 2, + "inpost_pl_delivering": 1, + "inpost_pl_packages": 3, + "inpost_pl_tracking": ["520113017830399002575123"], + "dpd_com_pl_delivered": 2, + "dpd_com_pl_delivering": 1, + "dpd_com_pl_packages": 3, + "dpd_com_pl_tracking": ["13490015284111"], + "gls_delivered": 2, + "gls_delivering": 1, + "gls_packages": 3, + "gls_tracking": ["51687952111"], } FAKE_CONFIG_DATA_MISSING_TIMEOUT = { @@ -495,11 +553,17 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -549,11 +613,17 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", ], "scan_interval": 20, "username": "user@fake.email", @@ -604,11 +674,17 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", ], "scan_interval": 20, "username": "user@fake.email", diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 4a2ec2a9..8c20255d 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -61,11 +61,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "config_3", @@ -110,11 +110,11 @@ "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -223,11 +223,11 @@ async def test_form( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "config_3", @@ -272,11 +272,11 @@ async def test_form( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -422,11 +422,11 @@ async def test_form_connection_error(input_1, step_id_2, hass, mock_imap): "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -464,11 +464,11 @@ async def test_form_connection_error(input_1, step_id_2, hass, mock_imap): "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -557,11 +557,11 @@ async def test_form_invalid_ffmpeg( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -601,11 +601,11 @@ async def test_form_invalid_ffmpeg( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -703,11 +703,11 @@ async def test_form_index_error( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -747,11 +747,11 @@ async def test_form_index_error( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -848,11 +848,11 @@ async def test_form_index_error_2( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -892,11 +892,11 @@ async def test_form_index_error_2( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -1021,11 +1021,11 @@ async def test_imap_login_error(mock_imap_login_error, caplog): "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "options_3", @@ -1073,11 +1073,11 @@ async def test_imap_login_error(mock_imap_login_error, caplog): "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -1197,11 +1197,11 @@ async def test_options_flow( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "options_3", @@ -1249,11 +1249,11 @@ async def test_options_flow( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -1426,11 +1426,11 @@ async def test_options_flow_connection_error( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -1470,11 +1470,11 @@ async def test_options_flow_connection_error( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -1574,11 +1574,11 @@ async def test_options_flow_invalid_ffmpeg( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -1618,11 +1618,11 @@ async def test_options_flow_invalid_ffmpeg( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -1723,11 +1723,11 @@ async def test_options_flow_index_error( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -1767,11 +1767,11 @@ async def test_options_flow_index_error( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -1872,11 +1872,11 @@ async def test_options_flow_index_error_2( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -1916,11 +1916,11 @@ async def test_options_flow_index_error_2( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -2019,11 +2019,11 @@ async def test_options_flow_mailbox_format2( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, "imap.test.email", @@ -2066,11 +2066,11 @@ async def test_options_flow_mailbox_format2( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -2177,11 +2177,11 @@ async def test_options_flow_bad( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), @@ -2271,11 +2271,11 @@ async def test_form_amazon_error( "auspost_delivered", "auspost_delivering", "auspost_packages", - "pocztapolska_delivering", - "pocztapolska_packages", - "inpostpl_delivered", - "inpostpl_delivering", - "inpostpl_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", ], }, ), diff --git a/tests/test_emails/dpd_com_pl_delivering.eml b/tests/test_emails/dpd_com_pl_delivering.eml new file mode 100644 index 00000000..2c2e16e6 --- /dev/null +++ b/tests/test_emails/dpd_com_pl_delivering.eml @@ -0,0 +1,379 @@ +Delivered-To: redacted@gmail.com +Received: by 2002:a02:92c6:0:0:0:0:0 with SMTP id c6csp26473jah; + Wed, 12 Jan 2022 23:09:11 -0800 (PST) +X-Google-Smtp-Source: ABdhPJxp85amMJLKyRyTZ1HiXivViHfnBQJ+286Y3B1phEYe0JE/jm+T5tIAYSg0rPav1f3ASild +X-Received: by 2002:a05:6512:3341:: with SMTP id y1mr2364960lfd.311.1642057751293; + Wed, 12 Jan 2022 23:09:11 -0800 (PST) +ARC-Seal: i=1; a=rsa-sha256; t=1642057751; cv=none; + d=google.com; s=arc-20160816; + b=A/+C63f2OZCcpaoD/ND1h7U7mAMJohm+YSGGgu2BGnpsNAuo/vU5PyNhhXJP+dcNPh + ZzKMWMasxIF8SlN+z4OSvkTmVqVyfwNGRlpcSg41lSUc0BX15A6wqgu9cFJfzEo1uDVU + djJ0Qsxj+rLcsgU0x/Oo5hsM6O3OKqtXWZRlvDnGAgEKWLtvlSEmU85przZMdtD7VDl0 + XnoHkzOpte8u8ypUe/XoTiJX4GBWGPqtGugh1Nie5c4IXIDlY1YFRJd5ickd3G6r1zfg + zBQXkZV+Jx86mw82GmCvDWrVEAs9k6DqxWtpsJfkof8udRZeujCigD5XCBz3x6z+hwk9 + pR/A== +ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; + h=subject:to:from:mime-version:content-transfer-encoding:message-id + :original-message-id:reply-to:date:dkim-signature; + bh=7et06Yb8grI6flUsaVcXiIDgjMTnnCL+V2GSvgGdzjg=; + b=VVTsda3dV+VxGh8hPpHzhWGbCoHSGrFw5FA5dW/kfmBejsZmbjY4H+pp6gnjxQ04sH + ELAOVVepy2XiH4DMCYzlocD42mIYfiI2daML+X5Svr/LN1TDFhSF/by4xl+FctWe9u3b + Gfqv4EL5/c5i/bHBCIHsS5vxgeCTDMgB6fzTVO1U4qc02rBhfN/RnpGwg2xikhRD/rig + wKQGJZTnJXQFtp+ITho5llBfA5AwIW9E+AfRR2Q8DJyUxHK+OLk+zE4kW/4DKbwdFU2L + QSzgrynz3KfrfzLhwansj9M27c20EjPb8j2EkimdYjZQj3E/OhcYrwY5QmgUw5c22gFH + VC9Q== +ARC-Authentication-Results: i=1; mx.google.com; + dkim=pass header.i=@allegromail.pl header.s=smtp header.b=qfe1NVAM; + spf=pass (google.com: domain of powiadomienia@allegromail.pl designates 91.207.14.253 as permitted sender) smtp.mailfrom=powiadomienia@allegromail.pl; + dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=allegromail.pl +Return-Path: +Received: from smtpfarm11.allegro.pl (smtpfarm11.allegro.pl. [91.207.14.253]) + by mx.google.com with ESMTPS id h18si2261684lja.319.2022.01.12.23.09.11 + for + (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); + Wed, 12 Jan 2022 23:09:11 -0800 (PST) +Received-SPF: pass (google.com: domain of powiadomienia@allegromail.pl designates 91.207.14.253 as permitted sender) client-ip=91.207.14.253; +Authentication-Results: mx.google.com; + dkim=pass header.i=@allegromail.pl header.s=smtp header.b=qfe1NVAM; + spf=pass (google.com: domain of powiadomienia@allegromail.pl designates 91.207.14.253 as permitted sender) smtp.mailfrom=powiadomienia@allegromail.pl; + dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=allegromail.pl +DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; + d=allegromail.pl; i=@allegromail.pl; q=dns/txt; s=smtp; + t=1642057751; x=1673593751; + h=reply-to:message-id:content-transfer-encoding: + mime-version:from:to:subject:date; + bh=paezi65dKZpfKqyW40fKmxJLbQjEiSvEtLwNJ+Gp44c=; + b=qfe1NVAMSIFRQMKH2A6xOaDro/I3rHxhk/vF6Fqc6HCqXDASwMnPbTiw + nDcEVRNXmCkyxDDkLk8CMB5053NH3pN8V9fn8ICfqt87b+nLpwzUfeFAL + XDXMVeVm9tLrSFHhwRebAlzreWzO8oQ3kpvN2xUNgTdlCNPAQgPDKRTAV + 8=; +Date: 13 Jan 2022 08:09:09 +0100 +Reply-To: KurierDPD3@dpd.com.pl +Original-Message-ID: <20220113070801.02CA71E15@middle.masterlink.com.pl> +Message-ID: + <20220113080909.c824f64b-1a0b-4bf1-86ac-b0d6c4cfcf27@allegromail.pl> +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable +MIME-Version: 1.0 +From: "Kurier DPD (via Poczta Allegro)" +To: redacted@gmail.com +Subject: =?utf-8?q?Bezpieczne_dor=C4=99czenie_Twojej_paczki_1000399528451U_-?= + =?utf-8?q?_DPD_Polska?= + +
= +
<= +/td>
=0D + =0D + =0D + =0D + =0D + =0D + =0D +
= +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Drogi Odbiorco,
 
+ Dzi=C5=9B dor=C4=99czamy Twoj=C4=85 paczk=C4=99 1000399528451U= +
+ Mo=C5=BCesz j=C4=85 odebra=C4=87 bezpiecznie - na zewn=C4=85trz lu= +b popro=C5=9B o pozostawienie w portierni/recepcji
+ Uzgodnij z kurierem najbezpieczniejszy dla Ciebie spos=C3=B3b dost= +awy.
+ P=C5=82a=C4=87 bezpiecznie kart=C4=85 za pobranie u kuriera DPD.
+ Poinformuj kuriera je=C5=BCeli przebywasz na kwarantannie.

+ Kurier DPD Polska - 739070409

+
+
P=C5=82atno=C5=9B=C4=87 mo=C5=BCliwa kart=C4=85, BLIKIEM lub got=C3=B3wk=C4=85.
+
 
+ Nie mo=C5=BCesz odebra=C4=87 paczki? Nic straconego! Sprawd=C5=BA = +nowe, ju=C5=BC dost=C4=99pne w ca=C5=82ej Polsce funkcjonalno=C5=9Bci porta= +lu Moja Paczka: https://mojapaczka.dpd.com.pl +
 
+ Portal pozwala na przekierowanie, zmian=C4=99 daty dor=C4=99czenia= +, rezygnacj=C4=99 z paczki i przekierowanie do punktu Pickup.
+
 
Nadawca:
+
ZIELONY PARAPET MARTYN DROZDA WERO= +NIKA KLIMEK SP=C3=93=C5=81KA CYWILNA
+
 
Odbiorca:
+
Adama Pra=C5=BCmowskiego, 7A
+
30399, Krak=C3=B3w, PL
 
+ Numer referencyjny przesy=C5=82ki 1: 367174256 [ucv8ws]
+ Numer referencyjny przesy=C5=82ki 2:
=0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D +
=0D + 3D""=0D +
=0D + 3D""=0D +
=0D + 3D""=0D +
=0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + = +=0D + =0D + =0D + = +=0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D +
 
=0D +=0D + Informujemy, =C5=BCe niniejsze powiadomienie zosta=C5= +=82o wygenerowane automatycznie, prosimy nie odpowiada=C4=87 na t=C4=99=0D + wiadomo=C5=9B=C4=87.=0D +
=0D +
 
=0D + Szczeg=C3=B3=C5=82owe informacje o przesy=C5=82ce: =0D + www.dpd.com.pl
=0D +
Pa=C5=84stwa dane osobowe s=C4=85 przetwarzane przez = +DPD Polska sp. z o.o. z siedzib=C4=85 w Warszawie 02-274 przy ul.=0D + Mineralnej 15, w celu i zakresie wskazanym na stronie inter= +netowej: =0D + www.dpd.com.pl
 
=0D + Dzi=C4=99kujemy za skorzystanie z naszych us=C5=82ug.=0D +
=0D +
Pozdrawiamy,=0D +
=0D + DPD Polska sp. z o.o.
 
=0D +
=0D + 3D""=0D + =0D + 3D""=0D +
=0D + 3D""=0D +
=0D +
=0D + =0D +
=0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D +
=0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D + =0D +
 
=0D +

=0D +=0D + www.dpd.com.pl=0D +

=0D +
=0D +

=0D + =0D +

=0D +
 
 
=0D + DPD Polska sp. z o.o., 0= +2-274 Warszawa, ul. Mineralna 15=0D +
Sp=C3=B3=C5=82ka wpisana do rejestru przedsi=C4=99b= +iorc=C3=B3w w S=C4=85dzie Rejonowym dla m.st.Warszawy=0D +
XIV Wydzia=C5=82 Gospodarczy Krajowego Rejestru S= +=C4=85dowego=0D +
KRS nr 0000028368, NIP:526-020-41-10, kapita=C5=82 = +zak=C5=82adowy: 228.604.000 PLN.
=0D +
 
=0D + Pomy=C5=9Bl o =C5=9Brodo= +wisku! Nie drukuj tej wiadomo=C5=9Bci je=C5=9Bli nie jest to=0D + konieczne.=0D +
 
=0D +
=0D + 3D""=0D +
=0D +
=0D + =0D +=0D += diff --git a/tests/test_emails/gls_delivered.eml b/tests/test_emails/gls_delivered.eml new file mode 100644 index 00000000..f4960a99 --- /dev/null +++ b/tests/test_emails/gls_delivered.eml @@ -0,0 +1,75 @@ +Delivered-To: redacted@gmail.com +Received: by 2002:a59:dd4c:0:b0:271:7674:4f97 with SMTP id s12csp7418849vqs; + Fri, 24 Dec 2021 02:24:22 -0800 (PST) +X-Google-Smtp-Source: ABdhPJz5Ig4uxL9BL+dYNU+hvaV5+AVMiPmKFRatFH33ZOqzPd7zgw6GC+JhX4I1DwNVAmNwB9Bo +X-Received: by 2002:a05:6512:3497:: with SMTP id v23mr4779880lfr.251.1640341461901; + Fri, 24 Dec 2021 02:24:21 -0800 (PST) +ARC-Seal: i=1; a=rsa-sha256; t=1640341461; cv=none; + d=google.com; s=arc-20160816; + b=wgvuQm+xPlnqvw4z8WsZ+r2VcR9NC3496C/mQdqovu7voaHe15R5FpASU2Ajdc85se + GBpmqSbPCJcev3KYKNm0PyeXX59jFEHoxyWzjbjW23JJVelKq0m4gKcV3L0fewXMExzu + Dj1c1y26c1WbgGiNNfz1/PRgGvpUkwh0+07yOGXoPOv0BrHwDS/ydj+Y2H88azotJUMv + lOwhb29k8J21l+hB69KhYUo8lvC/QuHaUCYSk0zvW6E8djCGmyYmqrV/yZ7q6PckBHeX + BZaLlOE7rWI91izdYHeZkogiaho1FOXykQa1qZbkZpxygDuvCDx7jmyyAlAosWsi0yMz + 51qg== +ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; + h=date:message-id:subject:cc:to:from; + bh=h+GyfITiKT9mThGdsFTIQlqBHYxdSdWsTIg6DmHsMws=; + b=rJTaoa2OMxhXFZ3qiRR/jpMCy8JAI6V0FBTNPjLKgvNxYspOkq355Vc11ez/xJ7itN + nqJKEn7vOH2/XWw3ATYvrEQvnZPvDLcdNgD1o5ZbWnM53VXQMLjxVeITbQyMnVLVQvSn + y+PPJTnAcRdgk9mf4GbFiTVWfpbLE443/RXclV4ZcNFMeNwDCc9V+Jpt/3txicBn+MrN + RitiURmAR0ML/CrZaLIUQ3BaNlIBDoWQz9ezuUaJUo04u8xWsVBSv+bwwOJlEIVZeKfS + Yf/Vtfwln9ziuY5IOGnb9NpwF6YOZgE/ZExqF2vaE4CypLWhYAHHxfk1/peahrjIu30Y + m6kw== +ARC-Authentication-Results: i=1; mx.google.com; + spf=pass (google.com: domain of noreply@gls-group.eu designates 193.106.225.236 as permitted sender) smtp.mailfrom=noreply@gls-group.eu; + dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gls-group.eu +Return-Path: +Received: from glsmail02.gls-group.eu (glsmail02.gls-group.eu. [193.106.225.236]) + by mx.google.com with ESMTPS id l8si6678000ljg.487.2021.12.24.02.24.21 + for + (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); + Fri, 24 Dec 2021 02:24:21 -0800 (PST) +Received-SPF: pass (google.com: domain of noreply@gls-group.eu designates 193.106.225.236 as permitted sender) client-ip=193.106.225.236; +Authentication-Results: mx.google.com; + spf=pass (google.com: domain of noreply@gls-group.eu designates 193.106.225.236 as permitted sender) smtp.mailfrom=noreply@gls-group.eu; + dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gls-group.eu +Received: from apl1.portal1.gls-unique.com (glsmail-vip [193.106.225.20]) + by glsmail02.gls-group.eu (Postfix) with ESMTP id 19699803EFE7 + for ; Fri, 24 Dec 2021 11:24:21 +0100 (CET) +Received: by apl1.portal1.gls-unique.com (Postfix, from userid 1000) + id 1A49C18000A7; Fri, 24 Dec 2021 11:24:21 +0100 (CET) +From: GLS +To: redacted@gmail.com +CC: +Subject: =?ISO-8859-1?Q?Twoja=20paczka=20od=20Mailboxde.com=20GmbH=20CLIPA:=20informacja=20o=20dostawie?= +Content-Type: text/html; charset=UTF-8 +Message-Id: <20211224102421.1A49C18000A7@apl1.portal1.gls-unique.com> +Date: Fri, 24 Dec 2021 11:24:21 +0100 (CET) + + + +

GLS FlexDeliveryService

Szanowny Odbiorco,
+
+informujemy, że Twoja paczka o numerze 90346692444 nadana przez Sender Name, została dzisiaj dostarczona o godzinie 11:18 na następujący adres:
+
+John Doe A redacted@gmail.com
+Address Line 7
+12-345 City
+
+Dziękujemy za skorzystanie z usług GLS!
+
+Pozdrawiamy,
+Zespół GLS
+
+Infolinia GLS:
+
+46 814 82 20 (koszt wg stawek operatora osoby dzwoniącej).
+Godziny pracy: pon.-pt. w godzinach 7.00 - 19.00, w sob. w godzinach 7.00 - 15.00.
+
+Odwiedź nas na www.gls-group.eu
+
+Wiadomość wygenerowana automatycznie, prosimy na nią nie odpowiadać.
+
+
+ diff --git a/tests/test_emails/gls_delivering.eml b/tests/test_emails/gls_delivering.eml new file mode 100644 index 00000000..3c1873ab --- /dev/null +++ b/tests/test_emails/gls_delivering.eml @@ -0,0 +1,83 @@ +Delivered-To: redacted@email.com +Received: by 2002:a59:dd4c:0:b0:271:7674:4f97 with SMTP id s12csp6228910vqs; + Wed, 22 Dec 2021 15:33:25 -0800 (PST) +X-Google-Smtp-Source: ABdhPJwl1sMjV9+o4x8RfnBQCcpCFjO3Sh2cRSw74UIz2CMgcwcQu8tIhYEsJUwMzNqCh72aHoPj +X-Received: by 2002:a1c:f609:: with SMTP id w9mr34829wmc.99.1640216005734; + Wed, 22 Dec 2021 15:33:25 -0800 (PST) +ARC-Seal: i=1; a=rsa-sha256; t=1640216005; cv=none; + d=google.com; s=arc-20160816; + b=zdEriAwtdMCGH8QHusQ010U4oe6rH1U6hp9EhG5G/+7mHom7iJs1q0MDC+quIilGKi + k8QsMx0ZeiZhH+8HwQ1QaDNlD8tLmrslWz59QeJuTXf15Ke7DN9IE171K47fiRglyYmL + t87moR7q8MWGQjQHAvzggDZ55PONWGyuI3LS/EKIm/F4QT4wpGqCMgjn03JkQAPZ/LKu + lzj/tC+OGZ7WYMsQA+kq+wYcjHr4JetNFWpsEGx2vMXTiSAxXKKVyGn4ipH9q1mywp+V + Iv7g2McAgGrxcdrEKudoDxlaAYgwBSwA4TkuExcIIlSjrNZmJ8bfr2UVMqQJ9ydSPCEr + 3mbQ== +ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; + h=date:message-id:subject:cc:to:from; + bh=HXz5SQODCpjGMSO4I6NtjsLeuwbAaeONx+w+ZkYs+yg=; + b=sXCuyp0MNrrf5FVY8sxufnObt6oy6qYpMpo/3k8huurSQy9j74/OTcVWWt1oESTXRG + IA7G/d1jgU8MQysO9VRG5PXJP373NajuW1i8CzKDB0lY0ExBuhgSOzumk+kM4rnPVrlW + 9dJ0dUcf0yjGApsa8/V2Km1F8/yPUC4860vSGmJR0xJyvMMLwMMt46627A7kan3rulXw + KlX57X+ilT0od4iy6f4v6fbt3gn/ePCELvqVhgOjvdvNSec1FvWPJF6p9psoNI/R2i15 + jhyC6W6d0L+z4gG/zJAnOAt0/ARv7rtp+uav5OdeYhbR8YFR+9fT1NPswFk/LdPPINdT + HCoQ== +ARC-Authentication-Results: i=1; mx.google.com; + spf=pass (google.com: domain of noreply@gls-group.eu designates 193.106.225.236 as permitted sender) smtp.mailfrom=noreply@gls-group.eu; + dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gls-group.eu +Return-Path: +Received: from glsmail02.gls-group.eu (glsmail02.gls-group.eu. [193.106.225.236]) + by mx.google.com with ESMTPS id g2si1638247wrq.775.2021.12.22.15.33.25 + for + (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); + Wed, 22 Dec 2021 15:33:25 -0800 (PST) +Received-SPF: pass (google.com: domain of noreply@gls-group.eu designates 193.106.225.236 as permitted sender) client-ip=193.106.225.236; +Authentication-Results: mx.google.com; + spf=pass (google.com: domain of noreply@gls-group.eu designates 193.106.225.236 as permitted sender) smtp.mailfrom=noreply@gls-group.eu; + dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gls-group.eu +Received: from apl7.portal1.gls-unique.com (glsmail-vip [193.106.225.20]) + by glsmail02.gls-group.eu (Postfix) with ESMTP id 3607C803F264 + for ; Thu, 23 Dec 2021 00:33:25 +0100 (CET) +Received: by apl7.portal1.gls-unique.com (Postfix, from userid 1000) + id 36D151800088; Thu, 23 Dec 2021 00:33:25 +0100 (CET) +From: GLS +To: redacted@email.com +CC: +Subject: =?ISO-8859-1?Q?Kurier=20GLS=20-=20wybierz=20opcje=20dostawy=20-=20paczka=20w=20drodze?= +Content-Type: text/html; charset=UTF-8 +Message-Id: <20211222233325.36D151800088@apl7.portal1.gls-unique.com> +Date: Thu, 23 Dec 2021 00:33:25 +0100 (CET) + + + +

GLS FlexDeliveryService

Szanowny Odbiorco,
+
+informujemy, że paczka o numerze: 90346692444 (Nr referencyjny: 23221984) nadana przez Mailboxde.com GmbH CLIPA została przekazana kurierowi GLS.
+
+Przewidywana data doręczenia:2021-12-24.w godzinach: 11:00 - 16:00.
+
+Nie będzie Ciebie w miejscu dostawy w podanym terminie? Skorzystaj z jednej z wielu opcji przekierowań paczek np. odbierz wygodnie i bezpiecznie swoje paczki z punktów Szybka Paczka / ParcelShop na terenie całego kraju!
+
+Kliknij w poniższy link i przekieruj swoją paczkę np. do wybranego przez Ciebie punktu Szybka Paczka / ParcelShop.
+
+Zmieniam za pomoca przegladarki www
+
+Jeśli wcześniej złożona została dyspozycja zmiany dostawy, nie ma potrzeby ponownego jej dokonywania.
+
+ Aktualnie wybrany adres doręczenia: John Doe A redacted@email.com, ul. Street Name 7, 12-345 City.
+
+Odbierając paczkę pobraniową od kuriera skorzystaj z bezdotykowej usługi płatności BLIKIEM lub zapłać gotówką.
+
+Pozdrawiamy,
+Zespół GLS
+
+Infolinia GLS:
+
+46 814 82 20 (koszt wg stawek operatora osoby dzwoniącej).
+Godziny pracy: pon.-pt. w godzinach 7.00 - 19.00, w sob. w godzinach 7.00 - 15.00.
+
+Odwiedź nas na www.gls-group.eu
+
+Wiadomość wygenerowana automatycznie, prosimy na nią nie odpowiadać.
+
+
+ diff --git a/tests/test_emails/inpostpl_delivered.eml b/tests/test_emails/inpost_pl_delivered.eml similarity index 99% rename from tests/test_emails/inpostpl_delivered.eml rename to tests/test_emails/inpost_pl_delivered.eml index 50138412..27db1436 100644 --- a/tests/test_emails/inpostpl_delivered.eml +++ b/tests/test_emails/inpost_pl_delivered.eml @@ -1,3 +1,5 @@ +From: "Kurier InPost (via Poczta Allegro)" +Subject: Twoja paczka jest już prawie u Ciebie ------=_Part_18559691_431614451.1641551203581 Content-Type: text/html;charset=UTF-8 @@ -652,7 +654,7 @@ com/c/InpostPl-twoj-inpost" target=3D"_blank"> diff --git a/tests/test_emails/inpostpl_out_for_delivery.eml b/tests/test_emails/inpost_pl_out_for_delivery.eml similarity index 100% rename from tests/test_emails/inpostpl_out_for_delivery.eml rename to tests/test_emails/inpost_pl_out_for_delivery.eml diff --git a/tests/test_emails/pocztapolska_out_for_delivery.eml b/tests/test_emails/poczta_polska_delivering.eml similarity index 69% rename from tests/test_emails/pocztapolska_out_for_delivery.eml rename to tests/test_emails/poczta_polska_delivering.eml index 71c201cf..21058c04 100644 --- a/tests/test_emails/pocztapolska_out_for_delivery.eml +++ b/tests/test_emails/poczta_polska_delivering.eml @@ -1,22 +1,28 @@ +From: informacja@poczta-polska.pl +Subject: Poczta Polska S.A. eINFO -Dzień dobry, +------=_Part_100864109_559649553.1606574683953 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable -Dziś doręczymy Twoją przesyłkę 00111222333444555666. +Dzień dobry, + +Dziś doręczymy Twoją przesyłkę 00111222333444555666. Uzgodnij bezpieczną dostawę. Kurier tel. 555666777. Odbierz za pomoca kodu odbioru: 433566 -Przesyłki (z zastrzeżeniem przesyłek z dokumentami zwrotnymi) możesz odebrać bez pokwitowania. Okaż kurierowi z bezpiecznej odległości dokument tożsamości np. dowód osobisty, paszport, prawo jazdy. Kurier spisze 4 ostatnie cyfry numeru dokumentu. +Przesyłki (z zastrzeżeniem przesyłek z dokumentami zwrotnymi) możesz odebrać bez pokwitowania. Okaż kurierowi z bezpiecznej odległości dokument tożsamości np. dowód osobisty, paszport, prawo jazdy. Kurier spisze 4 ostatnie cyfry numeru dokumentu. -W przypadku przesyłek pobraniowych polecamy płatność kartą. +W przypadku przesyłek pobraniowych polecamy płatność kartą. Szczegółowe informacje o przesyłce: http://emonitoring.poczta-polska.pl/?numer=00111222333444555666 Polecamy zakupy on-line i bezpieczną formę dostawy pod adres realizowaną przez Pocztę Polską. Szczegółowe informacje dotyczące specjalnych procedur: -www.poczta-polska.pl; www.pocztex.pl; www.facebook.com/pocztapolska. +www.poczta-polska.pl; www.pocztex.pl; www.facebook.com/poczta_polska. Niniejsze powiadomienie zostało wygenerowane automatycznie. diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 8faa70d2..2685f206 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -418,7 +418,7 @@ async def test_image_filename_oserr( assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 32 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 43 assert "Problem accessing file:" in caplog.text @@ -446,7 +446,7 @@ async def test_image_getctime_oserr( assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 32 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 43 assert "Problem accessing file:" in caplog.text @@ -800,7 +800,7 @@ async def test_amazon_shipped_order_it_count(hass, mock_imap_amazon_shipped_it): with patch("datetime.date") as mock_date: mock_date.today.return_value = date(2021, 12, 1) result = get_items(mock_imap_amazon_shipped_it, "count") - assert result == 1 + assert result == 0 async def test_amazon_search(hass, mock_imap_no_email): @@ -812,7 +812,7 @@ async def test_amazon_search_results(hass, mock_imap_amazon_shipped): result = amazon_search( mock_imap_amazon_shipped, "test/path", hass, "testfilename.jpg" ) - assert result == 12 + assert result == 21 async def test_amazon_search_delivered( @@ -821,7 +821,7 @@ async def test_amazon_search_delivered( result = amazon_search( mock_imap_amazon_delivered, "test/path", hass, "testfilename.jpg" ) - assert result == 12 + assert result == 21 assert mock_download_img.called @@ -831,7 +831,7 @@ async def test_amazon_search_delivered_it( result = amazon_search( mock_imap_amazon_delivered_it, "test/path", hass, "testfilename.jpg" ) - assert result == 12 + assert result == 21 async def test_amazon_hub(hass, mock_imap_amazon_the_hub): @@ -1049,13 +1049,14 @@ async def test_amazon_exception(hass, mock_imap_amazon_exception, caplog): "123-1234567-1234567", "123-1234567-1234567", "123-1234567-1234567", + "123-1234567-1234567", ] - assert result["count"] == 6 + assert result["count"] == 7 result = amazon_exception(mock_imap_amazon_exception, ["testemail@fakedomain.com"]) - assert result["count"] == 7 + assert result["count"] == 8 assert ( - "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'testemail@fakedomain.com']" + "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'amazon.pl', 'testemail@fakedomain.com']" in caplog.text ) @@ -1063,7 +1064,7 @@ async def test_amazon_exception(hass, mock_imap_amazon_exception, caplog): async def test_hash_file(): """Test file hashing function.""" result = hash_file("tests/test_emails/amazon_delivered.eml") - assert result == "7f9d94e97bb4fc870d2d2b3aeae0c428ebed31dc" + assert result == "0e2c7e290472fffc0cb1c6b6b860b4bd7f386f7c" async def test_fedex_out_for_delivery(hass, mock_imap_fedex_out_for_delivery): diff --git a/tests/test_init.py b/tests/test_init.py index 954eeb50..79dfeefb 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -28,13 +28,13 @@ async def test_unload_entry(hass, mock_update, mock_copy_overlays): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 32 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 43 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert await hass.config_entries.async_unload(entries[0].entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 32 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 43 assert len(hass.states.async_entity_ids(DOMAIN)) == 0 assert await hass.config_entries.async_remove(entries[0].entry_id) @@ -65,7 +65,7 @@ async def test_setup_entry( assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 32 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 43 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 @@ -91,7 +91,7 @@ async def test_no_path_no_sec( assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 31 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 43 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 @@ -120,7 +120,7 @@ async def test_missing_imap_timeout( assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 31 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 42 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 @@ -149,7 +149,7 @@ async def test_amazon_fwds_string( assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 31 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 42 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 @@ -177,6 +177,6 @@ async def test_custom_img( assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 32 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 43 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 52511315..51799f84 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -108,23 +108,47 @@ async def test_sensor(hass, mock_update): assert state assert state.state == "3" - state = hass.states.get("sensor.mail_pocztapolska_delivering") + state = hass.states.get("sensor.mail_poczta_polska_delivering") assert state assert state.state == "1" - state = hass.states.get("sensor.mail_pocztapolska_packages") + state = hass.states.get("sensor.mail_poczta_polska_packages") assert state assert state.state == "1" - state = hass.states.get("sensor.mail_inpostpl_delivered") + state = hass.states.get("sensor.mail_inpost_pl_delivered") assert state assert state.state == "2" - state = hass.states.get("sensor.mail_inpostpl_delivering") + state = hass.states.get("sensor.mail_inpost_pl_delivering") assert state assert state.state == "1" - state = hass.states.get("sensor.mail_inpostpl_packages") + state = hass.states.get("sensor.mail_inpost_pl_packages") + assert state + assert state.state == "3" + + state = hass.states.get("sensor.mail_dpd_com_pl_delivered") + assert state + assert state.state == "2" + + state = hass.states.get("sensor.mail_dpd_com_pl_delivering") + assert state + assert state.state == "1" + + state = hass.states.get("sensor.mail_dpd_com_pl_packages") + assert state + assert state.state == "3" + + state = hass.states.get("sensor.mail_gls_delivered") + assert state + assert state.state == "2" + + state = hass.states.get("sensor.mail_gls_delivering") + assert state + assert state.state == "1" + + state = hass.states.get("sensor.mail_gls_packages") assert state assert state.state == "3" From b4ab0decb298fd3e60089d3a3af2b84381278bb0 Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Wed, 19 Jan 2022 11:25:29 +0100 Subject: [PATCH 39/88] Make Amazon domains and time pattern more maintainable --- .gitignore | 1 + custom_components/mail_and_packages/const.py | 24 +++++++++++++++---- .../mail_and_packages/helpers.py | 8 +++---- tests/test_helpers.py | 22 ++++++----------- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index f615c475..af7f0b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ custom_components/mail_and_packages/.backup/__init__.py custom_components/mail_and_packages/.backup/camera.py custom_components/mail_and_packages/.backup/manifest.json custom_components/mail_and_packages/.backup/sensor.py +custom_components/mail_and_packages/images/* notes.txt .vscode/settings.json *.pyc diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index f9a48149..0c8ae1a6 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -68,9 +68,15 @@ DEFAULT_AMAZON_DAYS = 3 # Amazon -AMAZON_DOMAINS = ( - "amazon.com,amazon.ca,amazon.co.uk,amazon.in,amazon.de,amazon.it,amazon.pl" -) +AMAZON_DOMAINS = [ + "amazon.com", + "amazon.ca", + "amazon.co.uk", + "amazon.in", + "amazon.de", + "amazon.it", + "amazon.pl", +] AMAZON_DELIVERED_SUBJECT = ["Delivered: Your", "Consegna effettuata:", "Dostarczono: "] AMAZON_SHIPMENT_TRACKING = ["shipment-tracking", "conferma-spedizione"] AMAZON_EMAIL = "order-update@" @@ -86,7 +92,15 @@ AMAZON_HUB_SUBJECT = "ready for pickup from Amazon Hub Locker" AMAZON_HUB_SUBJECT_SEARCH = "(You have a package to pick up)(.*)(\\d{6})" AMAZON_HUB_BODY = "(Your pickup code is )(\\d{6})" -AMAZON_TIME_PATTERN = "will arrive:,estimated delivery date is:,guaranteed delivery date is:,Arriving:,Arriverà:,arriving:,Dostawa:" +AMAZON_TIME_PATTERN = [ + "will arrive:", + "estimated delivery date is:", + "guaranteed delivery date is:", + "Arriving:", + "Arriverà:", + "arriving:", + "Dostawa:", +] AMAZON_EXCEPTION_SUBJECT = "Delivery update:" AMAZON_EXCEPTION_BODY = "running late" AMAZON_EXCEPTION = "amazon_exception" @@ -244,7 +258,7 @@ }, "inpost_pl_delivering": { "email": ["powiadomienia@inpost.pl", "powiadomienia@allegromail.pl"], - "subject": ["paczka jest w drodze", "jest już prawie u Ciebie"], + "subject": ["paczka jest w drodze", "prawie u Ciebie"], }, "inpost_pl_packages": {}, "inpost_pl_tracking": { diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index d067eecc..0374df0c 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -981,7 +981,7 @@ def amazon_search( Returns email found count as integer """ _LOGGER.debug("Searching for Amazon delivered email(s)...") - domains = AMAZON_DOMAINS.split(",") + domains = AMAZON_DOMAINS subjects = AMAZON_DELIVERED_SUBJECT today = get_formatted_date() count = 0 @@ -1159,7 +1159,7 @@ def amazon_exception( tfmt = get_formatted_date() count = 0 info = {} - domains = AMAZON_DOMAINS.split(",") + domains = AMAZON_DOMAINS if isinstance(fwds, list): for fwd in fwds: if fwd and fwd != '""': @@ -1214,7 +1214,7 @@ def get_items( order_number = [] domains = _process_amazon_forwards(fwds) - main_domains = AMAZON_DOMAINS.split(",") + main_domains = AMAZON_DOMAINS for main_domain in main_domains: domains.append(main_domain) @@ -1284,7 +1284,7 @@ def get_items( ): order_number.append(found[0]) - searches = AMAZON_TIME_PATTERN.split(",") + searches = AMAZON_TIME_PATTERN for search in searches: _LOGGER.debug("Looking for: %s", search) if search not in email_msg: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 2685f206..b8202277 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -812,7 +812,7 @@ async def test_amazon_search_results(hass, mock_imap_amazon_shipped): result = amazon_search( mock_imap_amazon_shipped, "test/path", hass, "testfilename.jpg" ) - assert result == 21 + assert result == 105 async def test_amazon_search_delivered( @@ -821,7 +821,7 @@ async def test_amazon_search_delivered( result = amazon_search( mock_imap_amazon_delivered, "test/path", hass, "testfilename.jpg" ) - assert result == 21 + assert result == 105 assert mock_download_img.called @@ -831,7 +831,7 @@ async def test_amazon_search_delivered_it( result = amazon_search( mock_imap_amazon_delivered_it, "test/path", hass, "testfilename.jpg" ) - assert result == 21 + assert result == 105 async def test_amazon_hub(hass, mock_imap_amazon_the_hub): @@ -1042,21 +1042,13 @@ async def test_image_file_name( async def test_amazon_exception(hass, mock_imap_amazon_exception, caplog): result = amazon_exception(mock_imap_amazon_exception, ['""']) - assert result["order"] == [ - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - ] - assert result["count"] == 7 + assert result["order"] == ["123-1234567-1234567"] * 35 + assert result["count"] == 35 result = amazon_exception(mock_imap_amazon_exception, ["testemail@fakedomain.com"]) - assert result["count"] == 8 + assert result["count"] == 36 assert ( - "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'amazon.pl', 'testemail@fakedomain.com']" + "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'amazon.pl', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'testemail@fakedomain.com']" in caplog.text ) From efdb9f81d38a515c9e8def4d71238efe42aa5098 Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Wed, 19 Jan 2022 13:44:12 +0100 Subject: [PATCH 40/88] Make select readonly --- custom_components/mail_and_packages/helpers.py | 2 +- test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 0374df0c..8b7052d4 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -449,7 +449,7 @@ def selectfolder(account: Type[imaplib.IMAP4_SSL], folder: str) -> bool: _LOGGER.error("Error listing folders: %s", str(err)) return False try: - account.select(folder) + account.select(folder, readonly=True) except Exception as err: _LOGGER.error("Error selecting folder: %s", str(err)) return False diff --git a/test.py b/test.py index e0482586..ee2c15e4 100644 --- a/test.py +++ b/test.py @@ -69,7 +69,7 @@ def login(): def selectfolder(account, folder): (rv, mailboxes) = account.list() - (rv, data) = account.select(folder) + (rv, data) = account.select(folder, readonly=True) # Returns today in specific format From 1de9de266bbfb64d6eb7c620d86d1eabec27ab5b Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Wed, 19 Jan 2022 17:57:05 +0100 Subject: [PATCH 41/88] Shippers adjustments --- custom_components/mail_and_packages/const.py | 13 +++++-------- custom_components/mail_and_packages/helpers.py | 4 +++- test.py | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 0c8ae1a6..28102413 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -106,7 +106,7 @@ AMAZON_EXCEPTION = "amazon_exception" AMAZON_EXCEPTION_ORDER = "amazon_exception_order" AMAZON_PATTERN = "[0-9]{3}-[0-9]{7}-[0-9]{7}" -AMAZON_LANGS = ["it_IT", "it_IT.UTF-8", ""] +AMAZON_LANGS = ["it_IT", "it_IT.UTF-8", "pl_PL", "pl_PL.UTF-8", ""] # Sensor Data SENSOR_DATA = { @@ -214,7 +214,7 @@ "body": ["scheduled for delivery TODAY", "zostanie dziś do Państwa doręczona"], }, "dhl_packages": {}, - "dhl_tracking": {"pattern": ["\\d{10}"]}, + "dhl_tracking": {"pattern": ["\\d{10,11}"]}, # Hermes.co.uk "hermes_delivered": { "email": ["donotreply@myhermes.co.uk"], @@ -252,7 +252,7 @@ "inpost_pl_delivered": { "email": ["powiadomienia@inpost.pl", "powiadomienia@allegromail.pl"], "subject": [ - "InPost - Potwierdzenie odbioru przesyłki", + "InPost - Potwierdzenie odbioru", "InPost - Paczka umieszczona w Paczkomacie", ], }, @@ -281,9 +281,7 @@ "KurierDPD10@dpd.com.pl", "powiadomienia@allegromail.pl", ], - "subject": [ - "została doręczona", - ], + "subject": ["została doręczona"], }, "dpd_com_pl_delivering": { "email": [ @@ -301,11 +299,10 @@ "powiadomienia@allegromail.pl", ], "subject": [ - "Bezpieczne_dor=C4=99czenie_Twojej_paczki", "Bezpieczne doręczenie", "przesyłka została nadana", ], - "body": ["Dzi=C5=9B dor=C4=99czamy", "DPD Polska"], + "body": ["Dziś doręczamy", "DPD Polska"], }, "dpd_com_pl_packages": {}, "dpd_com_pl_tracking": { diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 8b7052d4..0346110f 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -449,7 +449,7 @@ def selectfolder(account: Type[imaplib.IMAP4_SSL], folder: str) -> bool: _LOGGER.error("Error listing folders: %s", str(err)) return False try: - account.select(folder, readonly=True) + account.select(folder) except Exception as err: _LOGGER.error("Error selecting folder: %s", str(err)) return False @@ -502,6 +502,8 @@ def email_search( email_list = '" FROM "'.join(address) prefix_list = " ".join(["OR"] * (len(address) - 1)) + _LOGGER.debug("DEBUG subject: %s", subject) + if subject is not None: search = f'FROM "{email_list}" SUBJECT "{subject}" {the_date}' else: diff --git a/test.py b/test.py index ee2c15e4..e0482586 100644 --- a/test.py +++ b/test.py @@ -69,7 +69,7 @@ def login(): def selectfolder(account, folder): (rv, mailboxes) = account.list() - (rv, data) = account.select(folder, readonly=True) + (rv, data) = account.select(folder) # Returns today in specific format From 30675f8c8831092a11137a59e585c4bff3816679 Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Wed, 19 Jan 2022 18:53:59 +0100 Subject: [PATCH 42/88] More emails from InPost.pl --- custom_components/mail_and_packages/const.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 28102413..0060a701 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -250,15 +250,26 @@ }, # InPost.pl "inpost_pl_delivered": { - "email": ["powiadomienia@inpost.pl", "powiadomienia@allegromail.pl"], + "email": [ + "powiadomienia@inpost.pl", + "info@paczkomaty.pl", + "powiadomienia@allegromail.pl", + ], "subject": [ "InPost - Potwierdzenie odbioru", "InPost - Paczka umieszczona w Paczkomacie", ], }, "inpost_pl_delivering": { - "email": ["powiadomienia@inpost.pl", "powiadomienia@allegromail.pl"], - "subject": ["paczka jest w drodze", "prawie u Ciebie"], + "email": [ + "powiadomienia@inpost.pl", + "info@paczkomaty.pl", + "powiadomienia@allegromail.pl", + ], + "subject": [ + "Kurier InPost: Twoja paczka jest w drodze", + "prawie u Ciebie", + ], }, "inpost_pl_packages": {}, "inpost_pl_tracking": { From fcc74326c1c7c369a01ca60f705dd2f3bbda179c Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Sun, 23 Jan 2022 10:39:24 +0100 Subject: [PATCH 43/88] Review follow-up --- custom_components/mail_and_packages/helpers.py | 2 +- tests/test_helpers.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 0346110f..9ac8ab3c 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -517,7 +517,7 @@ def email_search( _LOGGER.debug("DEBUG imap_search: %s", imap_search) try: - value = account.search(None, imap_search) + value = account.search(None, imap_search.encode('utf-8')) except Exception as err: _LOGGER.error("Error searching emails: %s", str(err)) value = "BAD", err.args[0] diff --git a/tests/test_helpers.py b/tests/test_helpers.py index b8202277..cc5865d9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -800,7 +800,7 @@ async def test_amazon_shipped_order_it_count(hass, mock_imap_amazon_shipped_it): with patch("datetime.date") as mock_date: mock_date.today.return_value = date(2021, 12, 1) result = get_items(mock_imap_amazon_shipped_it, "count") - assert result == 0 + assert result == 1 async def test_amazon_search(hass, mock_imap_no_email): @@ -1048,7 +1048,7 @@ async def test_amazon_exception(hass, mock_imap_amazon_exception, caplog): result = amazon_exception(mock_imap_amazon_exception, ["testemail@fakedomain.com"]) assert result["count"] == 36 assert ( - "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'amazon.pl', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'testemail@fakedomain.com']" + "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'amazon.pl', 'testemail@fakedomain.com']" in caplog.text ) @@ -1056,7 +1056,7 @@ async def test_amazon_exception(hass, mock_imap_amazon_exception, caplog): async def test_hash_file(): """Test file hashing function.""" result = hash_file("tests/test_emails/amazon_delivered.eml") - assert result == "0e2c7e290472fffc0cb1c6b6b860b4bd7f386f7c" + assert result == "7f9d94e97bb4fc870d2d2b3aeae0c428ebed31dc" async def test_fedex_out_for_delivery(hass, mock_imap_fedex_out_for_delivery): @@ -1097,7 +1097,8 @@ async def test_email_search_none(mock_imap_search_error_none, caplog): ) assert result == ("OK", [b""]) -async def test_amazon_shipped_fwd(hass, mock_imap_amazon_fwd,caplog): + +async def test_amazon_shipped_fwd(hass, mock_imap_amazon_fwd, caplog): result = get_items(mock_imap_amazon_fwd, "order") assert result == ["123-1234567-1234567"] - assert "Arrive Date: Tuesday, January 11" in caplog.text \ No newline at end of file + assert "Arrive Date: Tuesday, January 11" in caplog.text From 78a3afb33d3c2eb70fb0f68c030fbad8022fbdff Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Feb 2022 16:20:46 -0700 Subject: [PATCH 44/88] tests: stop upgrading pip This was a work around for an old issue. --- .github/workflows/pytest.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 299c642b..70af2516 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -24,8 +24,6 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - - python -m pip install --upgrade pip pip install -r requirements_test.txt sudo apt-get update sudo apt-get -y install language-pack-it From a7713f3dcb03223abdfc2fb7caf6cf2c735b2583 Mon Sep 17 00:00:00 2001 From: kaizersoje <54566217+kaizersoje@users.noreply.github.com> Date: Wed, 2 Feb 2022 10:44:00 +1100 Subject: [PATCH 45/88] Add amazon.com.au and update auspost_delivering Added amazon.com.au domain and updated subject for auspost_delivering as the subject in the emails either say "Your delivery from XXX is on its way" or "Your delivery from XXX is coming today" --- custom_components/mail_and_packages/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 26fa561b..3c5cec2b 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -68,7 +68,7 @@ DEFAULT_AMAZON_DAYS = 3 # Amazon -AMAZON_DOMAINS = "amazon.com,amazon.ca,amazon.co.uk,amazon.in,amazon.de,amazon.it" +AMAZON_DOMAINS = "amazon.com,amazon.ca,amazon.co.uk,amazon.in,amazon.de,amazon.it,amazon.com.au" AMAZON_DELIVERED_SUBJECT = ["Delivered: Your", "Consegna effettuata:"] AMAZON_SHIPMENT_TRACKING = ["shipment-tracking", "conferma-spedizione"] AMAZON_EMAIL = "order-update@" @@ -208,7 +208,7 @@ }, "auspost_delivering": { "email": ["noreply@notifications.auspost.com.au"], - "subject": ["Your delivery is coming today"], + "subject": ["is on its way", "is coming today"], }, "auspost_packages": {}, "auspost_tracking": {"pattern": ["\\d{7,10,12}|[A-Za-z]{2}[0-9]{9}AU "]}, From a4cac44e875027212f472a7919c226e59a3db8cc Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 7 Feb 2022 10:33:45 -0700 Subject: [PATCH 46/88] Fix tests --- tests/test_helpers.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 8faa70d2..557a4719 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -812,7 +812,7 @@ async def test_amazon_search_results(hass, mock_imap_amazon_shipped): result = amazon_search( mock_imap_amazon_shipped, "test/path", hass, "testfilename.jpg" ) - assert result == 12 + assert result == 14 async def test_amazon_search_delivered( @@ -821,7 +821,7 @@ async def test_amazon_search_delivered( result = amazon_search( mock_imap_amazon_delivered, "test/path", hass, "testfilename.jpg" ) - assert result == 12 + assert result == 14 assert mock_download_img.called @@ -831,7 +831,7 @@ async def test_amazon_search_delivered_it( result = amazon_search( mock_imap_amazon_delivered_it, "test/path", hass, "testfilename.jpg" ) - assert result == 12 + assert result == 14 async def test_amazon_hub(hass, mock_imap_amazon_the_hub): @@ -1042,20 +1042,13 @@ async def test_image_file_name( async def test_amazon_exception(hass, mock_imap_amazon_exception, caplog): result = amazon_exception(mock_imap_amazon_exception, ['""']) - assert result["order"] == [ - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - "123-1234567-1234567", - ] - assert result["count"] == 6 + assert result["order"] == ["123-1234567-1234567"]*7 + assert result["count"] == 7 result = amazon_exception(mock_imap_amazon_exception, ["testemail@fakedomain.com"]) - assert result["count"] == 7 + assert result["count"] == 8 assert ( - "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'testemail@fakedomain.com']" + "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'amazon.com.au', 'testemail@fakedomain.com']" in caplog.text ) @@ -1107,4 +1100,4 @@ async def test_email_search_none(mock_imap_search_error_none, caplog): async def test_amazon_shipped_fwd(hass, mock_imap_amazon_fwd,caplog): result = get_items(mock_imap_amazon_fwd, "order") assert result == ["123-1234567-1234567"] - assert "Arrive Date: Tuesday, January 11" in caplog.text \ No newline at end of file + assert "Arrive Date: Tuesday, January 11" in caplog.text From cf06ca8167f4db388438004a2ade9608e1c96a4c Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 7 Feb 2022 10:37:31 -0700 Subject: [PATCH 47/88] Fix pip --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 70af2516..535fe782 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install -r requirements_test.txt + pip install --use-deprecated legacy-resolver -r requirements_test.txt sudo apt-get update sudo apt-get -y install language-pack-it - name: Generate coverage report From cfe7aed4d9b5c2264e41af717b404e52d0afcda7 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 7 Feb 2022 12:51:35 -0700 Subject: [PATCH 48/88] tests: use tox for testing --- .github/workflows/pytest.yaml | 37 +++++++++++----- .gitignore | 79 ++++++++++++++++++++++++++++++----- pylintrc | 8 +++- requirements_format.txt | 3 +- tox.ini | 39 +++++++++++++++++ 5 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 tox.ini diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 535fe782..2135b8d9 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -14,23 +14,40 @@ jobs: python-version: - "3.8" - "3.9" - # - "3.10" + - "3.10" steps: - uses: actions/checkout@v2 + with: + fetch-depth: 2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2.2.2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install --use-deprecated legacy-resolver -r requirements_test.txt sudo apt-get update sudo apt-get -y install language-pack-it - - name: Generate coverage report - run: | - python -m pytest - pip install pytest-cov - pytest ./tests/ --cov=custom_components/mail_and_packages/ --cov-report=xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + pip install tox tox-gh-actions + - name: Test with tox + run: tox + - name: Upload coverage data + uses: "actions/upload-artifact@v2.2.4" + with: + name: coverage-data + path: "coverage.xml" + + coverage: + runs-on: ubuntu-latest + needs: build + steps: + - name: Check out the repository + uses: actions/checkout@v2.3.4 + with: + fetch-depth: 2 + - name: Download coverage data + uses: actions/download-artifact@v2.0.10 + with: + name: coverage-data + - name: Upload coverage report + uses: codecov/codecov-action@v2.0.2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index f615c475..dffca95a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,78 @@ -custom_components/.DS_Store -custom_components/mail_and_packages/.DS_Store -custom_components/mail_and_packages/__pycache__/.DS_Store -includes/.DS_Store -includes/packages/.DS_Store -www/.DS_Store -www/mail_and_packages/.DS_Store +# Hide some OS X stuff .DS_Store +.AppleDouble +.LSOverride +Icon + +# Thumbnails +._* + custom_components/mail_and_packages/camera.py.old custom_components/mail_and_packages/.backup/__init__.py custom_components/mail_and_packages/.backup/camera.py custom_components/mail_and_packages/.backup/manifest.json custom_components/mail_and_packages/.backup/sensor.py notes.txt -.vscode/settings.json -*.pyc -.coverage + +# Test files ra_0_mailerProvidedImage0 mailerProvidedImage0 + +# pytest +.pytest_cache +.cache + +# Unit test / coverage reports +.coverage +.tox +coverage.xml +nosetests.xml +htmlcov/ +test-reports/ +test-results.xml +test-output.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +.python-version + +# emacs auto backups +*~ +*# +*.orig + +# venv stuff +pyvenv.cfg +pip-selfcheck.json +venv +.venv +Pipfile* +share/* +Scripts/ + +# vimmy stuff +*.swp +*.swo +tags +ctags.tmp + +# vagrant stuff +virtualization/vagrant/setup_done +virtualization/vagrant/.vagrant +virtualization/vagrant/config + +# Visual Studio Code +.vscode/* +!.vscode/cSpell.json +!.vscode/extensions.json +!.vscode/tasks.json + +# Typing +.mypy_cache +*.pyc \ No newline at end of file diff --git a/pylintrc b/pylintrc index 55d8a9d7..ca5c1e2c 100644 --- a/pylintrc +++ b/pylintrc @@ -26,7 +26,11 @@ disable= too-many-public-methods, too-many-instance-attributes, too-many-branches, - no-self-use + no-self-use, + too-many-statements, + broad-except, + too-many-lines, + too-many-locals [REPORTS] score=no @@ -36,4 +40,4 @@ score=no ignored-classes=_CountingAttr [FORMAT] -expected-line-ending-format=CRLF +expected-line-ending-format=LF diff --git a/requirements_format.txt b/requirements_format.txt index ff1c4dc6..1e6db1fb 100644 --- a/requirements_format.txt +++ b/requirements_format.txt @@ -2,4 +2,5 @@ black isort flake8 pydocstyle -pylint \ No newline at end of file +pylint +mypy \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..ec7c6c38 --- /dev/null +++ b/tox.ini @@ -0,0 +1,39 @@ +[tox] +skipsdist = true +envlist = py38, py39, py310, lint, mypy +skip_missing_interpreters = True +ignore_basepython_conflict = True + +[gh-actions] +python = + 3.8: py38, lint, mypy + 3.9: py39 + 3.10: py310 + +[testenv] +pip_version = pip>=21.0,<22.1 +install_command = python -m pip install --use-deprecated legacy-resolver {opts} {packages} +commands = + pytest --timeout=30 --cov=custom_components/mail_and_packages/ --cov-report=xml {posargs} +deps = + -rrequirements_test.txt + +[testenv:lint] +basepython = python3 +ignore_errors = True +commands = + black --check custom_components/mail_and_packages/ + flake8 custom_components/mail_and_packages/ + pylint custom_components/mail_and_packages/ + pydocstyle custom_components/mail_and_packages/ tests/ +deps = + -rrequirements_format.txt + -rrequirements_test.txt + +[testenv:mypy] +basepython = python3 +ignore_errors = True +commands = + mypy custom_components/mail_and_packages/ +deps = + -rrequirements_format.txt \ No newline at end of file From 4b8662cd79fb2f16e4df74234d93150d5186967b Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 7 Feb 2022 12:57:24 -0700 Subject: [PATCH 49/88] clean up --- .github/workflows/formatting.yml | 90 +------------------------------- .github/workflows/mypy.yml | 12 ----- 2 files changed, 1 insertion(+), 101 deletions(-) delete mode 100644 .github/workflows/mypy.yml diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 76b8dd24..a5e06297 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -1,4 +1,4 @@ -name: "Validation and Formatting" +name: "Validation" on: push: @@ -22,91 +22,3 @@ jobs: steps: - uses: "actions/checkout@v2" - uses: home-assistant/actions/hassfest@master - validate_flake8: - name: "flake8" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: Cache - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}-3 - restore-keys: | - ${{ runner.os }}-pip- - - name: Install dependencies - run: | - python -m pip install --upgrade pip wheel - python -m pip install --upgrade -r requirements.txt wemake-python-styleguide - - name: Check for fatal errors - run: | - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - - name: Docstrings - run: | - flake8 . --inline-quotes '"' --count --exit-zero --max-complexity=15 --max-line-length=90 --statistics --select=D,DAR - - name: Small tweaks that might help, but might conflict or be inconvenient - run: | - flake8 . --inline-quotes '"' --count --exit-zero --max-complexity=15 --max-line-length=90 --statistics --select=WPS323,WPS336,WPS305,WPS420,WPS440,WPS441,WPS515,E800\:,WPS421,W503,WPS412 - - name: Trailing commas and isort - run: | - flake8 . --inline-quotes '"' --count --exit-zero --max-complexity=15 --max-line-length=90 --statistics --select=I,C81 - - name: Overcomplex code - run: | - flake8 . --inline-quotes '"' --count --exit-zero --max-complexity=15 --max-line-length=90 --statistics --select=WPS201,WPS210,WPS214,WPS221,WPS229,WPS226 - - name: Useless stuff - run: | - flake8 . --inline-quotes '"' --count --exit-zero --max-complexity=15 --max-line-length=90 --statistics --select=F401,F841,WPS327,WPS503,WPS504,WPS507 - - name: Bandit - run: | - flake8 . --inline-quotes '"' --count --exit-zero --max-complexity=15 --max-line-length=90 --statistics --select=S - - name: Clarity and quality improvements - run: | - flake8 . --inline-quotes '"' --count --exit-zero --max-complexity=15 --max-line-length=90 --statistics --select=WPS432,WPS110,WPS111,WPS322,E501 - - name: General stats - run: | - flake8 . --inline-quotes '"' --count --exit-zero --max-complexity=15 --max-line-length=90 --statistics \ - --ignore=D,DAR,WPS323,WPS336,WPS305,WPS420,WPS440,WPS441,WPS515,E800\:,WPS421,W503,WPS412,I,C81,WPS201,WPS210,WPS214,WPS221,WPS229,WPS226,F401,F841,WPS327,WPS503,WPS504,WPS507,S,WPS432,WPS110,WPS111,WPS322,E501 - format: - name: "Format with black and isort" - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Cache - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}-2 - restore-keys: | - ${{ runner.os }}-pip-2 - - name: Install dependencies - run: | - python -m pip install --upgrade pip wheel - python -m pip install --upgrade -r requirements.txt -r requirements_format.txt - - name: Pull again - run: git pull || true - - name: Run formatting - run: | - python -m isort -v --profile black . - python -m black -v . - - name: Commit files - run: | - if [ $(git diff HEAD | wc -l) -gt 30 ] - then - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config user.name "GitHub Actions" - git commit -a --amend --no-edit || true - git push --force || true - fi diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml deleted file mode 100644 index e0a6cfcb..00000000 --- a/.github/workflows/mypy.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: "Check mypy" -on: - pull_request: - -jobs: - mypy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: jpetrucciani/mypy-check@master - with: - path: "custom_components" From 294c143747af6b41e3c6828e49d9f2c486d62a6d Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Feb 2022 10:03:49 -0700 Subject: [PATCH 50/88] formatting --- .../mail_and_packages/__init__.py | 11 +- custom_components/mail_and_packages/camera.py | 2 - .../mail_and_packages/config_flow.py | 73 +++++------- custom_components/mail_and_packages/const.py | 13 ++- .../mail_and_packages/helpers.py | 104 ++++++++---------- custom_components/mail_and_packages/sensor.py | 18 ++- pylintrc | 9 +- tests/__init__.py | 1 + tests/conftest.py | 64 +++++------ tests/const.py | 2 +- 10 files changed, 141 insertions(+), 156 deletions(-) diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index 78e28c7f..1896edfb 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -31,9 +31,10 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup(hass, config_entry): - """Disallow configuration via YAML""" - +async def async_setup( + hass: HomeAssistant, config_entry: ConfigEntry +): # pylint: disable=unused-argument + """Disallow configuration via YAML.""" return True @@ -116,7 +117,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Handle removal of an entry.""" - _LOGGER.debug("Attempting to unload sensors from the %s integration", DOMAIN) unload_ok = all( @@ -137,7 +137,6 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Update listener.""" - _LOGGER.debug("Attempting to reload sensors from the %s integration", DOMAIN) if config_entry.data == config_entry.options: @@ -242,7 +241,7 @@ def __init__(self, hass, host, the_timeout, interval, config): super().__init__(hass, _LOGGER, name=self.name, update_interval=self.interval) async def _async_update_data(self): - """Fetch data""" + """Fetch data.""" async with timeout(self.timeout): try: data = await self.hass.async_add_executor_job( diff --git a/custom_components/mail_and_packages/camera.py b/custom_components/mail_and_packages/camera.py index cf65d333..9550571e 100644 --- a/custom_components/mail_and_packages/camera.py +++ b/custom_components/mail_and_packages/camera.py @@ -130,7 +130,6 @@ def check_file_path_access(self, file_path: str) -> None: def update_file_path(self) -> None: """Update the file_path.""" - _LOGGER.debug("Camera Update: %s", self._type) _LOGGER.debug("Custom No Mail: %s", self._no_mail) @@ -176,7 +175,6 @@ async def async_on_demand_update(self): @property def device_info(self) -> dict: """Return device information about the mailbox.""" - return { "connections": {(DOMAIN, self._unique_id)}, "name": self._host, diff --git a/custom_components/mail_and_packages/config_flow.py b/custom_components/mail_and_packages/config_flow.py index 4ebfec59..051778e5 100644 --- a/custom_components/mail_and_packages/config_flow.py +++ b/custom_components/mail_and_packages/config_flow.py @@ -1,7 +1,7 @@ """Adds config flow for Mail and Packages.""" import logging -import os.path as path +from os import path from typing import Any import homeassistant.helpers.config_validation as cv @@ -120,7 +120,7 @@ async def _validate_user_input(user_input: dict) -> tuple: def _get_mailboxes(host: str, port: int, user: str, pwd: str) -> list: - """Gets list of mailbox folders from mail server.""" + """Get list of mailbox folders from mail server.""" account = login(host, port, user, pwd) status, folderlist = account.list() @@ -144,13 +144,13 @@ def _get_mailboxes(host: str, port: int, user: str, pwd: str) -> list: return mailboxes -def _get_schema_step_1(hass: Any, user_input: list, default_dict: list) -> Any: - """Gets a schema using the default_dict as a backup.""" +def _get_schema_step_1(user_input: list, default_dict: list) -> Any: + """Get a schema using the default_dict as a backup.""" if user_input is None: user_input = {} def _get_default(key: str, fallback_default: Any = None) -> None: - """Gets default value for key.""" + """Get default value for key.""" return user_input.get(key, default_dict.get(key, fallback_default)) return vol.Schema( @@ -163,15 +163,13 @@ def _get_default(key: str, fallback_default: Any = None) -> None: ) -def _get_schema_step_2( - hass: Any, data: list, user_input: list, default_dict: list -) -> Any: - """Gets a schema using the default_dict as a backup.""" +def _get_schema_step_2(data: list, user_input: list, default_dict: list) -> Any: + """Get a schema using the default_dict as a backup.""" if user_input is None: user_input = {} def _get_default(key: str, fallback_default: Any = None) -> None: - """Gets default value for key.""" + """Get default value for key.""" return user_input.get(key, default_dict.get(key, fallback_default)) return vol.Schema( @@ -211,15 +209,13 @@ def _get_default(key: str, fallback_default: Any = None) -> None: ) -def _get_schema_step_3( - hass: Any, data: list, user_input: list, default_dict: list -) -> Any: - """Gets a schema using the default_dict as a backup.""" +def _get_schema_step_3(user_input: list, default_dict: list) -> Any: + """Get a schema using the default_dict as a backup.""" if user_input is None: user_input = {} def _get_default(key: str, fallback_default: Any = None) -> None: - """Gets default value for key.""" + """Get default value for key.""" return user_input.get(key, default_dict.get(key, fallback_default)) return vol.Schema( @@ -244,7 +240,7 @@ def __init__(self): self._data = {} self._errors = {} - async def async_step_user(self, user_input={}): + async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" self._errors = {} @@ -267,7 +263,6 @@ async def async_step_user(self, user_input={}): async def _show_config_form(self, user_input): """Show the configuration form to edit configuration data.""" - # Defaults defaults = { CONF_PORT: DEFAULT_PORT, @@ -275,12 +270,12 @@ async def _show_config_form(self, user_input): return self.async_show_form( step_id="user", - data_schema=_get_schema_step_1(self.hass, user_input, defaults), + data_schema=_get_schema_step_1(user_input, defaults), errors=self._errors, ) async def async_step_config_2(self, user_input=None): - """Configuration form step 2.""" + """Configure form step 2.""" self._errors = {} if user_input is not None: self._errors, user_input = await _validate_user_input(user_input) @@ -288,17 +283,15 @@ async def async_step_config_2(self, user_input=None): if len(self._errors) == 0: if self._data[CONF_CUSTOM_IMG]: return await self.async_step_config_3() - else: - return self.async_create_entry( - title=self._data[CONF_HOST], data=self._data - ) + return self.async_create_entry( + title=self._data[CONF_HOST], data=self._data + ) return await self._show_config_2(user_input) return await self._show_config_2(user_input) async def _show_config_2(self, user_input): - """Step 2 setup""" - + """Step 2 setup.""" # Defaults defaults = { CONF_FOLDER: DEFAULT_FOLDER, @@ -316,12 +309,12 @@ async def _show_config_2(self, user_input): return self.async_show_form( step_id="config_2", - data_schema=_get_schema_step_2(self.hass, self._data, user_input, defaults), + data_schema=_get_schema_step_2(self._data, user_input, defaults), errors=self._errors, ) async def async_step_config_3(self, user_input=None): - """Configuration form step 2.""" + """Configure form step 2.""" self._errors = {} if user_input is not None: self._data.update(user_input) @@ -335,8 +328,7 @@ async def async_step_config_3(self, user_input=None): return await self._show_config_3(user_input) async def _show_config_3(self, user_input): - """Step 3 setup""" - + """Step 3 setup.""" # Defaults defaults = { CONF_CUSTOM_IMG_FILE: DEFAULT_CUSTOM_IMG_FILE, @@ -344,13 +336,14 @@ async def _show_config_3(self, user_input): return self.async_show_form( step_id="config_3", - data_schema=_get_schema_step_3(self.hass, self._data, user_input, defaults), + data_schema=_get_schema_step_3(user_input, defaults), errors=self._errors, ) @staticmethod @callback def async_get_options_flow(config_entry): + """Redirect to options flow.""" return MailAndPackagesOptionsFlow(config_entry) @@ -385,15 +378,14 @@ async def async_step_init(self, user_input=None): async def _show_options_form(self, user_input): """Show the configuration form to edit location data.""" - return self.async_show_form( step_id="init", - data_schema=_get_schema_step_1(self.hass, user_input, self._data), + data_schema=_get_schema_step_1(user_input, self._data), errors=self._errors, ) async def async_step_options_2(self, user_input=None): - """Configuration form step 2.""" + """Configure form step 2.""" self._errors = {} if user_input is not None: self._errors, user_input = await _validate_user_input(user_input) @@ -401,16 +393,12 @@ async def async_step_options_2(self, user_input=None): if len(self._errors) == 0: if self._data[CONF_CUSTOM_IMG]: return await self.async_step_options_3() - else: - return self.async_create_entry(title="", data=self._data) - + return self.async_create_entry(title="", data=self._data) return await self._show_step_options_2(user_input) - return await self._show_step_options_2(user_input) async def _show_step_options_2(self, user_input): """Step 2 of options.""" - # Defaults defaults = { CONF_FOLDER: self._data.get(CONF_FOLDER), @@ -430,12 +418,12 @@ async def _show_step_options_2(self, user_input): return self.async_show_form( step_id="options_2", - data_schema=_get_schema_step_2(self.hass, self._data, user_input, defaults), + data_schema=_get_schema_step_2(self._data, user_input, defaults), errors=self._errors, ) async def async_step_options_3(self, user_input=None): - """Configuration form step 3.""" + """Configure form step 3.""" self._errors = {} if user_input is not None: self._data.update(user_input) @@ -447,8 +435,7 @@ async def async_step_options_3(self, user_input=None): return await self._show_step_options_3(user_input) async def _show_step_options_3(self, user_input): - """Step 3 setup""" - + """Step 3 setup.""" # Defaults defaults = { CONF_CUSTOM_IMG_FILE: self._data.get(CONF_CUSTOM_IMG_FILE) @@ -457,6 +444,6 @@ async def _show_step_options_3(self, user_input): return self.async_show_form( step_id="options_3", - data_schema=_get_schema_step_3(self.hass, self._data, user_input, defaults), + data_schema=_get_schema_step_3(user_input, defaults), errors=self._errors, ) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index f55b659e..29dcf12e 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -1,4 +1,4 @@ -""" Constants for Mail and Packages.""" +"""Constants for Mail and Packages.""" from __future__ import annotations from typing import Final @@ -7,7 +7,7 @@ from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC DOMAIN = "mail_and_packages" -DOMAIN_DATA = "{}_data".format(DOMAIN) +DOMAIN_DATA = f"{DOMAIN}_data" VERSION = "0.0.0-dev" # Now updated by release workflow ISSUE_URL = "http://github.com/moralmunky/Home-Assistant-Mail-And-Packages" PLATFORM = "sensor" @@ -86,7 +86,14 @@ AMAZON_HUB_SUBJECT = "ready for pickup from Amazon Hub Locker" AMAZON_HUB_SUBJECT_SEARCH = "(You have a package to pick up)(.*)(\\d{6})" AMAZON_HUB_BODY = "(Your pickup code is )(\\d{6})" -AMAZON_TIME_PATTERN = "will arrive:,estimated delivery date is:,guaranteed delivery date is:,Arriving:,Arriverà:,arriving:" +AMAZON_TIME_PATTERN = [ + "will arrive:", + "estimated delivery date is:", + "guaranteed delivery date is:", + "Arriving:", + "Arriverà:", + "arriving:", +] AMAZON_EXCEPTION_SUBJECT = "Delivery update:" AMAZON_EXCEPTION_BODY = "running late" AMAZON_EXCEPTION = "amazon_exception" diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 11dbb3cf..42daf01e 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -1,4 +1,4 @@ -""" Helper functions for Mail and Packages """ +"""Helper functions for Mail and Packages.""" import datetime import email @@ -90,7 +90,6 @@ def get_resources() -> dict: Returns dict of user selected sensors """ - known_available_resources = { sensor_id: sensor.name for sensor_id, sensor in SENSOR_TYPES.items() } @@ -107,7 +106,7 @@ async def _check_ffmpeg() -> bool: async def _test_login(host: str, port: int, user: str, pwd: str) -> bool: - """Tests IMAP login to specified server. + """Test IMAP login to specified server. Returns success boolean """ @@ -129,12 +128,13 @@ async def _test_login(host: str, port: int, user: str, pwd: str) -> bool: # Email Data helpers -def default_image_path(hass: HomeAssistant, config_entry: ConfigEntry) -> str: - """Return value of the default image path +def default_image_path( + hass: HomeAssistant, config_entry: ConfigEntry # pylint: disable=unused-argument +) -> str: + """Return value of the default image path. Returns the default path based on logic (placeholder for future code) """ - # Return the default return "custom_components/mail_and_packages/images/" @@ -246,7 +246,7 @@ def image_file_name( mail_none = config.get(CONF_CUSTOM_IMG_FILE) else: mail_none = f"{os.path.dirname(__file__)}/mail_none.gif" - not_used, image_name = os.path.split(mail_none) + image_name = os.path.split(mail_none)[1] # Path check path_check = os.path.exists(path) @@ -301,11 +301,10 @@ def image_file_name( def hash_file(filename: str) -> str: - """This function returns the SHA-1 hash of the file passed into it. + """Return the SHA-1 hash of the file passed into it. Returns hash of file as string """ - # make a hash object the_hash = hashlib.sha1() # nosec @@ -330,7 +329,6 @@ def fetch( Returns integer of sensor passed to it """ - img_out_path = f"{hass.config.path()}/{config.get(CONF_PATH)}" gif_duration = config.get(CONF_DURATION) generate_mp4 = config.get(CONF_GENERATE_MP4) @@ -418,11 +416,10 @@ def fetch( def login( host: str, port: int, user: str, pwd: str ) -> Union[bool, Type[imaplib.IMAP4_SSL]]: - """Function used to login to IMAP server. + """Login to IMAP server. Returns account object """ - # Catch invalid mail server / host names try: account = imaplib.IMAP4_SSL(host, port) @@ -442,8 +439,7 @@ def login( def selectfolder(account: Type[imaplib.IMAP4_SSL], folder: str) -> bool: - - """Select folder inside the mailbox""" + """Select folder inside the mailbox.""" try: account.list() except Exception as err: @@ -458,7 +454,7 @@ def selectfolder(account: Type[imaplib.IMAP4_SSL], folder: str) -> bool: def get_formatted_date() -> str: - """Returns today in specific format. + """Return today in specific format. Returns current timestamp as string """ @@ -471,7 +467,7 @@ def get_formatted_date() -> str: def update_time() -> str: - """Gets update time. + """Get update time. Returns current timestamp as string """ @@ -488,7 +484,6 @@ def email_search( Returns a tuple """ - imap_search = None # Holds our IMAP SEARCH command prefix_list = None email_list = address @@ -555,7 +550,7 @@ def get_mails( gen_mp4: bool = False, custom_img: str = None, ) -> int: - """Creates GIF image based on the attachments in the inbox""" + """Create GIF image based on the attachments in the inbox.""" image_count = 0 images = [] images_delete = [] @@ -609,14 +604,15 @@ def get_mails( # Log error message if we are unable to open the filepath for # some reason try: - the_file = open(image_output_path + part.get_filename(), "wb") + with open( + image_output_path + part.get_filename(), "wb" + ) as the_file: + the_file.write(part.get_payload(decode=True)) + images.append(image_output_path + part.get_filename()) + image_count = image_count + 1 except Exception as err: _LOGGER.critical("Error opening filepath: %s", str(err)) return image_count - the_file.write(part.get_payload(decode=True)) - images.append(image_output_path + part.get_filename()) - image_count = image_count + 1 - the_file.close() # Remove duplicate images _LOGGER.debug("Removing duplicate images.") @@ -696,8 +692,8 @@ def get_mails( def _generate_mp4(path: str, image_file: str) -> None: - """ - Generate mp4 from gif + """Generate mp4 from gif. + use a subprocess so we don't lock up the thread comamnd: ffmpeg -f gif -i infile.gif outfile.mp4 """ @@ -729,37 +725,37 @@ def _generate_mp4(path: str, image_file: str) -> None: def resize_images(images: list, width: int, height: int) -> list: - """ - Resize images - This should keep the aspect ratio of the images + """Resize images. + This should keep the aspect ratio of the images Returns list of images """ all_images = [] for image in images: try: - fd_img = open(image, "rb") + with open(image, "rb") as fd_img: + try: + img = Image.open(fd_img) + img = resizeimage.resize_contain(img, [width, height]) + pre = os.path.splitext(image)[0] + image = pre + ".gif" + img.save(image, img.format) + fd_img.close() + all_images.append(image) + except Exception as err: + _LOGGER.error( + "Error attempting to read image %s: %s", str(image), str(err) + ) + continue except Exception as err: _LOGGER.error("Error attempting to open image %s: %s", str(image), str(err)) continue - try: - img = Image.open(fd_img) - except Exception as err: - _LOGGER.error("Error attempting to read image %s: %s", str(image), str(err)) - continue - img = resizeimage.resize_contain(img, [width, height]) - pre = os.path.splitext(image)[0] - image = pre + ".gif" - img.save(image, img.format) - fd_img.close() - all_images.append(image) return all_images def copy_overlays(path: str) -> None: """Copy overlay images to image output path.""" - overlays = OVERLAY check = all(item in overlays for item in os.listdir(path)) @@ -774,12 +770,10 @@ def copy_overlays(path: str) -> None: def cleanup_images(path: str, image: Optional[str] = None) -> None: - """ - Clean up image storage directory. + """Clean up image storage directory. Only supose to delete .gif, .mp4, and .jpg files """ - if image is not None: try: os.remove(path + image) @@ -803,8 +797,7 @@ def get_count( hass: Optional[HomeAssistant] = None, amazon_image_name: Optional[str] = None, ) -> dict: - """ - Get Package Count. + """Get Package Count. Returns dict of sensor data """ @@ -887,7 +880,7 @@ def get_tracking( mail_list = sdata.split() _LOGGER.debug("Searching for tracking numbers in %s messages...", len(mail_list)) - pattern = re.compile(r"{}".format(the_format)) + pattern = re.compile(rf"{the_format}") for i in mail_list: data = email_fetch(account, i, "(RFC822)")[1] for response_part in data: @@ -933,8 +926,7 @@ def get_tracking( def find_text(sdata: Any, account: Type[imaplib.IMAP4_SSL], search: str) -> int: - """ - Filter for specific words in email. + """Filter for specific words in email. Return count of items found as integer """ @@ -955,7 +947,7 @@ def find_text(sdata: Any, account: Type[imaplib.IMAP4_SSL], search: str) -> int: continue email_msg = part.get_payload(decode=True) email_msg = email_msg.decode("utf-8", "ignore") - pattern = re.compile(r"{}".format(search)) + pattern = re.compile(rf"{search}") if (found := pattern.findall(email_msg)) and len(found) > 0: _LOGGER.debug( "Found (%s) in email %s times.", search, str(len(found)) @@ -1027,7 +1019,7 @@ def get_amazon_image( _LOGGER.debug("Processing HTML email...") part = part.get_payload(decode=True) part = part.decode("utf-8", "ignore") - pattern = re.compile(r"{}".format(AMAZON_IMG_PATTERN)) + pattern = re.compile(rf"{AMAZON_IMG_PATTERN}") found = pattern.findall(part) for url in found: if url[1] != "us-prod-temp.s3.amazonaws.com": @@ -1043,7 +1035,6 @@ def get_amazon_image( async def download_img(img_url: str, img_path: str, img_name: str) -> None: """Download image from url.""" - img_path = f"{img_path}amazon/" filepath = f"{img_path}{img_name}" @@ -1067,7 +1058,6 @@ def _process_amazon_forwards(email_list: Union[List[str], None]) -> list: Returns list of email addresses """ - result = [] if email_list: for fwd in email_list: @@ -1116,7 +1106,7 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> # Get combo number from subject line email_subject = msg["subject"] - pattern = re.compile(r"{}".format(subject_regex)) + pattern = re.compile(rf"{subject_regex}") search = pattern.search(email_subject) if search is not None: if len(search.groups()) > 1: @@ -1130,7 +1120,7 @@ def amazon_hub(account: Type[imaplib.IMAP4_SSL], fwds: Optional[str] = None) -> _LOGGER.debug("Problem decoding email message: %s", str(err)) continue email_msg = email_msg.decode("utf-8", "ignore") - pattern = re.compile(r"{}".format(body_regex)) + pattern = re.compile(rf"{body_regex}") search = pattern.search(email_msg) if search is not None: if len(search.groups()) > 1: @@ -1198,7 +1188,6 @@ def get_items( Returns list of order numbers or email count as integer """ - _LOGGER.debug("Attempting to find Amazon email with item list ...") # Limit to past 3 days (plan to make this configurable) @@ -1278,8 +1267,7 @@ def get_items( ): order_number.append(found[0]) - searches = AMAZON_TIME_PATTERN.split(",") - for search in searches: + for search in AMAZON_TIME_PATTERN: _LOGGER.debug("Looking for: %s", search) if search not in email_msg: continue diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index 16ca6999..fcbe6632 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -1,10 +1,10 @@ -""" -Based on @skalavala work at -https://blog.kalavala.net/usps/homeassistant/mqtt/2018/01/12/usps.html +"""Based on @skalavala work. +https://blog.kalavala.net/usps/homeassistant/mqtt/2018/01/12/usps.html Configuration code contribution from @firstof9 https://github.com/firstof9/ """ import logging +from multiprocessing.sharedctypes import Value from typing import Optional from homeassistant.components.sensor import SensorEntity, SensorEntityDescription @@ -41,10 +41,8 @@ async def async_setup_entry(hass, entry, async_add_entities): for variable in resources: sensors.append(PackagesSensor(entry, SENSOR_TYPES[variable], coordinator)) - for variable in IMAGE_SENSORS: - sensors.append( - ImagePathSensors(hass, entry, IMAGE_SENSORS[variable], coordinator) - ) + for variable, value in IMAGE_SENSORS.items(): + sensors.append(ImagePathSensors(hass, entry, value, coordinator)) async_add_entities(sensors, False) @@ -58,7 +56,7 @@ def __init__( sensor_description: SensorEntityDescription, coordinator: str, ): - """Initialize the sensor""" + """Initialize the sensor.""" super().__init__(coordinator) self.entity_description = sensor_description self.coordinator = coordinator @@ -72,7 +70,6 @@ def __init__( @property def device_info(self) -> dict: """Return device information about the mailbox.""" - return { "connections": {(DOMAIN, self._unique_id)}, "name": self._host, @@ -144,7 +141,7 @@ def __init__( sensor_description: SensorEntityDescription, coordinator: str, ): - """Initialize the sensor""" + """Initialize the sensor.""" super().__init__(coordinator) self.entity_description = sensor_description self.hass = hass @@ -158,7 +155,6 @@ def __init__( @property def device_info(self) -> dict: """Return device information about the mailbox.""" - return { "connections": {(DOMAIN, self._unique_id)}, "name": self._host, diff --git a/pylintrc b/pylintrc index ca5c1e2c..a4fa914c 100644 --- a/pylintrc +++ b/pylintrc @@ -8,6 +8,8 @@ persistent=no [BASIC] good-names=id,i,j,k,ex,Run,_,fp max-attributes=15 +argument-naming-style=snake_case +attr-naming-style=snake_case [MESSAGES CONTROL] # Reasons disabled: @@ -30,7 +32,12 @@ disable= too-many-statements, broad-except, too-many-lines, - too-many-locals + too-many-locals, + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=8 [REPORTS] score=no diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..624b656e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Pytests init file.""" diff --git a/tests/conftest.py b/tests/conftest.py index c470910d..d5d37037 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -""" Fixtures for Mail and Packages tests. """ +"""Fixtures for Mail and Packages tests.""" import datetime import errno import imaplib @@ -734,7 +734,7 @@ def mock_imap_amazon_the_hub_2(): @pytest.fixture def test_valid_ffmpeg(): - """Fixture to mock which""" + """Fixture to mock which.""" with patch("custom_components.mail_and_packages.helpers.which") as mock_which: mock_which.return_value = "anything" yield mock_which @@ -742,7 +742,7 @@ def test_valid_ffmpeg(): @pytest.fixture def test_invalid_ffmpeg(): - """Fixture to mock which""" + """Fixture to mock which.""" with patch("custom_components.mail_and_packages.helpers.which") as mock_which: mock_which.return_value = None yield mock_which @@ -750,7 +750,7 @@ def test_invalid_ffmpeg(): @pytest.fixture def mock_copyfile_exception(): - """Fixture to mock which""" + """Fixture to mock copyfile.""" with patch("custom_components.mail_and_packages.helpers.copyfile") as mock_copyfile: mock_copyfile.side_effect = Exception("File not found") yield mock_copyfile @@ -758,7 +758,7 @@ def mock_copyfile_exception(): @pytest.fixture def mock_copyfile(): - """Fixture to mock copyfile""" + """Fixture to mock copyfile.""" with patch("custom_components.mail_and_packages.helpers.copyfile") as mock_copyfile: mock_copyfile.return_value = True yield mock_copyfile @@ -766,7 +766,7 @@ def mock_copyfile(): @pytest.fixture def mock_listdir(): - """Fixture to mock listdir""" + """Fixture to mock listdir.""" with patch("os.listdir") as mock_listdir: mock_listdir.return_value = [ "testfile.gif", @@ -778,7 +778,7 @@ def mock_listdir(): @pytest.fixture def mock_listdir_nogif(): - """Fixture to mock listdir""" + """Fixture to mock listdir.""" with patch("os.listdir") as mock_listdir_nogif: mock_listdir_nogif.return_value = [ "testfile.jpg", @@ -790,7 +790,7 @@ def mock_listdir_nogif(): @pytest.fixture def mock_listdir_noimgs(): - """Fixture to mock listdir""" + """Fixture to mock listdir.""" with patch("os.listdir") as mock_listdir_noimgs: mock_listdir_noimgs.return_value = [ "testfile.xls", @@ -802,7 +802,7 @@ def mock_listdir_noimgs(): @pytest.fixture def mock_osremove(): - """Fixture to mock remove""" + """Fixture to mock remove.""" with patch("os.remove") as mock_remove: mock_remove.return_value = True yield mock_remove @@ -810,7 +810,7 @@ def mock_osremove(): @pytest.fixture def mock_osremove_exception(): - """Fixture to mock remove""" + """Fixture to mock remove.""" with patch("os.remove") as mock_osremove_exception: mock_osremove_exception.side_effect = Exception("Invalid directory") yield mock_osremove_exception @@ -818,7 +818,7 @@ def mock_osremove_exception(): @pytest.fixture def mock_osmakedir(): - """Fixture to mock makedirs""" + """Fixture to mock makedirs.""" with patch("os.makedirs") as mock_osmakedir: mock_osmakedir.return_value = True yield mock_osmakedir @@ -826,7 +826,7 @@ def mock_osmakedir(): @pytest.fixture def mock_osmakedir_excpetion(): - """Fixture to mock makedir""" + """Fixture to mock makedir.""" with patch("os.makedir") as mock_osmakedir: mock_osmakedir.side_effect = Exception("File not found") yield mock_osmakedir @@ -834,7 +834,7 @@ def mock_osmakedir_excpetion(): @pytest.fixture def mock_open_excpetion(): - """Fixture to mock open""" + """Fixture to mock open.""" with patch("builtins.open") as mock_open_excpetion: mock_open_excpetion.side_effect = Exception("File not found") @@ -843,7 +843,7 @@ def mock_open_excpetion(): @pytest.fixture def mock_os_path_splitext(): - """Fixture to mock splitext""" + """Fixture to mock splitext.""" with patch("os.path.splitext") as mock_os_path_splitext: mock_os_path_splitext.return_value = ("test_filename", "gif") yield mock_os_path_splitext @@ -851,7 +851,7 @@ def mock_os_path_splitext(): @pytest.fixture def mock_update_time(): - """Fixture to mock splitext""" + """Fixture to mock update_time.""" with patch( "custom_components.mail_and_packages.helpers.update_time" ) as mock_update_time: @@ -864,7 +864,7 @@ def mock_update_time(): @pytest.fixture def mock_image(): - """Fixture to mock splitext""" + """Fixture to mock Image.""" with patch("custom_components.mail_and_packages.helpers.Image") as mock_image: yield mock_image @@ -872,7 +872,7 @@ def mock_image(): @pytest.fixture def mock_image_excpetion(): - """Fixture to mock splitext""" + """Fixture to mock Image.""" with patch( "custom_components.mail_and_packages.helpers.Image" ) as mock_image_excpetion: @@ -883,7 +883,7 @@ def mock_image_excpetion(): @pytest.fixture def mock_resizeimage(): - """Fixture to mock splitext""" + """Fixture to mock splitext.""" with patch( "custom_components.mail_and_packages.helpers.resizeimage" ) as mock_resizeimage: @@ -893,7 +893,7 @@ def mock_resizeimage(): @pytest.fixture def mock_io(): - """Fixture to mock splitext""" + """Fixture to mock io.""" with patch("custom_components.mail_and_packages.helpers.io") as mock_io: yield mock_io @@ -901,7 +901,7 @@ def mock_io(): @pytest.fixture def mock_os_path_isfile(): - """Fixture to mock splitext""" + """Fixture to mock isfile.""" with patch("os.path.isfile") as mock_os_path_isfile: mock_os_path_isfile.return_value = True yield mock_os_path_isfile @@ -909,7 +909,7 @@ def mock_os_path_isfile(): @pytest.fixture def mock_os_path_join(): - """Fixture to mock splitext""" + """Fixture to mock join.""" with patch("os.path.join") as mock_path_join: yield mock_path_join @@ -917,7 +917,7 @@ def mock_os_path_join(): @pytest.fixture def mock_os_path_split(): - """Fixture to mock split""" + """Fixture to mock split.""" with patch("os.path.split") as mock_os_path_split: yield mock_os_path_split @@ -925,7 +925,7 @@ def mock_os_path_split(): @pytest.fixture def mock_subprocess_call(): - """Fixture to mock splitext""" + """Fixture to mock subprocess.""" with patch("subprocess.call") as mock_subprocess_call: yield mock_subprocess_call @@ -933,7 +933,7 @@ def mock_subprocess_call(): @pytest.fixture def mock_copy_overlays(): - """Fixture to mock splitext""" + """Fixture to mock copy_overlays.""" with patch( "custom_components.mail_and_packages.helpers.copy_overlays" ) as mock_copy_overlays: @@ -1003,7 +1003,7 @@ def mock_imap_royal_out_for_delivery(): @pytest.fixture def mock_copyoverlays(): - """Fixture to mock makedirs""" + """Fixture to mock copy_overlays.""" with patch( "custom_components.mail_and_packages.helpers.copy_overlays" ) as mock_copyoverlays: @@ -1013,7 +1013,7 @@ def mock_copyoverlays(): @pytest.fixture def mock_hash_file(): - """Fixture to mock makedirs""" + """Fixture to mock hash_file.""" with patch( "custom_components.mail_and_packages.helpers.hash_file" ) as mock_hash_file: @@ -1022,6 +1022,7 @@ def mock_hash_file(): def hash_side_effect(value): + """Side effect value.""" if "mail_none.gif" in value: return "633d7356947eec543c50b76a1852f92427f4dca9" else: @@ -1030,7 +1031,7 @@ def hash_side_effect(value): @pytest.fixture def mock_getctime_today(): - """Fixture to mock os.path.getctime""" + """Fixture to mock os.path.getctime.""" with patch( "custom_components.mail_and_packages.helpers.os.path.getctime" ) as mock_getctime_today: @@ -1040,7 +1041,7 @@ def mock_getctime_today(): @pytest.fixture def mock_getctime_yesterday(): - """Fixture to mock os.path.getctime""" + """Fixture to mock os.path.getctime.""" with patch( "custom_components.mail_and_packages.helpers.os.path.getctime" ) as mock_getctime_yesterday: @@ -1050,7 +1051,7 @@ def mock_getctime_yesterday(): @pytest.fixture def mock_hash_file_oserr(): - """Fixture to mock makedirs""" + """Fixture to mock hash_file.""" with patch( "custom_components.mail_and_packages.helpers.hash_file" ) as mock_hash_file_oserr: @@ -1060,7 +1061,7 @@ def mock_hash_file_oserr(): @pytest.fixture def mock_getctime_err(): - """Fixture to mock os.path.getctime""" + """Fixture to mock os.path.getctime.""" with patch( "custom_components.mail_and_packages.helpers.os.path.getctime" ) as mock_getctime_err: @@ -1129,7 +1130,7 @@ def aioclient_mock_error(): @pytest.fixture def mock_copytree(): - """Fixture to mock copyfile""" + """Fixture to mock copytree.""" with patch("custom_components.mail_and_packages.helpers.copytree") as mock_copytree: mock_copytree.return_value = True yield mock_copytree @@ -1259,4 +1260,5 @@ def mock_imap_amazon_fwd(): @pytest.fixture(autouse=True) def auto_enable_custom_integrations(enable_custom_integrations): + """Enable custom integration tests.""" yield diff --git a/tests/const.py b/tests/const.py index 3e6ee019..bb4eca31 100644 --- a/tests/const.py +++ b/tests/const.py @@ -1,4 +1,4 @@ -""" Constants for tests. """ +"""Constants for tests.""" FAKE_CONFIG_DATA_BAD = { "folder": '"INBOX"', From 2cc48ef738de76730388db4ae34c8609cd7a2efc Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Feb 2022 10:08:22 -0700 Subject: [PATCH 51/88] clean up --- custom_components/mail_and_packages/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index fcbe6632..43c19110 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -4,7 +4,6 @@ Configuration code contribution from @firstof9 https://github.com/firstof9/ """ import logging -from multiprocessing.sharedctypes import Value from typing import Optional from homeassistant.components.sensor import SensorEntity, SensorEntityDescription From a2c64a19f5b2ecd4807f06784105283900a87998 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Feb 2022 10:11:53 -0700 Subject: [PATCH 52/88] update tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ec7c6c38..1cb1727a 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ ignore_basepython_conflict = True [gh-actions] python = 3.8: py38, lint, mypy - 3.9: py39 + 3.9: py39, lint, mypy 3.10: py310 [testenv] From 7349a05b76e61c5f67067ce7cc234f6a704d3f75 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Feb 2022 10:15:27 -0700 Subject: [PATCH 53/88] update validation --- .github/workflows/formatting.yml | 24 ------------------------ .github/workflows/validation.yml | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 24 deletions(-) delete mode 100644 .github/workflows/formatting.yml create mode 100644 .github/workflows/validation.yml diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml deleted file mode 100644 index a5e06297..00000000 --- a/.github/workflows/formatting.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: "Validation" - -on: - push: - pull_request: - schedule: - - cron: "0 0 * * *" - -jobs: - validate_hacs: - name: "HACS Validation" - runs-on: ubuntu-latest - steps: - - uses: "actions/checkout@v2" - - name: HACS Action - uses: "hacs/action@main" - with: - category: "integration" - validate_hassfest: - name: "Hassfest Validation" - runs-on: "ubuntu-latest" - steps: - - uses: "actions/checkout@v2" - - uses: home-assistant/actions/hassfest@master diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 00000000..6dd3c4ca --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,23 @@ +name: "Validation" + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +jobs: + validate: + runs-on: "ubuntu-latest" + name: Validate + steps: + - uses: "actions/checkout@v2" + + - name: HACS validation + uses: "hacs/action@main" + with: + category: "integration" + ignore: brands + + - name: Hassfest validation + uses: "home-assistant/actions/hassfest@master" From 974997f82a401f1d388449b2cb699ba7bfbe1dae Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Feb 2022 12:13:25 -0700 Subject: [PATCH 54/88] refactor: make amazon easier to manage --- custom_components/mail_and_packages/const.py | 12 +++++++++--- custom_components/mail_and_packages/helpers.py | 12 +++++------- tests/test_helpers.py | 14 +++++++------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 29dcf12e..b3417617 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -68,9 +68,15 @@ DEFAULT_AMAZON_DAYS = 3 # Amazon -AMAZON_DOMAINS = ( - "amazon.com,amazon.ca,amazon.co.uk,amazon.in,amazon.de,amazon.it,amazon.com.au" -) +AMAZON_DOMAINS = [ + "amazon.com", + "amazon.ca", + "amazon.co.uk", + "amazon.in", + "amazon.de", + "amazon.it", + "amazon.com.au", +] AMAZON_DELIVERED_SUBJECT = ["Delivered: Your", "Consegna effettuata:"] AMAZON_SHIPMENT_TRACKING = ["shipment-tracking", "conferma-spedizione"] AMAZON_EMAIL = "order-update@" diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 42daf01e..8b916840 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -969,12 +969,11 @@ def amazon_search( Returns email found count as integer """ _LOGGER.debug("Searching for Amazon delivered email(s)...") - domains = AMAZON_DOMAINS.split(",") subjects = AMAZON_DELIVERED_SUBJECT today = get_formatted_date() count = 0 - for domain in domains: + for domain in AMAZON_DOMAINS: for subject in subjects: email_address = AMAZON_EMAIL + domain _LOGGER.debug("Amazon email search address: %s", str(email_address)) @@ -1061,7 +1060,7 @@ def _process_amazon_forwards(email_list: Union[List[str], None]) -> list: result = [] if email_list: for fwd in email_list: - if fwd and fwd != '""': + if fwd and fwd != '""' and fwd not in result: result.append(fwd) return result @@ -1143,10 +1142,10 @@ def amazon_exception( tfmt = get_formatted_date() count = 0 info = {} - domains = AMAZON_DOMAINS.split(",") + domains = AMAZON_DOMAINS if isinstance(fwds, list): for fwd in fwds: - if fwd and fwd != '""': + if fwd and fwd != '""' and fwd not in domains: domains.append(fwd) _LOGGER.debug("Amazon email adding %s to list", str(fwd)) @@ -1197,8 +1196,7 @@ def get_items( order_number = [] domains = _process_amazon_forwards(fwds) - main_domains = AMAZON_DOMAINS.split(",") - for main_domain in main_domains: + for main_domain in AMAZON_DOMAINS: domains.append(main_domain) _LOGGER.debug("Amazon email list: %s", str(domains)) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 20c5b72c..4a86b7b9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -812,7 +812,7 @@ async def test_amazon_search_results(hass, mock_imap_amazon_shipped): result = amazon_search( mock_imap_amazon_shipped, "test/path", hass, "testfilename.jpg" ) - assert result == 14 + assert result == 18 async def test_amazon_search_delivered( @@ -821,7 +821,7 @@ async def test_amazon_search_delivered( result = amazon_search( mock_imap_amazon_delivered, "test/path", hass, "testfilename.jpg" ) - assert result == 14 + assert result == 18 assert mock_download_img.called @@ -831,7 +831,7 @@ async def test_amazon_search_delivered_it( result = amazon_search( mock_imap_amazon_delivered_it, "test/path", hass, "testfilename.jpg" ) - assert result == 14 + assert result == 18 async def test_amazon_hub(hass, mock_imap_amazon_the_hub): @@ -1042,13 +1042,13 @@ async def test_image_file_name( async def test_amazon_exception(hass, mock_imap_amazon_exception, caplog): result = amazon_exception(mock_imap_amazon_exception, ['""']) - assert result["order"] == ["123-1234567-1234567"] * 7 - assert result["count"] == 7 + assert result["order"] == ["123-1234567-1234567"] * 9 + assert result["count"] == 9 result = amazon_exception(mock_imap_amazon_exception, ["testemail@fakedomain.com"]) - assert result["count"] == 8 + assert result["count"] == 10 assert ( - "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'amazon.com.au', 'testemail@fakedomain.com']" + "Amazon domains to be checked: ['amazon.com', 'amazon.ca', 'amazon.co.uk', 'amazon.in', 'amazon.de', 'amazon.it', 'amazon.com.au', 'fakeuser@fake.email', 'fakeuser2@fake.email', 'testemail@fakedomain.com']" in caplog.text ) From 03df9d359deacefa6c3fcd7eb0a86cd95475d26c Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Feb 2022 12:35:49 -0700 Subject: [PATCH 55/88] Update test_helpers.py --- tests/test_helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index f7a1fb04..272f628a 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -812,7 +812,7 @@ async def test_amazon_search_results(hass, mock_imap_amazon_shipped): result = amazon_search( mock_imap_amazon_shipped, "test/path", hass, "testfilename.jpg" ) - assert result == 19 + assert result == 30 async def test_amazon_search_delivered( @@ -821,7 +821,7 @@ async def test_amazon_search_delivered( result = amazon_search( mock_imap_amazon_delivered, "test/path", hass, "testfilename.jpg" ) - assert result == 19 + assert result == 30 assert mock_download_img.called @@ -831,7 +831,7 @@ async def test_amazon_search_delivered_it( result = amazon_search( mock_imap_amazon_delivered_it, "test/path", hass, "testfilename.jpg" ) - assert result == 19 + assert result == 30 async def test_amazon_hub(hass, mock_imap_amazon_the_hub): From 2001a8c2b92cafc1af8269a757af2414e51bb1eb Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Feb 2022 12:58:06 -0700 Subject: [PATCH 56/88] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62ed8907..4b5f54fa 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ![Validate with hassfest](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/workflows/Validate%20with%20hassfest/badge.svg?branch=master) ![GitHub contributors](https://img.shields.io/github/contributors/moralmunky/Home-Assistant-Mail-And-Packages) -![Maintenance](https://img.shields.io/maintenance/yes/2021) +![Maintenance](https://img.shields.io/maintenance/yes/2022) ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/moralmunky/Home-Assistant-Mail-And-Packages) ![GitHub commits since tagged version](https://img.shields.io/github/commits-since/moralmunky/Home-Assistant-Mail-And-Packages/0.3.2/master) ![GitHub last commit](https://img.shields.io/github/last-commit/moralmunky/Home-Assistant-Mail-And-Packages) From 984d917fe4119d2aa78d67a6ecddf580ad66044a Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Feb 2022 13:04:53 -0700 Subject: [PATCH 57/88] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b5f54fa..a5463206 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ ![GitHub contributors](https://img.shields.io/github/contributors/moralmunky/Home-Assistant-Mail-And-Packages) ![Maintenance](https://img.shields.io/maintenance/yes/2022) -![GitHub commit activity](https://img.shields.io/github/commit-activity/m/moralmunky/Home-Assistant-Mail-And-Packages) -![GitHub commits since tagged version](https://img.shields.io/github/commits-since/moralmunky/Home-Assistant-Mail-And-Packages/0.3.2/master) -![GitHub last commit](https://img.shields.io/github/last-commit/moralmunky/Home-Assistant-Mail-And-Packages) +![GitHub commit activity](https://img.shields.io/github/commit-activity/y/moralmunky/Home-Assistant-Mail-And-Packages) +![GitHub commits since tagged version](https://img.shields.io/github/commits-since/moralmunky/Home-Assistant-Mail-And-Packages/0.3.3-2/dev) +![GitHub last commit](https://img.shields.io/github/last-commit/moralmunky/Home-Assistant-Mail-And-Packages/dev) ![Codecov branch](https://img.shields.io/codecov/c/github/moralmunky/Home-Assistant-Mail-And-Packages/master) ## About Mail and Packages integration From 6e1b43683d4e0a386724dfcd14ce653968ce6557 Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Sat, 12 Feb 2022 10:54:01 +0100 Subject: [PATCH 58/88] Reformat --- custom_components/mail_and_packages/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index b6e84b61..f336d847 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -513,7 +513,7 @@ def email_search( _LOGGER.debug("DEBUG imap_search: %s", imap_search) try: - value = account.search(None, imap_search.encode('utf-8')) + value = account.search(None, imap_search.encode("utf-8")) except Exception as err: _LOGGER.error("Error searching emails: %s", str(err)) value = "BAD", err.args[0] From f15d177fba5a44544e74926aceae211db08c735c Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 13 Feb 2022 20:18:09 -0700 Subject: [PATCH 59/88] fix: resolve utf-8 subject searching error fixes #628 --- custom_components/mail_and_packages/helpers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index f336d847..3a060370 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -513,7 +513,12 @@ def email_search( _LOGGER.debug("DEBUG imap_search: %s", imap_search) try: - value = account.search(None, imap_search.encode("utf-8")) + if subject is not None and not subject.isascii(): + subject = subject.encode("utf-8") + account.literal = subject + value = account.uid('SEARCH','CHARSET', 'UTF-8', 'SUBJECT') + else: + value = account.search("utf-8", imap_search) except Exception as err: _LOGGER.error("Error searching emails: %s", str(err)) value = "BAD", err.args[0] From d99e8f7e059fe1c7f3703921da9f657ce343146c Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 06:59:37 -0700 Subject: [PATCH 60/88] fix tests --- tests/conftest.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 0750f89c..05f18b6d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -42,6 +42,7 @@ def mock_imap(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) mock_conn.select.return_value = ("OK", []) yield mock_conn @@ -120,6 +121,7 @@ def mock_imap_no_email(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b""]) + mock_conn.uid.return_value = ("OK", [b"1"]) mock_conn.select.return_value = ("OK", []) yield mock_conn @@ -164,6 +166,7 @@ def mock_imap_fetch_error(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) mock_conn.select.return_value = ("OK", []) mock_conn.fetch.side_effect = Exception("Invalid Email") yield mock_conn @@ -208,6 +211,7 @@ def mock_imap_index_error_2(): [b'(\\HasNoChildren) ";" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"0"]) + mock_conn.uid.return_value = ("OK", [b"0"]) yield mock_imap_index_error @@ -229,6 +233,7 @@ def mock_imap_mailbox_format2(): [b'(\\HasNoChildren) "." "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"0"]) + mock_conn.uid.return_value = ("OK", [b"0"]) yield mock_conn @@ -250,6 +255,7 @@ def mock_imap_usps_informed_digest(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/informed_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -275,6 +281,7 @@ def mock_imap_usps_informed_digest_missing(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/informed_delivery_missing_mailpiece.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -300,6 +307,7 @@ def mock_imap_usps_informed_digest_no_mail(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/informed_delivery_no_mail.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -325,6 +333,7 @@ def mock_imap_ups_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/ups_out_for_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -350,6 +359,7 @@ def mock_imap_ups_out_for_delivery_html(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/ups_out_for_delivery_new.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -375,6 +385,7 @@ def mock_imap_dhl_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/dhl_out_for_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -400,6 +411,7 @@ def mock_imap_fedex_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/fedex_out_for_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -425,6 +437,7 @@ def mock_imap_fedex_out_for_delivery_2(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/fedex_out_for_delivery_2.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -450,6 +463,7 @@ def mock_imap_usps_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/usps_out_for_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -475,6 +489,7 @@ def mock_imap_amazon_shipped(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_shipped.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -500,6 +515,7 @@ def mock_imap_amazon_shipped_uk(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_uk_shipped.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -525,6 +541,7 @@ def mock_imap_amazon_shipped_uk_2(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_uk_shipped_2.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -550,6 +567,7 @@ def mock_imap_amazon_shipped_alt(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_shipped_alt.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -575,6 +593,7 @@ def mock_imap_amazon_shipped_alt_2(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_shipped_alt_2.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -600,6 +619,7 @@ def mock_imap_amazon_shipped_it(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_shipped_it.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -625,6 +645,7 @@ def mock_imap_amazon_shipped_alt_timeformat(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_shipped_alt_timeformat.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -650,6 +671,7 @@ def mock_imap_amazon_delivered(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_delivered.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -675,6 +697,7 @@ def mock_imap_amazon_delivered_it(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_delivered_it.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -700,6 +723,7 @@ def mock_imap_amazon_the_hub(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_hub_notice.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -725,6 +749,7 @@ def mock_imap_amazon_the_hub_2(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_hub_notice_2.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -969,6 +994,7 @@ def mock_imap_hermes_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/hermes_out_for_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -994,6 +1020,7 @@ def mock_imap_royal_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/royal_mail_uk_out_for_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1087,6 +1114,7 @@ def mock_imap_usps_exception(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/usps_exception.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1154,6 +1182,7 @@ def mock_imap_amazon_exception(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_exception.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1179,6 +1208,7 @@ def mock_imap_auspost_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/auspost_out_for_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1204,6 +1234,7 @@ def mock_imap_auspost_delivered(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/auspost_delivered.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1229,6 +1260,7 @@ def mock_imap_poczta_polska_delivering(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/poczta_polska_delivering.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1254,6 +1286,7 @@ def mock_imap_inpost_pl_out_for_delivery(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/inpost_pl_out_for_delivery.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1279,6 +1312,7 @@ def mock_imap_inpost_pl_delivered(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/inpost_pl_delivered.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1304,6 +1338,7 @@ def mock_imap_dpd_com_pl_delivering(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/dpd_com_pl_delivering.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) @@ -1351,6 +1386,7 @@ def mock_imap_amazon_fwd(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b"1"]) f = open("tests/test_emails/amazon_fwd.eml", "r") email_file = f.read() mock_conn.fetch.return_value = ("OK", [(b"", email_file.encode("utf-8"))]) From 1b2c17af68935c8952d65d8b5943b6b2400679bf Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 08:02:06 -0700 Subject: [PATCH 61/88] fix test --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 05f18b6d..af0178a2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -121,7 +121,7 @@ def mock_imap_no_email(): [b'(\\HasNoChildren) "/" "INBOX"'], ) mock_conn.search.return_value = ("OK", [b""]) - mock_conn.uid.return_value = ("OK", [b"1"]) + mock_conn.uid.return_value = ("OK", [b""]) mock_conn.select.return_value = ("OK", []) yield mock_conn From 9c34a2e3502d7efe535cb2fb5dbd469d350aa52b Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 08:37:46 -0700 Subject: [PATCH 62/88] linting --- custom_components/mail_and_packages/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 3a060370..330f0e86 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -516,8 +516,8 @@ def email_search( if subject is not None and not subject.isascii(): subject = subject.encode("utf-8") account.literal = subject - value = account.uid('SEARCH','CHARSET', 'UTF-8', 'SUBJECT') - else: + value = account.uid("SEARCH", "CHARSET", "UTF-8", "SUBJECT") + else: value = account.search("utf-8", imap_search) except Exception as err: _LOGGER.error("Error searching emails: %s", str(err)) From 16e388806d887d89b988d72f0db27c27b7baecec Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 11:02:20 -0700 Subject: [PATCH 63/88] fix: remove utf-8 encoding on default imap search --- custom_components/mail_and_packages/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 330f0e86..62044840 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -518,7 +518,7 @@ def email_search( account.literal = subject value = account.uid("SEARCH", "CHARSET", "UTF-8", "SUBJECT") else: - value = account.search("utf-8", imap_search) + value = account.search(None, imap_search) except Exception as err: _LOGGER.error("Error searching emails: %s", str(err)) value = "BAD", err.args[0] From 33599787efaeb2fe3e598b2bb0298b34bba65bd9 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 11:44:06 -0700 Subject: [PATCH 64/88] adjust search parameters --- custom_components/mail_and_packages/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 62044840..f7f97c4e 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -493,7 +493,6 @@ def email_search( if isinstance(address, list): if len(address) == 1: email_list = address[0] - else: email_list = '" FROM "'.join(address) prefix_list = " ".join(["OR"] * (len(address) - 1)) @@ -516,7 +515,8 @@ def email_search( if subject is not None and not subject.isascii(): subject = subject.encode("utf-8") account.literal = subject - value = account.uid("SEARCH", "CHARSET", "UTF-8", "SUBJECT") + imap_search = f"SEARCH CHARSET UTF-8 {the_date} SUBJECT" + value = account.uid("SEARCH", "CHARSET", "UTF-8", f"{the_date}", "SUBJECT") else: value = account.search(None, imap_search) except Exception as err: From b1f554aeec7d65b9f45116d3f5eca5499541431b Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 12:46:44 -0700 Subject: [PATCH 65/88] add buld_search function --- .../mail_and_packages/helpers.py | 81 ++++++++++++------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index f7f97c4e..22bd57c3 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -477,18 +477,16 @@ def update_time() -> str: return updated -def email_search( - account: Type[imaplib.IMAP4_SSL], address: list, date: str, subject: str = None -) -> tuple: - """Search emails with from, subject, senton date. +def build_search(address: list, date: str, subject: str = None) -> tuple: + """Build IMAP search query. - Returns a tuple + Return tuple of utf8 flag and search query. """ - imap_search = None # Holds our IMAP SEARCH command - prefix_list = None - email_list = address - search = None the_date = f'SINCE "{date}"' + imap_search = None + utf8_flag = False + prefix_list = None + email_list = None if isinstance(address, list): if len(address) == 1: @@ -500,28 +498,57 @@ def email_search( _LOGGER.debug("DEBUG subject: %s", subject) if subject is not None: - search = f'FROM "{email_list}" SUBJECT "{subject}" {the_date}' - else: - search = f'FROM "{email_list}" {the_date}' - - if prefix_list is not None: - imap_search = f"({prefix_list} {search})" + if not subject.isascii(): + utf8_flag = True + # if prefix_list is not None: + # imap_search = f"CHARSET UTF-8 {prefix_list}" + # imap_search = f'{imap_search} FROM "{email_list} {the_date} SUBJECT' + # else: + # imap_search = ( + # f"CHARSET UTF-8 FROM {email_list} {the_date} SUBJECT" + # ) + imap_search = f"{the_date} SUBJECT" + else: + if prefix_list is not None: + imap_search = f'({prefix_list} FROM "{email_list}" SUBJECT "{subject}" {the_date})' + else: + imap_search = f'(FROM "{email_list}" SUBJECT "{subject}" {the_date})' else: - imap_search = f"({search})" + if prefix_list is not None: + imap_search = f'({prefix_list} FROM "{email_list}" {the_date})' + else: + imap_search = f'(FROM "{email_list}" {the_date})' _LOGGER.debug("DEBUG imap_search: %s", imap_search) - try: - if subject is not None and not subject.isascii(): - subject = subject.encode("utf-8") - account.literal = subject - imap_search = f"SEARCH CHARSET UTF-8 {the_date} SUBJECT" - value = account.uid("SEARCH", "CHARSET", "UTF-8", f"{the_date}", "SUBJECT") - else: - value = account.search(None, imap_search) - except Exception as err: - _LOGGER.error("Error searching emails: %s", str(err)) - value = "BAD", err.args[0] + return (utf8_flag, imap_search) + + +def email_search( + account: Type[imaplib.IMAP4_SSL], address: list, date: str, subject: str = None +) -> tuple: + """Search emails with from, subject, senton date. + + Returns a tuple + """ + utf8_flag, search = build_search(address, date, subject) + + if utf8_flag: + subject = subject.encode("utf-8") + account.literal = subject + try: + value = account.uid("SEARCH", "CHARSET", "UTF-8", search) + except Exception as err: + _LOGGER.warning( + "Error searching emails with unicode characters: %s", str(err) + ) + value = "BAD", err.args[0] + else: + try: + value = account.search(None, search) + except Exception as err: + _LOGGER.error("Error searching emails: %s", str(err)) + value = "BAD", err.args[0] _LOGGER.debug("DEBUG email_search value: %s", value) From f8dce2d377a94b6b50ace938099559706c753a90 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 13:14:00 -0700 Subject: [PATCH 66/88] feat: add diagnostics --- .../mail_and_packages/diagnostics.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 custom_components/mail_and_packages/diagnostics.py diff --git a/custom_components/mail_and_packages/diagnostics.py b/custom_components/mail_and_packages/diagnostics.py new file mode 100644 index 00000000..ae4aaf9f --- /dev/null +++ b/custom_components/mail_and_packages/diagnostics.py @@ -0,0 +1,33 @@ +"""Provide diagnostics for Mail and Packages.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntry + +from .const import ( + COORDINATOR, + DOMAIN, +) + +REDACT_KEYS = {CONF_PASSWORD, CONF_USERNAME} + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + diag: dict[str, Any] = {} + diag["config"] = config_entry.as_dict() + return async_redact_data(diag, REDACT_KEYS) + + +async def async_get_device_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry, device: DeviceEntry +) -> dict[str, Any]: + """Return diagnostics for a device.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + return coordinator.data \ No newline at end of file From 3537e141e2c6c3c1c3cfb6f0c38188839ee03b1b Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 14:40:32 -0700 Subject: [PATCH 67/88] fix tests, pin versions --- .../mail_and_packages/diagnostics.py | 9 ++++++--- requirements.txt | 4 ++-- requirements_format.txt | 12 ++++++------ requirements_test.txt | 16 +++++++++------- tox.ini | 5 ++--- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/custom_components/mail_and_packages/diagnostics.py b/custom_components/mail_and_packages/diagnostics.py index ae4aaf9f..a951c2f4 100644 --- a/custom_components/mail_and_packages/diagnostics.py +++ b/custom_components/mail_and_packages/diagnostics.py @@ -16,8 +16,9 @@ REDACT_KEYS = {CONF_PASSWORD, CONF_USERNAME} + async def async_get_config_entry_diagnostics( - hass: HomeAssistant, config_entry: ConfigEntry + hass: HomeAssistant, config_entry: ConfigEntry # pylint: disable=unused-argument ) -> dict[str, Any]: """Return diagnostics for a config entry.""" diag: dict[str, Any] = {} @@ -26,8 +27,10 @@ async def async_get_config_entry_diagnostics( async def async_get_device_diagnostics( - hass: HomeAssistant, config_entry: ConfigEntry, device: DeviceEntry + hass: HomeAssistant, + config_entry: ConfigEntry, + device: DeviceEntry, # pylint: disable=unused-argument ) -> dict[str, Any]: """Return diagnostics for a device.""" coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] - return coordinator.data \ No newline at end of file + return coordinator.data diff --git a/requirements.txt b/requirements.txt index ccbc0efc..cd0978a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -imageio -python-resize-image \ No newline at end of file +imageio==2.16.0 +python-resize-image==1.1.20 \ No newline at end of file diff --git a/requirements_format.txt b/requirements_format.txt index 1e6db1fb..a870154b 100644 --- a/requirements_format.txt +++ b/requirements_format.txt @@ -1,6 +1,6 @@ -black -isort -flake8 -pydocstyle -pylint -mypy \ No newline at end of file +black==22.1.0 +isort==5.10.1 +flake8==4.0.1 +pydocstyle==6.1.1 +pylint==2.12.2 +mypy==0.931 \ No newline at end of file diff --git a/requirements_test.txt b/requirements_test.txt index c08fdf47..1894a041 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,9 +1,11 @@ -r requirements.txt -av +pip>=21.0,<22.1 +av==8.1.0 aiohttp_cors -aioresponses -black -isort -pytest -pytest-cov -pytest-homeassistant-custom-component +aioresponses==0.7.3 +black==22.1.0 +isort==5.10.1 +pytest==7.0.1 +pytest-cov==3.0.0 +pytest-homeassistant-custom-component==0.6.12 +homeassistant==2022.2.6 \ No newline at end of file diff --git a/tox.ini b/tox.ini index 1cb1727a..dea061dc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,17 @@ [tox] skipsdist = true -envlist = py38, py39, py310, lint, mypy +envlist = py39, py310, lint, mypy skip_missing_interpreters = True ignore_basepython_conflict = True [gh-actions] python = - 3.8: py38, lint, mypy 3.9: py39, lint, mypy 3.10: py310 [testenv] pip_version = pip>=21.0,<22.1 -install_command = python -m pip install --use-deprecated legacy-resolver {opts} {packages} +install_command = python -m pip install {opts} {packages} commands = pytest --timeout=30 --cov=custom_components/mail_and_packages/ --cov-report=xml {posargs} deps = From 0966af3c2bfb428b601870f223d5f26059af1d88 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 14:44:28 -0700 Subject: [PATCH 68/88] add dependabot configuration --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..cf7a39fb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" From 02d3d1068c9150e4d626180497e9023cfdf11b7f Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 14:45:45 -0700 Subject: [PATCH 69/88] fix dependency conflict --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1894a041..7d742328 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,6 +6,6 @@ aioresponses==0.7.3 black==22.1.0 isort==5.10.1 pytest==7.0.1 -pytest-cov==3.0.0 +pytest-cov pytest-homeassistant-custom-component==0.6.12 homeassistant==2022.2.6 \ No newline at end of file From 311f1aae9da948550a57eb87edb630ce4332d48a Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 14:51:49 -0700 Subject: [PATCH 70/88] dependency conflict --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 7d742328..706e710e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ aiohttp_cors aioresponses==0.7.3 black==22.1.0 isort==5.10.1 -pytest==7.0.1 +pytest pytest-cov pytest-homeassistant-custom-component==0.6.12 homeassistant==2022.2.6 \ No newline at end of file From aaa33f4bdecdf5c04ad36febb5411a57f770b153 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 15:05:03 -0700 Subject: [PATCH 71/88] dependency --- requirements_test.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 706e710e..ec01044e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,4 @@ -r requirements.txt -pip>=21.0,<22.1 av==8.1.0 aiohttp_cors aioresponses==0.7.3 From c8fcc5d29b62e24687587fb7c280dcde375c2d1d Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 15:34:31 -0700 Subject: [PATCH 72/88] add tests --- tests/test_diagnostics.py | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/test_diagnostics.py diff --git a/tests/test_diagnostics.py b/tests/test_diagnostics.py new file mode 100644 index 00000000..c1d18c3d --- /dev/null +++ b/tests/test_diagnostics.py @@ -0,0 +1,46 @@ +"""Test the Mail and Packages diagnostics.""" +from unittest.mock import patch + +import pytest + +from custom_components.mail_and_packages.const import DOMAIN +from custom_components.mail_and_packages.diagnostics import async_get_config_entry_diagnostics, async_get_device_diagnostics +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from tests.const import FAKE_CONFIG_DATA, FAKE_UPDATE_DATA + +async def test_config_entry_diagnostics(hass): + """Test the config entry level diagnostics data dump.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="imap.test.email", + data=FAKE_CONFIG_DATA, + ) + + entry.add_to_hass(hass) + result = await async_get_config_entry_diagnostics(hass, entry) + + assert isinstance(result, dict) + assert result["config"]["data"][CONF_HOST] == "imap.test.email" + assert result["config"]["data"][CONF_PORT] == 993 + assert result["config"]["data"][CONF_PASSWORD] == "**REDACTED**" + assert result["config"]["data"][CONF_USERNAME] == "**REDACTED**" + + + +async def test_device_diagnostics(hass, mock_update): + """Test the device level diagnostics data dump.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="imap.test.email", + data=FAKE_CONFIG_DATA, + ) + + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await async_get_device_diagnostics(hass, entry, None) + + assert result == FAKE_UPDATE_DATA \ No newline at end of file From f6500b40c1284a4a6ef4fd49083aad111eefa8ef Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 16:08:43 -0700 Subject: [PATCH 73/88] redact tracking and order number from diagnostics --- .../mail_and_packages/diagnostics.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/custom_components/mail_and_packages/diagnostics.py b/custom_components/mail_and_packages/diagnostics.py index a951c2f4..1acb7f44 100644 --- a/custom_components/mail_and_packages/diagnostics.py +++ b/custom_components/mail_and_packages/diagnostics.py @@ -1,11 +1,13 @@ """Provide diagnostics for Mail and Packages.""" from __future__ import annotations +import logging + from typing import Any from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_RESOURCES from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntry @@ -14,6 +16,7 @@ DOMAIN, ) +_LOGGER = logging.getLogger(__name__) REDACT_KEYS = {CONF_PASSWORD, CONF_USERNAME} @@ -33,4 +36,13 @@ async def async_get_device_diagnostics( ) -> dict[str, Any]: """Return diagnostics for a device.""" coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] - return coordinator.data + + for variable in coordinator.data: + if "tracking" in variable or "order" in variable: + _LOGGER.debug("Atempting to add: %s for redaction.", variable) + REDACT_KEYS.add(variable) + + _LOGGER.debug("Redacted keys: %s", REDACT_KEYS) + + return async_redact_data(coordinator.data, REDACT_KEYS) + From b5fe7c4669492ff62bf2e4babc8dc9a477a1d1e7 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 16:09:09 -0700 Subject: [PATCH 74/88] redact tracking and order numbers from diagnostics --- tests/const.py | 49 +++++++++++++++++++++++++++++++++++++++ tests/test_diagnostics.py | 4 ++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/tests/const.py b/tests/const.py index aca4bacd..77627c2d 100644 --- a/tests/const.py +++ b/tests/const.py @@ -689,3 +689,52 @@ "scan_interval": 20, "username": "user@fake.email", } + +FAKE_UPDATE_DATA_REDACTED = { + "image_name": "mail_today.gif", + "mail_updated": "2022-01-06T12:14:38+00:00", + "usps_mail": 6, + "usps_delivered": 3, + "usps_delivering": 3, + "usps_packages": 3, + "usps_tracking": "**REDACTED**", + "ups_delivered": 1, + "ups_delivering": 1, + "ups_packages": 1, + "ups_tracking": "**REDACTED**", + "fedex_delivered": 0, + "fedex_delivering": 2, + "fedex_packages": 2, + "fedex_tracking": "**REDACTED**", + "amazon_packages": 7, + "amazon_delivered": 2, + "amazon_order": "**REDACTED**", + "amazon_hub": 2, + "amazon_hub_code": 123456, + "capost_delivered": 1, + "capost_delivering": 1, + "capost_packages": 2, + "dhl_delivered": 0, + "dhl_delivering": 1, + "dhl_packages": 2, + "dhl_tracking": "**REDACTED**", + "zpackages_delivered": 7, + "zpackages_transit": 8, + "auspost_delivered": 2, + "auspost_delivering": 1, + "auspost_packages": 3, + "poczta_polska_delivering": 1, + "poczta_polska_packages": 1, + "inpost_pl_delivered": 2, + "inpost_pl_delivering": 1, + "inpost_pl_packages": 3, + "inpost_pl_tracking": "**REDACTED**", + "dpd_com_pl_delivered": 2, + "dpd_com_pl_delivering": 1, + "dpd_com_pl_packages": 3, + "dpd_com_pl_tracking": "**REDACTED**", + "gls_delivered": 2, + "gls_delivering": 1, + "gls_packages": 3, + "gls_tracking": "**REDACTED**", +} \ No newline at end of file diff --git a/tests/test_diagnostics.py b/tests/test_diagnostics.py index c1d18c3d..e9d4cb37 100644 --- a/tests/test_diagnostics.py +++ b/tests/test_diagnostics.py @@ -8,7 +8,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from pytest_homeassistant_custom_component.common import MockConfigEntry -from tests.const import FAKE_CONFIG_DATA, FAKE_UPDATE_DATA +from tests.const import FAKE_CONFIG_DATA, FAKE_UPDATE_DATA, FAKE_UPDATE_DATA_REDACTED async def test_config_entry_diagnostics(hass): """Test the config entry level diagnostics data dump.""" @@ -43,4 +43,4 @@ async def test_device_diagnostics(hass, mock_update): result = await async_get_device_diagnostics(hass, entry, None) - assert result == FAKE_UPDATE_DATA \ No newline at end of file + assert result == FAKE_UPDATE_DATA_REDACTED \ No newline at end of file From 18513737296c71ffd2bb344ceb1ebe3fa95c2cf3 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 18:14:46 -0700 Subject: [PATCH 75/88] linting --- custom_components/mail_and_packages/diagnostics.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/custom_components/mail_and_packages/diagnostics.py b/custom_components/mail_and_packages/diagnostics.py index 1acb7f44..27e1e951 100644 --- a/custom_components/mail_and_packages/diagnostics.py +++ b/custom_components/mail_and_packages/diagnostics.py @@ -2,19 +2,15 @@ from __future__ import annotations import logging - from typing import Any from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_RESOURCES +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntry -from .const import ( - COORDINATOR, - DOMAIN, -) +from .const import COORDINATOR, DOMAIN _LOGGER = logging.getLogger(__name__) REDACT_KEYS = {CONF_PASSWORD, CONF_USERNAME} @@ -45,4 +41,3 @@ async def async_get_device_diagnostics( _LOGGER.debug("Redacted keys: %s", REDACT_KEYS) return async_redact_data(coordinator.data, REDACT_KEYS) - From 9b850aa8dbf19d04b77c04f618e22663618272e4 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 18:25:58 -0700 Subject: [PATCH 76/88] adjust requirements --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cd0978a2..ccbc0efc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -imageio==2.16.0 -python-resize-image==1.1.20 \ No newline at end of file +imageio +python-resize-image \ No newline at end of file From bc3c5d6b6f19b484371b19e62bc6fe6f31a85a62 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Feb 2022 18:32:52 -0700 Subject: [PATCH 77/88] adjust requirements --- requirements_test.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index ec01044e..606c8a34 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,10 +1,10 @@ -r requirements.txt -av==8.1.0 +av aiohttp_cors -aioresponses==0.7.3 -black==22.1.0 -isort==5.10.1 +aioresponses +black +isort pytest pytest-cov -pytest-homeassistant-custom-component==0.6.12 -homeassistant==2022.2.6 \ No newline at end of file +pytest-homeassistant-custom-component +homeassistant \ No newline at end of file From 06b57013e10b31f0ae51760ba12a5c6d8a25ebfc Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 15 Feb 2022 06:35:48 -0700 Subject: [PATCH 78/88] unpin versions --- requirements_format.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements_format.txt b/requirements_format.txt index a870154b..1e6db1fb 100644 --- a/requirements_format.txt +++ b/requirements_format.txt @@ -1,6 +1,6 @@ -black==22.1.0 -isort==5.10.1 -flake8==4.0.1 -pydocstyle==6.1.1 -pylint==2.12.2 -mypy==0.931 \ No newline at end of file +black +isort +flake8 +pydocstyle +pylint +mypy \ No newline at end of file From 65fc4d8076c9cc18b849291a4d7f21bf966d00dd Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 17 Feb 2022 13:35:49 -0700 Subject: [PATCH 79/88] fix(amazon): add german language support --- custom_components/mail_and_packages/const.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index e5af9ef3..29801a70 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -78,7 +78,12 @@ "amazon.com.au", "amazon.pl", ] -AMAZON_DELIVERED_SUBJECT = ["Delivered: Your", "Consegna effettuata:", "Dostarczono: "] +AMAZON_DELIVERED_SUBJECT = [ + "Delivered: Your", + "Consegna effettuata:", + "Dostarczono:", + "Geliefert:", +] AMAZON_SHIPMENT_TRACKING = ["shipment-tracking", "conferma-spedizione"] AMAZON_EMAIL = "order-update@" AMAZON_PACKAGES = "amazon_packages" @@ -101,13 +106,22 @@ "Arriverà:", "arriving:", "Dostawa:", + "Zustellung:", ] AMAZON_EXCEPTION_SUBJECT = "Delivery update:" AMAZON_EXCEPTION_BODY = "running late" AMAZON_EXCEPTION = "amazon_exception" AMAZON_EXCEPTION_ORDER = "amazon_exception_order" AMAZON_PATTERN = "[0-9]{3}-[0-9]{7}-[0-9]{7}" -AMAZON_LANGS = ["it_IT", "it_IT.UTF-8", "pl_PL", "pl_PL.UTF-8", ""] +AMAZON_LANGS = [ + "it_IT", + "it_IT.UTF-8", + "pl_PL", + "pl_PL.UTF-8", + "de_DE", + "de_DE.UTF-8", + "", +] # Sensor Data SENSOR_DATA = { From cc1bc4d0765da1a9ba406fb55add850f24158368 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 17 Feb 2022 13:41:50 -0700 Subject: [PATCH 80/88] fix tests --- tests/test_helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 272f628a..9dd32f87 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -812,7 +812,7 @@ async def test_amazon_search_results(hass, mock_imap_amazon_shipped): result = amazon_search( mock_imap_amazon_shipped, "test/path", hass, "testfilename.jpg" ) - assert result == 30 + assert result == 40 async def test_amazon_search_delivered( @@ -821,7 +821,7 @@ async def test_amazon_search_delivered( result = amazon_search( mock_imap_amazon_delivered, "test/path", hass, "testfilename.jpg" ) - assert result == 30 + assert result == 40 assert mock_download_img.called @@ -831,7 +831,7 @@ async def test_amazon_search_delivered_it( result = amazon_search( mock_imap_amazon_delivered_it, "test/path", hass, "testfilename.jpg" ) - assert result == 30 + assert result == 40 async def test_amazon_hub(hass, mock_imap_amazon_the_hub): From 616efe14093e3a4a41911bb6d9e6dbdcf2a36e53 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 17 Feb 2022 13:46:43 -0700 Subject: [PATCH 81/88] fix missing from in email search --- custom_components/mail_and_packages/helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 22bd57c3..fb6d42b9 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -494,6 +494,8 @@ def build_search(address: list, date: str, subject: str = None) -> tuple: else: email_list = '" FROM "'.join(address) prefix_list = " ".join(["OR"] * (len(address) - 1)) + else: + email_list = address _LOGGER.debug("DEBUG subject: %s", subject) From 7bf5280469546a7337ff878dcfe120d9717ebf32 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Mar 2022 15:36:26 -0700 Subject: [PATCH 82/88] fix: changes for HA core 2022.4 --- custom_components/mail_and_packages/const.py | 8 ++--- .../mail_and_packages/helpers.py | 7 +++-- custom_components/mail_and_packages/sensor.py | 9 ++++-- tests/test_helpers.py | 31 ++----------------- tests/test_sensor.py | 1 - 5 files changed, 18 insertions(+), 38 deletions(-) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 29801a70..8f35fa89 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -4,7 +4,7 @@ from typing import Final from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription -from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC +from homeassistant.helpers.entity import EntityCategory DOMAIN = "mail_and_packages" DOMAIN_DATA = f"{DOMAIN}_data" @@ -378,7 +378,7 @@ name="Mail Updated", icon="mdi:update", key="mail_updated", - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + entity_category=EntityCategory.DIAGNOSTIC, device_class=SensorDeviceClass.TIMESTAMP, ), # USPS @@ -674,13 +674,13 @@ name="Mail Image System Path", icon="mdi:folder-multiple-image", key="usps_mail_image_system_path", - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + entity_category=EntityCategory.DIAGNOSTIC, ), "usps_mail_image_url": SensorEntityDescription( name="Mail Image URL", icon="mdi:link-variant", key="usps_mail_image_url", - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + entity_category=EntityCategory.DIAGNOSTIC, ), } diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index fb6d42b9..33a56984 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -402,7 +402,7 @@ def fetch( total += fetch(hass, config, account, data, delivering) count[sensor] = max(0, total) elif sensor == "mail_updated": - count[sensor] = dt_util.parse_datetime(update_time()) + count[sensor] = update_time() else: count[sensor] = get_count( account, sensor, False, img_out_path, hass, amazon_image_name @@ -466,13 +466,14 @@ def get_formatted_date() -> str: return today -def update_time() -> str: +def update_time() -> Any: """Get update time. Returns current timestamp as string """ # updated = datetime.datetime.now().strftime("%b-%d-%Y %I:%M %p") - updated = datetime.datetime.now(timezone.utc).isoformat(timespec="minutes") + # updated = datetime.datetime.now(timezone.utc).isoformat(timespec="minutes") + updated = datetime.datetime.now(timezone.utc) return updated diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index 1f1c9f30..041c9c81 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -3,8 +3,10 @@ https://blog.kalavala.net/usps/homeassistant/mqtt/2018/01/12/usps.html Configuration code contribution from @firstof9 https://github.com/firstof9/ """ +import datetime +from datetime import timezone import logging -from typing import Optional +from typing import Any, Optional from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.config_entries import ConfigEntry @@ -12,6 +14,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import CoordinatorEntity +from .helpers import update_time from .const import ( AMAZON_EXCEPTION_ORDER, AMAZON_ORDER, @@ -87,12 +90,14 @@ def name(self) -> str: return self._name @property - def native_value(self) -> Optional[int]: + def native_value(self) -> Any: """Return the state of the sensor.""" value = None if self.type in self.coordinator.data.keys(): value = self.coordinator.data[self.type] + if self.type == "mail_updated": + value = datetime.datetime.now(timezone.utc) else: value = None return value diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 9dd32f87..1c5437b9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -50,9 +50,7 @@ async def test_get_formatted_date(): async def test_update_time(): - assert update_time() == datetime.datetime.now(timezone.utc).isoformat( - timespec="minutes" - ) + assert isinstance(update_time(), datetime.datetime) async def test_cleanup_images(mock_listdir, mock_osremove): @@ -86,7 +84,6 @@ async def test_process_emails( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -114,9 +111,7 @@ async def test_process_emails( state = hass.states.get(MAIL_IMAGE_URL_ENTITY) assert state.state == "http://127.0.0.1:8123/local/mail_and_packages/testfile.gif" result = process_emails(hass, config) - assert result["mail_updated"] == datetime.datetime( - 2022, 1, 6, 12, 14, tzinfo=datetime.timezone.utc - ) + assert isinstance(result["mail_updated"],datetime.datetime) assert result["zpackages_delivered"] == 0 assert result["zpackages_transit"] == 0 assert result["amazon_delivered"] == 0 @@ -132,7 +127,6 @@ async def test_process_emails_external( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -164,9 +158,7 @@ async def test_process_emails_external( == "http://really.fake.host.net:8123/local/mail_and_packages/testfile.gif" ) result = process_emails(hass, config) - assert result["mail_updated"] == datetime.datetime( - 2022, 1, 6, 12, 14, tzinfo=datetime.timezone.utc - ) + assert isinstance(result["mail_updated"],datetime.datetime) assert result["zpackages_delivered"] == 0 assert result["zpackages_transit"] == 0 assert result["amazon_delivered"] == 0 @@ -191,7 +183,6 @@ async def test_process_emails_external_error( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -221,7 +212,6 @@ async def test_process_emails_copytree_error( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_hash_file, mock_getctime_today, @@ -262,7 +252,6 @@ async def test_process_emails_non_random( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -289,7 +278,6 @@ async def test_process_emails_random( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -316,7 +304,6 @@ async def test_process_nogif( mock_osremove, mock_osmakedir, mock_listdir_noimgs, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -343,7 +330,6 @@ async def test_process_old_image( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -370,7 +356,6 @@ async def test_process_folder_error( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -400,7 +385,6 @@ async def test_image_filename_oserr( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file_oserr, @@ -428,7 +412,6 @@ async def test_image_getctime_oserr( mock_osremove, mock_osmakedir, mock_listdir, - mock_update_time, mock_copyfile, mock_copytree, mock_hash_file, @@ -515,7 +498,6 @@ async def test_informed_delivery_emails( mock_osmakedir, mock_listdir, mock_os_path_splitext, - mock_update_time, mock_image, mock_io, mock_resizeimage, @@ -539,7 +521,6 @@ async def test_get_mails_imageio_error( mock_osmakedir, mock_listdir, mock_os_path_splitext, - mock_update_time, mock_image, mock_resizeimage, mock_copyfile, @@ -563,7 +544,6 @@ async def test_informed_delivery_emails_mp4( mock_osmakedir, mock_listdir, mock_os_path_splitext, - mock_update_time, mock_image, mock_io, mock_resizeimage, @@ -587,7 +567,6 @@ async def test_informed_delivery_emails_open_err( mock_osremove, mock_osmakedir, mock_os_path_splitext, - mock_update_time, mock_image, mock_io, mock_resizeimage, @@ -612,7 +591,6 @@ async def test_informed_delivery_emails_io_err( mock_listdir, mock_osremove, mock_osmakedir, - mock_update_time, mock_os_path_splitext, mock_image, mock_resizeimage, @@ -636,7 +614,6 @@ async def test_informed_delivery_missing_mailpiece( mock_listdir, mock_osremove, mock_osmakedir, - mock_update_time, mock_os_path_splitext, mock_image, mock_io, @@ -656,7 +633,6 @@ async def test_informed_delivery_no_mail( mock_listdir, mock_osremove, mock_osmakedir, - mock_update_time, mock_os_path_splitext, mock_image, mock_io, @@ -677,7 +653,6 @@ async def test_informed_delivery_no_mail_copy_error( mock_listdir, mock_osremove, mock_osmakedir, - mock_update_time, mock_os_path_splitext, mock_image, mock_io, diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 51799f84..c0a0fc79 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -22,7 +22,6 @@ async def test_sensor(hass, mock_update): # Check for mail_updated sensor reporting value from test data state = hass.states.get("sensor.mail_updated") assert state - assert state.state == "2022-01-06T12:14:38+00:00" # Make sure the rest of the sensors are importing our test data state = hass.states.get("sensor.mail_usps_mail") From 5d5539c31b5916309f6b706285e33aa8a610df8b Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Mar 2022 15:41:57 -0700 Subject: [PATCH 83/88] linting --- custom_components/mail_and_packages/helpers.py | 1 - custom_components/mail_and_packages/sensor.py | 1 - 2 files changed, 2 deletions(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index 33a56984..e8b126ef 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -27,7 +27,6 @@ CONF_USERNAME, ) from homeassistant.core import HomeAssistant -from homeassistant.util import dt as dt_util from PIL import Image from resizeimage import resizeimage diff --git a/custom_components/mail_and_packages/sensor.py b/custom_components/mail_and_packages/sensor.py index 041c9c81..e3e373c0 100644 --- a/custom_components/mail_and_packages/sensor.py +++ b/custom_components/mail_and_packages/sensor.py @@ -14,7 +14,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .helpers import update_time from .const import ( AMAZON_EXCEPTION_ORDER, AMAZON_ORDER, From 830c4eb134b79ded0a927760c944cc04bcfcba97 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Mar 2022 16:01:29 -0700 Subject: [PATCH 84/88] docs: update hacs info.md --- info.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/info.md b/info.md index e8e3f951..2d52617e 100644 --- a/info.md +++ b/info.md @@ -7,9 +7,10 @@ It may contain bugs or break functionality in addition to adding new features an ![GitHub release (latest by date)](https://img.shields.io/github/v/release/moralmunky/Home-Assistant-Mail-And-Packages) [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) ![GitHub contributors](https://img.shields.io/github/contributors/moralmunky/Home-Assistant-Mail-And-Packages) -![Maintenance](https://img.shields.io/maintenance/yes/2021) -![GitHub commit activity](https://img.shields.io/github/commit-activity/m/moralmunky/Home-Assistant-Mail-And-Packages) -![GitHub last commit](https://img.shields.io/github/last-commit/moralmunky/Home-Assistant-Mail-And-Packages) +![Maintenance](https://img.shields.io/maintenance/yes/2022) +![GitHub commit activity](https://img.shields.io/github/commit-activity/y/moralmunky/Home-Assistant-Mail-And-Packages) +![GitHub commits since tagged version](https://img.shields.io/github/commits-since/moralmunky/Home-Assistant-Mail-And-Packages/0.3.3-2/dev) +![GitHub last commit](https://img.shields.io/github/last-commit/moralmunky/Home-Assistant-Mail-And-Packages/dev) ## About Mail and Packages integration From 6dc5277668753f6ea28570c712bf70e19658f511 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 21 Mar 2022 06:39:15 -0700 Subject: [PATCH 85/88] fix: spelling mistake in UI --- custom_components/mail_and_packages/strings.json | 4 ++-- custom_components/mail_and_packages/translations/en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/mail_and_packages/strings.json b/custom_components/mail_and_packages/strings.json index 83cbe9a0..842a50cb 100644 --- a/custom_components/mail_and_packages/strings.json +++ b/custom_components/mail_and_packages/strings.json @@ -34,7 +34,7 @@ "image_security": "Random Image Filename", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "generate_mp4": "Create mp4 from images", - "amazon_fwds": "Amazon fowarded email addresses", + "amazon_fwds": "Amazon forwarded email addresses", "amazon_days": "Days back to check for Amazon emails", "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" @@ -82,7 +82,7 @@ "image_security": "Random Image Filename", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "generate_mp4": "Create mp4 from images", - "amazon_fwds": "Amazon fowarded email addresses", + "amazon_fwds": "Amazon forwarded email addresses", "amazon_days": "Days back to check for Amazon emails", "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" diff --git a/custom_components/mail_and_packages/translations/en.json b/custom_components/mail_and_packages/translations/en.json index c96e51a3..8df90a79 100644 --- a/custom_components/mail_and_packages/translations/en.json +++ b/custom_components/mail_and_packages/translations/en.json @@ -34,7 +34,7 @@ "image_security": "Random Image Filename", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "generate_mp4": "Create mp4 from images", - "amazon_fwds": "Amazon fowarded email addresses", + "amazon_fwds": "Amazon forwarded email addresses", "amazon_days": "Days back to check for Amazon emails", "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" @@ -82,7 +82,7 @@ "image_security": "Random Image Filename", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "generate_mp4": "Create mp4 from images", - "amazon_fwds": "Amazon fowarded email addresses", + "amazon_fwds": "Amazon forwarded email addresses", "amazon_days": "Days back to check for Amazon emails", "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" From 2211a899a32b26449f65e3052968b3788a8e07c7 Mon Sep 17 00:00:00 2001 From: Morgan Kesler Date: Wed, 13 Apr 2022 09:05:23 -0400 Subject: [PATCH 86/88] fix(shippers): Add extra matcher for USPS Informed Delivery --- custom_components/mail_and_packages/const.py | 1 + tests/test_helpers.py | 1 + 2 files changed, 2 insertions(+) diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 8f35fa89..e9429935 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -146,6 +146,7 @@ "USPSInformedDelivery@usps.gov", "USPSInformeddelivery@email.informeddelivery.usps.com", "USPSInformeddelivery@informeddelivery.usps.com", + "USPS Informed Delivery", ], "subject": ["Your Daily Digest"], }, diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 1c5437b9..004dddf1 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -513,6 +513,7 @@ async def test_informed_delivery_emails( assert "USPSInformedDelivery@usps.gov" in caplog.text assert "USPSInformeddelivery@informeddelivery.usps.com" in caplog.text assert "USPSInformeddelivery@email.informeddelivery.usps.com" in caplog.text + assert "USPS Informed Delivery" in caplog.text async def test_get_mails_imageio_error( From 9a807b1c04609252178ec30e8da13de0ec214db6 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 15 Apr 2022 07:01:41 -0700 Subject: [PATCH 87/88] fix: update translations --- .../mail_and_packages/strings.json | 1 - .../mail_and_packages/translations/ca.json | 37 +++- .../mail_and_packages/translations/de.json | 37 +++- .../mail_and_packages/translations/en.json | 193 +++++++++--------- .../mail_and_packages/translations/es.json | 37 +++- .../translations/es_419.json | 37 +++- .../mail_and_packages/translations/fi.json | 37 +++- .../mail_and_packages/translations/fr.json | 37 +++- .../mail_and_packages/translations/hu.json | 37 +++- .../mail_and_packages/translations/it.json | 37 +++- .../mail_and_packages/translations/ko.json | 37 +++- .../mail_and_packages/translations/nl.json | 37 +++- .../mail_and_packages/translations/no.json | 37 +++- .../mail_and_packages/translations/pl.json | 37 +++- .../mail_and_packages/translations/pt.json | 37 +++- .../mail_and_packages/translations/pt_BR.json | 37 +++- .../mail_and_packages/translations/ru.json | 37 +++- .../mail_and_packages/translations/sl.json | 37 +++- .../mail_and_packages/translations/sv.json | 37 +++- .../translations/zh_Hant_HK.json | 37 +++- 20 files changed, 673 insertions(+), 187 deletions(-) diff --git a/custom_components/mail_and_packages/strings.json b/custom_components/mail_and_packages/strings.json index 842a50cb..82c5f45d 100644 --- a/custom_components/mail_and_packages/strings.json +++ b/custom_components/mail_and_packages/strings.json @@ -1,5 +1,4 @@ { - "title": "Mail and Packages", "config": { "abort": { "single_instance_allowed": "Only a single configuration of Mail and Packages is allowed." diff --git a/custom_components/mail_and_packages/translations/ca.json b/custom_components/mail_and_packages/translations/ca.json index c289ca08..b3d9264c 100644 --- a/custom_components/mail_and_packages/translations/ca.json +++ b/custom_components/mail_and_packages/translations/ca.json @@ -6,7 +6,11 @@ "error": { "communication": "No es pot connectar o iniciar la sessió al servidor de correu. Consulteu el registre per obtenir més detalls.", "invalid_path": "Guardeu les imatges en un altre directori.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Finalitzeu la configuració personalitzant la següent en funció de la vostra instal·lació de correu electrònic i la instal·lació d’assistència a casa. \n\n Per obtenir més informació sobre les opcions [Integració de paquets i correus] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), reviseu les opcions [configuració, plantilles , secció i automatitzacions] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) a GitHub.", "title": "Correu i paquets (pas 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "No es pot connectar o iniciar la sessió al servidor de correu. Consulteu el registre per obtenir més detalls.", "invalid_path": "Guardeu les imatges en un altre directori.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Adreça de correu electrònic de reenviament no vàlida.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Llista de sensors", "imap_timeout": "Temps en segons abans del temps d'espera de la connexió (segons, mínim 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Crea una imatge per a aplicacions de notificació" + "allow_external": "Crea una imatge per a aplicacions de notificació", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Finalitzeu la configuració personalitzant la següent en funció de la vostra instal·lació de correu electrònic i la instal·lació d’assistència a casa. \n\n Per obtenir més informació sobre les opcions [Integració de paquets i correus](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), reviseu les opcions [configuració, plantilles , secció i automatitzacions](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) a GitHub.", "title": "Correu i paquets (pas 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/de.json b/custom_components/mail_and_packages/translations/de.json index bdf75a4d..f1afc2dc 100644 --- a/custom_components/mail_and_packages/translations/de.json +++ b/custom_components/mail_and_packages/translations/de.json @@ -6,7 +6,11 @@ "error": { "communication": "Es kann keine Verbindung zum Mailserver hergestellt oder angemeldet werden. Bitte überprüfen Sie das Protokoll für Details.", "invalid_path": "Bitte speichern Sie die Bilder in einem anderen Verzeichnis.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Beenden Sie die Konfiguration, indem Sie Folgendes basierend auf Ihrer E-Mail-Struktur und der Installation von Home Assistant anpassen. \n\n Weitere Informationen zu den Optionen [Mail- und Paketintegration] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) finden Sie in den [Konfiguration, Vorlagen und Abschnitt Automatisierungen] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) auf GitHub.", "title": "Post und Pakete (Schritt 2 von 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Es kann keine Verbindung zum Mailserver hergestellt oder angemeldet werden. Bitte überprüfen Sie das Protokoll für Details.", "invalid_path": "Bitte speichern Sie die Bilder in einem anderen Verzeichnis.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Ungültige Weiterleitungs-E-Mail-Adresse.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Liste der Sensoren", "imap_timeout": "Zeit in Sekunden vor dem Verbindungszeitlimit (Sekunden, mindestens 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Erstellen Sie ein Bild für Benachrichtigungs-Apps" + "allow_external": "Erstellen Sie ein Bild für Benachrichtigungs-Apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Beenden Sie die Konfiguration, indem Sie Folgendes basierend auf Ihrer E-Mail-Struktur und der Installation von Home Assistant anpassen. \n\n Weitere Informationen zu den Optionen [Mail- und Paketintegration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) finden Sie in den [Konfiguration, Vorlagen und Abschnitt Automatisierungen](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) auf GitHub.", "title": "Post und Pakete (Schritt 2 von 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/en.json b/custom_components/mail_and_packages/translations/en.json index 8df90a79..44e793e9 100644 --- a/custom_components/mail_and_packages/translations/en.json +++ b/custom_components/mail_and_packages/translations/en.json @@ -1,102 +1,103 @@ { - "title": "Mail and Packages", - "config": { - "abort": { - "single_instance_allowed": "Only a single configuration of Mail and Packages is allowed." - }, - "error": { - "communication": "Unable to connect or login to the mail server. Please check the log for details.", - "invalid_path": "Please store the images in another directory.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg", - "amazon_domain": "Invalid forwarding email address.", - "file_not_found": "Image file not found", - "scan_too_low": "Scan interval too low (minimum 5)", - "timeout_too_low": "IMAP timeout too low (minumim 10)" - }, - "step": { - "user": { - "data": { - "host": "Host", - "password": "Password", - "port": "Port", - "username": "Username" + "config": { + "abort": { + "single_instance_allowed": "Only a single configuration of Mail and Packages is allowed." }, - "description": "Please enter the connection information of your mail server.", - "title": "Mail and Packages (Step 1 of 2)" - }, - "config_2": { - "data": { - "folder": "Mail Folder", - "resources": "Sensors List", - "scan_interval": "Scanning Interval (minutes, minimum 5)", - "image_path": "Image Path", - "gif_duration": "Image Duration (seconds)", - "image_security": "Random Image Filename", - "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", - "generate_mp4": "Create mp4 from images", - "amazon_fwds": "Amazon forwarded email addresses", - "amazon_days": "Days back to check for Amazon emails", - "allow_external": "Create image for notification apps", - "custom_img": "Use custom 'no image' image?" + "error": { + "communication": "Unable to connect or login to the mail server. Please check the log for details.", + "invalid_path": "Please store the images in another directory.", + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, - "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma.", - "title": "Mail and Packages (Step 2 of 2)" - }, - "options_3": { - "data": { - "custom_img_file": "Path to custom image: (ie: images/my_custom_no_mail.jpg)" + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Port", + "username": "Username" + }, + "description": "Please enter the connection information of your mail server.", + "title": "Mail and Packages (Step 1 of 2)" + }, + "config_2": { + "data": { + "folder": "Mail Folder", + "scan_interval": "Scanning Interval (minutes)", + "image_path": "Image Path", + "gif_duration": "Image Duration (seconds)", + "image_security": "Random Image Filename", + "generate_mp4": "Create mp4 from images", + "resources": "Sensors List", + "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", + "amazon_fwds": "Amazon fowarded email addresses", + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" + }, + "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.", + "title": "Mail and Packages (Step 2 of 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" + } }, - "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", - "title": "Mail and Packages (Step 3 of 3)" - } - } - }, - "options": { - "error": { - "communication": "Unable to connect or login to the mail server. Please check the log for details.", - "invalid_path": "Please store the images in another directory.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg", - "amazon_domain": "Invalid forwarding email address.", - "file_not_found": "Image file not found", - "scan_too_low": "Scan interval too low (minimum 5)", - "timeout_too_low": "IMAP timeout too low (minumim 10)" + "title": "Mail and Packages" }, - "step": { - "init": { - "data": { - "host": "Host", - "password": "Password", - "port": "Port", - "username": "Username" - }, - "description": "Please enter the connection information of your mail server.", - "title": "Mail and Packages (Step 1 of 2)" - }, - "options_2": { - "data": { - "folder": "Mail Folder", - "resources": "Sensors List", - "scan_interval": "Scanning Interval (minutes, minimum 5)", - "image_path": "Image Path", - "gif_duration": "Image Duration (seconds)", - "image_security": "Random Image Filename", - "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", - "generate_mp4": "Create mp4 from images", - "amazon_fwds": "Amazon forwarded email addresses", - "amazon_days": "Days back to check for Amazon emails", - "allow_external": "Create image for notification apps", - "custom_img": "Use custom 'no image' image?" + "options": { + "error": { + "communication": "Unable to connect or login to the mail server. Please check the log for details.", + "invalid_path": "Please store the images in another directory.", + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, - "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma.", - "title": "Mail and Packages (Step 2 of 2)" - }, - "options_3": { - "data": { - "custom_img_file": "Path to custom image: (ie: images/my_custom_no_mail.jpg)" - }, - "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", - "title": "Mail and Packages (Step 3 of 3)" - } - } - } -} + "step": { + "init": { + "data": { + "host": "Host", + "password": "Password", + "port": "Port", + "username": "Username" + }, + "description": "Please enter the connection information of your mail server.", + "title": "Mail and Packages (Step 1 of 2)" + }, + "options_2": { + "data": { + "folder": "Mail Folder", + "scan_interval": "Scanning Interval (minutes)", + "image_path": "Image Path", + "gif_duration": "Image Duration (seconds)", + "image_security": "Random Image Filename", + "generate_mp4": "Create mp4 from images", + "resources": "Sensors List", + "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", + "amazon_fwds": "Amazon forwarded email addresses", + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" + }, + "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.", + "title": "Mail and Packages (Step 2 of 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" + } + } + }, + "title": "Mail and Packages" +} \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/es.json b/custom_components/mail_and_packages/translations/es.json index f75265e1..70f237fd 100644 --- a/custom_components/mail_and_packages/translations/es.json +++ b/custom_components/mail_and_packages/translations/es.json @@ -6,7 +6,11 @@ "error": { "communication": "No se puede conectar o iniciar sesión en el servidor de correo. Por favor, consulte el registro para más detalles.", "invalid_path": "Guarde las imágenes en otro directorio.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Termine la configuración personalizando lo siguiente según su estructura de correo electrónico y la instalación de Home Assistant. \n\n Para obtener detalles sobre las opciones [Integración de correo y paquetes] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) revise las [configuración, plantillas , y la sección de automatizaciones] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) en GitHub.", "title": "Correo y paquetes (Paso 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "No se puede conectar o iniciar sesión en el servidor de correo. Por favor, consulte el registro para más detalles.", "invalid_path": "Guarde las imágenes en otro directorio.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Dirección de correo electrónico de reenvío no válida.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Lista de sensores", "imap_timeout": "Tiempo en segundos antes del tiempo de espera de la conexión (segundos, mínimo 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Crear imagen para aplicaciones de notificación" + "allow_external": "Crear imagen para aplicaciones de notificación", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Termine la configuración personalizando lo siguiente según su estructura de correo electrónico y la instalación de Home Assistant. \n\n Para obtener detalles sobre las opciones [Integración de correo y paquetes](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) revise las [configuración, plantillas , y la sección de automatizaciones](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) en GitHub.", "title": "Correo y paquetes (Paso 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/es_419.json b/custom_components/mail_and_packages/translations/es_419.json index 2335ebef..4165d6dc 100644 --- a/custom_components/mail_and_packages/translations/es_419.json +++ b/custom_components/mail_and_packages/translations/es_419.json @@ -6,7 +6,11 @@ "error": { "communication": "No se puede conectar o iniciar sesión en el servidor de correo. Por favor, consulte el registro para más detalles.", "invalid_path": "Guarde las imágenes en otro directorio.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Termine la configuración personalizando lo siguiente según su estructura de correo electrónico y la instalación de Home Assistant. \n\n Para obtener detalles sobre las opciones [Integración de correo y paquetes] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) revise las [configuración, plantillas , y la sección de automatizaciones] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) en GitHub.", "title": "Correo y paquetes (Paso 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "No se puede conectar o iniciar sesión en el servidor de correo. Por favor, consulte el registro para más detalles.", "invalid_path": "Guarde las imágenes en otro directorio.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Dirección de correo electrónico de reenvío no válida.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Termine la configuración personalizando lo siguiente según su estructura de correo electrónico y la instalación de Home Assistant. \n\n Para obtener detalles sobre las opciones [Integración de correo y paquetes](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) revise las [configuración, plantillas , y la sección de automatizaciones](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) en GitHub.", "title": "Correo y paquetes (Paso 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/fi.json b/custom_components/mail_and_packages/translations/fi.json index 82309c1f..7c1159ac 100644 --- a/custom_components/mail_and_packages/translations/fi.json +++ b/custom_components/mail_and_packages/translations/fi.json @@ -6,7 +6,11 @@ "error": { "communication": "Ei voida muodostaa yhteyttä tai kirjautua sisään postipalvelimeen. Tarkista lokista yksityiskohdat.", "invalid_path": "Tallenna kuvat toiseen hakemistoon.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Viimeistele kokoonpano mukauttamalla seuraava sähköpostirakenteen ja Home Assistant -asennuksen perusteella. \n\n Lisätietoja [Posti ja paketit-integroinnista] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) -asetuksista on [kokoonpano, mallit , ja automaatiot-osio] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) GitHubissa.", "title": "Posti ja paketit (vaihe 2/2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Ei voida muodostaa yhteyttä tai kirjautua sisään postipalvelimeen. Tarkista lokista yksityiskohdat.", "invalid_path": "Tallenna kuvat toiseen hakemistoon.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Virheellinen edelleenlähetyssähköpostiosoite.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Anturiluettelo", "imap_timeout": "Aika sekunteina ennen yhteyden aikakatkaisua (sekuntia, vähintään 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Luo kuva ilmoitussovelluksia varten" + "allow_external": "Luo kuva ilmoitussovelluksia varten", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Viimeistele kokoonpano mukauttamalla seuraava sähköpostirakenteen ja Home Assistant -asennuksen perusteella. \n\n Lisätietoja [Posti ja paketit-integroinnista](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) -asetuksista on [kokoonpano, mallit , ja automaatiot-osio](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) GitHubissa.", "title": "Posti ja paketit (vaihe 2/2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/fr.json b/custom_components/mail_and_packages/translations/fr.json index 46abf7e8..704c8aaf 100644 --- a/custom_components/mail_and_packages/translations/fr.json +++ b/custom_components/mail_and_packages/translations/fr.json @@ -6,7 +6,11 @@ "error": { "communication": "Impossible de se connecter ou de se connecter au serveur de messagerie. Veuillez consulter le journal pour plus de détails.", "invalid_path": "Veuillez stocker les images dans un autre répertoire.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Terminez la configuration en personnalisant les éléments suivants en fonction de votre structure de messagerie et de l'installation de Home Assistant. \n\n Pour plus de détails sur les [Intégration de messagerie et de packages] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), passez en revue les [configuration, modèles et section automatisations] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) sur GitHub.", "title": "Courrier et colis (étape 2 sur 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Impossible de se connecter ou de se connecter au serveur de messagerie. Veuillez consulter le journal pour plus de détails.", "invalid_path": "Veuillez stocker les images dans un autre répertoire.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Adresse e-mail de transfert non valide.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Liste des capteurs", "imap_timeout": "Temps en secondes avant l'expiration de la connexion (secondes, minimum 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Créer une image pour les applications de notification" + "allow_external": "Créer une image pour les applications de notification", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Terminez la configuration en personnalisant les éléments suivants en fonction de votre structure de messagerie et de l'installation de Home Assistant. \n\n Pour plus de détails sur les [Intégration de messagerie et de packages](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), passez en revue les [configuration, modèles et section automatisations](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) sur GitHub.", "title": "Courrier et colis (étape 2 sur 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/hu.json b/custom_components/mail_and_packages/translations/hu.json index 982bb685..a789dc68 100644 --- a/custom_components/mail_and_packages/translations/hu.json +++ b/custom_components/mail_and_packages/translations/hu.json @@ -6,7 +6,11 @@ "error": { "communication": "Nem lehet csatlakozni vagy bejelentkezni az e-mail szerverhez. Kérjük, ellenőrizze a naplót a részletekért.", "invalid_path": "Tárolja a képeket egy másik könyvtárban.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Végezze el a konfigurációt az alábbiak testreszabásával az e-mail struktúrája és a Home Assistant telepítése alapján. \n\n A [Levelek és csomagok integrációja] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) opciókkal kapcsolatban tekintse meg a [konfiguráció, sablonok , és az automatizálás szakasz] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) a GitHubon.", "title": "Levél és csomagok (2. lépés a 2-ből)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Nem lehet csatlakozni vagy bejelentkezni az e-mail szerverhez. Kérjük, ellenőrizze a naplót a részletekért.", "invalid_path": "Tárolja a képeket egy másik könyvtárban.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Érvénytelen továbbítási e-mail cím.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Érzékelők listája", "imap_timeout": "Idő másodpercben a csatlakozás időtúllépése előtt (másodperc, minimum 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Kép létrehozása az értesítési alkalmazásokhoz" + "allow_external": "Kép létrehozása az értesítési alkalmazásokhoz", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Végezze el a konfigurációt az alábbiak testreszabásával az e-mail struktúrája és a Home Assistant telepítése alapján. \n\n A [Levelek és csomagok integrációja](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) opciókkal kapcsolatban tekintse meg a [konfiguráció, sablonok , és az automatizálás szakasz](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) a GitHubon.", "title": "Levél és csomagok (2. lépés a 2-ből)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/it.json b/custom_components/mail_and_packages/translations/it.json index cb722c84..84db77df 100644 --- a/custom_components/mail_and_packages/translations/it.json +++ b/custom_components/mail_and_packages/translations/it.json @@ -6,7 +6,11 @@ "error": { "communication": "Impossibile connettersi o accedere al server di posta. Si prega di controllare il registro per i dettagli.", "invalid_path": "Conservare le immagini in un'altra directory.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Termina la configurazione personalizzando quanto segue in base alla struttura della tua e-mail e all'installazione di Home Assistant. \n\n Per i dettagli sulle opzioni [Integrazione posta e pacchetti] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) rivedere le [configurazione, modelli e sezione automazioni] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) su GitHub.", "title": "Posta e pacchi (passaggio 2 di 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Impossibile connettersi o accedere al server di posta. Si prega di controllare il registro per i dettagli.", "invalid_path": "Conservare le immagini in un'altra directory.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Indirizzo e-mail di inoltro non valido.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Elenco dei sensori", "imap_timeout": "Tempo in secondi prima del timeout della connessione (secondi, minimo 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Crea un'immagine per le app di notifica" + "allow_external": "Crea un'immagine per le app di notifica", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Termina la configurazione personalizzando quanto segue in base alla struttura della tua e-mail e all'installazione di Home Assistant. \n\n Per i dettagli sulle opzioni [Integrazione posta e pacchetti](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) rivedere le [configurazione, modelli e sezione automazioni](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) su GitHub.", "title": "Posta e pacchi (passaggio 2 di 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/ko.json b/custom_components/mail_and_packages/translations/ko.json index 6fc7321b..8122a348 100644 --- a/custom_components/mail_and_packages/translations/ko.json +++ b/custom_components/mail_and_packages/translations/ko.json @@ -6,7 +6,11 @@ "error": { "communication": "메일 서버에 연결하거나 로그인 할 수 없습니다. 자세한 내용은 로그를 확인하십시오.", "invalid_path": "이미지를 다른 디렉토리에 저장하십시오.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "이메일 구조 및 Home Assistant 설치에 따라 다음을 사용자 정의하여 구성을 완료하십시오. \n\n [메일 및 패키지 통합] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) 옵션에 대한 자세한 내용은 [구성, 템플릿 및 자동화 섹션] (https://github.com/moralmunky/Home-Assistant-Mail-and-Packages/wiki/Configuration-and-Email-Settings#configuration)", "title": "메일 및 패키지 (2/2 단계)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "메일 서버에 연결하거나 로그인 할 수 없습니다. 자세한 내용은 로그를 확인하십시오.", "invalid_path": "이미지를 다른 디렉토리에 저장하십시오.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "잘못된 전달 이메일 주소입니다.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "센서 목록", "imap_timeout": "연결 시간 초과 전 시간 (초, 최소 10 초)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "알림 앱용 이미지 만들기" + "allow_external": "알림 앱용 이미지 만들기", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "이메일 구조 및 Home Assistant 설치에 따라 다음을 사용자 정의하여 구성을 완료하십시오. \n\n [메일 및 패키지 통합](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) 옵션에 대한 자세한 내용은 [구성, 템플릿 및 자동화 섹션](https://github.com/moralmunky/Home-Assistant-Mail-and-Packages/wiki/Configuration-and-Email-Settings#configuration)", "title": "메일 및 패키지 (2/2 단계)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/nl.json b/custom_components/mail_and_packages/translations/nl.json index 9118dc75..301a8f53 100644 --- a/custom_components/mail_and_packages/translations/nl.json +++ b/custom_components/mail_and_packages/translations/nl.json @@ -6,7 +6,11 @@ "error": { "communication": "Kan geen verbinding maken met of inloggen op de mailserver. Controleer het logboek voor details.", "invalid_path": "Bewaar de afbeeldingen in een andere map.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Voltooi de configuratie door het volgende aan te passen op basis van uw e-mailstructuur en Home Assistant-installatie. \n\n Voor meer informatie over de [E-mail en pakketten integratie] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) opties bekijk de [configuratie, sjablonen , en automatisering sectie] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) op GitHub.", "title": "Mail en pakketten (stap 2 van 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Kan geen verbinding maken met of inloggen op de mailserver. Controleer het logboek voor details.", "invalid_path": "Bewaar de afbeeldingen in een andere map.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Ongeldig e-mailadres voor doorsturen.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Sensoren lijst", "imap_timeout": "Tijd in seconden voordat verbinding wordt verbroken (seconden, minimaal 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Maak een afbeelding voor meldings-apps" + "allow_external": "Maak een afbeelding voor meldings-apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Voltooi de configuratie door het volgende aan te passen op basis van uw e-mailstructuur en Home Assistant-installatie. \n\n Voor meer informatie over de [E-mail en pakketten integratie](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) opties bekijk de [configuratie, sjablonen , en automatisering sectie](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) op GitHub.", "title": "Mail en pakketten (stap 2 van 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/no.json b/custom_components/mail_and_packages/translations/no.json index 9177c21d..61a5e8a7 100644 --- a/custom_components/mail_and_packages/translations/no.json +++ b/custom_components/mail_and_packages/translations/no.json @@ -6,7 +6,11 @@ "error": { "communication": "Kan ikke koble til eller logge inn på postserveren. Vennligst sjekk loggen for detaljer.", "invalid_path": "Lagre bildene i en annen katalog.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Fullfør konfigurasjonen ved å tilpasse følgende basert på e-poststrukturen og Home Assistant-installasjonen. \n\n For detaljer om alternativene [Mail and Packages] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), les gjennom [konfigurasjon, maler , og automatiseringsdel] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) på GitHub.", "title": "E-post og pakker (trinn 2 av 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Kan ikke koble til eller logge inn på postserveren. Vennligst sjekk loggen for detaljer.", "invalid_path": "Lagre bildene i en annen katalog.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Ugyldig videresendings-e-postadresse.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Sensorliste", "imap_timeout": "Tid i sekunder før tidsavbrudd for tilkobling (sekunder, minimum 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Lag bilde for varslingsapper" + "allow_external": "Lag bilde for varslingsapper", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Fullfør konfigurasjonen ved å tilpasse følgende basert på e-poststrukturen og Home Assistant-installasjonen. \n\n For detaljer om alternativene [Mail and Packages](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), les gjennom [konfigurasjon, maler , og automatiseringsdel](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) på GitHub.", "title": "E-post og pakker (trinn 2 av 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/pl.json b/custom_components/mail_and_packages/translations/pl.json index fd62e86d..8f5be91e 100644 --- a/custom_components/mail_and_packages/translations/pl.json +++ b/custom_components/mail_and_packages/translations/pl.json @@ -6,7 +6,11 @@ "error": { "communication": "Nie można połączyć się lub zalogować do serwera pocztowego. Sprawdź szczegóły w dzienniku.", "invalid_path": "Proszę przechowywać obrazy w innym katalogu.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Zakończ konfigurację, dostosowując następujące elementy w oparciu o strukturę poczty e-mail i instalację Home Assistant. \n\n Aby uzyskać szczegółowe informacje na temat opcji [Integracja poczty i pakietów] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) sprawdź opcje [konfiguracja, szablony i sekcja automatyzacji] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) na GitHub.", "title": "Poczta i paczki (krok 2 z 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Nie można połączyć się lub zalogować do serwera pocztowego. Sprawdź szczegóły w dzienniku.", "invalid_path": "Proszę przechowywać obrazy w innym katalogu.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Nieprawidłowy adres e-mail do przekazywania dalej.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Lista czujników", "imap_timeout": "Czas w sekundach do przekroczenia limitu czasu połączenia (sekundy, minimum 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Utwórz obraz dla aplikacji powiadamiających" + "allow_external": "Utwórz obraz dla aplikacji powiadamiających", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Zakończ konfigurację, dostosowując następujące elementy w oparciu o strukturę poczty e-mail i instalację Home Assistant. \n\n Aby uzyskać szczegółowe informacje na temat opcji [Integracja poczty i pakietów](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) sprawdź opcje [konfiguracja, szablony i sekcja automatyzacji](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) na GitHub.", "title": "Poczta i paczki (krok 2 z 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/pt.json b/custom_components/mail_and_packages/translations/pt.json index f0169661..df23e64a 100644 --- a/custom_components/mail_and_packages/translations/pt.json +++ b/custom_components/mail_and_packages/translations/pt.json @@ -6,7 +6,11 @@ "error": { "communication": "Não foi possível conectar ou fazer login no servidor de email. Por favor, verifique o log para obter detalhes.", "invalid_path": "Por favor, guarde as imagens em outro diretório.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Conclua a configuração, personalizando o seguinte com base na sua estrutura de email e instalação do Home Assistant. \n\n Para obter detalhes sobre as opções [integração de Mail e Pacotes] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), revise as opções de [configuração, modelos e seção de automações] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) no GitHub.", "title": "Correio e pacotes (Etapa 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Não foi possível conectar ou fazer login no servidor de email. Por favor, verifique o log para obter detalhes.", "invalid_path": "Por favor, guarde as imagens em outro diretório.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Endereço de email de encaminhamento inválido.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Lista de Sensores", "imap_timeout": "Tempo em segundos antes do tempo limite da conexão (segundos, mínimo 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Criar imagem para aplicativos de notificação" + "allow_external": "Criar imagem para aplicativos de notificação", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Conclua a configuração, personalizando o seguinte com base na sua estrutura de email e instalação do Home Assistant. \n\n Para obter detalhes sobre as opções [integração de Mail e Pacotes](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), revise as opções de [configuração, modelos e seção de automações](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) no GitHub.", "title": "Correio e pacotes (Etapa 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/pt_BR.json b/custom_components/mail_and_packages/translations/pt_BR.json index f2a9a13d..7af4642c 100644 --- a/custom_components/mail_and_packages/translations/pt_BR.json +++ b/custom_components/mail_and_packages/translations/pt_BR.json @@ -6,7 +6,11 @@ "error": { "communication": "Não foi possível conectar ou fazer login no servidor de email. Por favor, verifique o log para obter detalhes.", "invalid_path": "Por favor, guarde as imagens em outro diretório.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Conclua a configuração, personalizando o seguinte com base na sua estrutura de email e instalação do Home Assistant. \n\n Para obter detalhes sobre as opções [integração de Mail e Pacotes] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), revise as opções de [configuração, modelos e seção de automações] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) no GitHub.", "title": "Correio e pacotes (Etapa 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Não foi possível conectar ou fazer login no servidor de email. Por favor, verifique o log para obter detalhes.", "invalid_path": "Por favor, guarde as imagens em outro diretório.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Endereço de email de encaminhamento inválido.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Conclua a configuração, personalizando o seguinte com base na sua estrutura de email e instalação do Home Assistant. \n\n Para obter detalhes sobre as opções [integração de Mail e Pacotes](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration), revise as opções de [configuração, modelos e seção de automações](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) no GitHub.", "title": "Correio e pacotes (Etapa 2 de 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/ru.json b/custom_components/mail_and_packages/translations/ru.json index 41119f38..409543d6 100644 --- a/custom_components/mail_and_packages/translations/ru.json +++ b/custom_components/mail_and_packages/translations/ru.json @@ -6,7 +6,11 @@ "error": { "communication": "Невозможно подключиться или войти на почтовый сервер. Пожалуйста, проверьте журнал для деталей.", "invalid_path": "Пожалуйста, храните изображения в другом каталоге.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Завершите настройку, настроив следующие параметры в зависимости от структуры электронной почты и установки Home Assistant. \n\n Подробнее о параметрах [Интеграция с почтой и пакетами] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) см. В разделе [конфигурация, шаблоны и раздел автоматизации] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) на GitHub.", "title": "Почта и пакеты (шаг 2 из 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Невозможно подключиться или войти на почтовый сервер. Пожалуйста, проверьте журнал для деталей.", "invalid_path": "Пожалуйста, храните изображения в другом каталоге.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Недействительный адрес электронной почты для пересылки.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Список датчиков", "imap_timeout": "Время в секундах до тайм-аута соединения (секунд, минимум 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Создать изображение для приложений уведомлений" + "allow_external": "Создать изображение для приложений уведомлений", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Завершите настройку, настроив следующие параметры в зависимости от структуры электронной почты и установки Home Assistant. \n\n Подробнее о параметрах [Интеграция с почтой и пакетами](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) см. В разделе [конфигурация, шаблоны и раздел автоматизации](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) на GitHub.", "title": "Почта и пакеты (шаг 2 из 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/sl.json b/custom_components/mail_and_packages/translations/sl.json index 1983d153..8efa4f62 100644 --- a/custom_components/mail_and_packages/translations/sl.json +++ b/custom_components/mail_and_packages/translations/sl.json @@ -6,7 +6,11 @@ "error": { "communication": "Poštnega strežnika ni mogoče povezati ali prijaviti. Za podrobnosti preverite dnevnik.", "invalid_path": "Shranite slike v drugem imeniku.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Končajte konfiguracijo s prilagoditvijo naslednjih na podlagi strukture e-pošte in namestitve Home Assistant. \n\n Za podrobnosti o možnostih [Integracija pošte in paketov] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) preglejte [konfiguracijo, predloge in oddelku za avtomatizacije] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) na GitHubu.", "title": "Pošta in paketi (2. korak od 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Poštnega strežnika ni mogoče povezati ali prijaviti. Za podrobnosti preverite dnevnik.", "invalid_path": "Shranite slike v drugem imeniku.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Neveljaven e-poštni naslov za posredovanje.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Seznam senzorjev", "imap_timeout": "Čas v sekundah pred iztekom povezave (sekunde, najmanj 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Ustvari sliko za aplikacije za obveščanje" + "allow_external": "Ustvari sliko za aplikacije za obveščanje", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Končajte konfiguracijo s prilagoditvijo naslednjih na podlagi strukture e-pošte in namestitve Home Assistant. \n\n Za podrobnosti o možnostih [Integracija pošte in paketov](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) preglejte [konfiguracijo, predloge in oddelku za avtomatizacije](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) na GitHubu.", "title": "Pošta in paketi (2. korak od 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/sv.json b/custom_components/mail_and_packages/translations/sv.json index 7897ce2e..ad3edeee 100644 --- a/custom_components/mail_and_packages/translations/sv.json +++ b/custom_components/mail_and_packages/translations/sv.json @@ -6,7 +6,11 @@ "error": { "communication": "Det går inte att ansluta eller logga in på e-postservern. Kontrollera loggen för mer information.", "invalid_path": "Förvara bilderna i en annan katalog.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Avsluta konfigurationen genom att anpassa följande baserat på din e-poststruktur och installation av hemassistent. \n\n Mer information om alternativen [Mail and Packages] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) läser [konfiguration, mallar , och automatiseringsavsnitt] (https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) på GitHub.", "title": "Mail och paket (steg 2 av 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "Det går inte att ansluta eller logga in på e-postservern. Kontrollera loggen för mer information.", "invalid_path": "Förvara bilderna i en annan katalog.", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Ogiltig vidarebefordran av e-postadress.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "Sensorlista", "imap_timeout": "Tid i sekunder före timeout för anslutning (sekunder, minimum 10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "Skapa bild för anmälningsappar" + "allow_external": "Skapa bild för anmälningsappar", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "Avsluta konfigurationen genom att anpassa följande baserat på din e-poststruktur och installation av hemassistent. \n\n Mer information om alternativen [Mail and Packages](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) läser [konfiguration, mallar , och automatiseringsavsnitt](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) på GitHub.", "title": "Mail och paket (steg 2 av 2)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file diff --git a/custom_components/mail_and_packages/translations/zh_Hant_HK.json b/custom_components/mail_and_packages/translations/zh_Hant_HK.json index 5c75659f..f180c569 100644 --- a/custom_components/mail_and_packages/translations/zh_Hant_HK.json +++ b/custom_components/mail_and_packages/translations/zh_Hant_HK.json @@ -6,7 +6,11 @@ "error": { "communication": "無法連接或登錄到郵件服務器。請檢查日誌以獲取詳細信息。", "invalid_path": "請將圖像存儲在另一個目錄中。", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "Invalid forwarding email address.", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "user": { @@ -30,10 +34,19 @@ "resources": "Sensors List", "imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)", "amazon_fwds": "Amazon fowarded email addresses", - "allow_external": "Create image for notification apps" + "allow_external": "Create image for notification apps", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "通過根據您的電子郵件結構和Home Assistant安裝自定義以下內容來完成配置。 \n\n有關[郵件和軟件包集成](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration)選項的詳細信息,請查看[配置,模板和自動化部分](GitHub上的https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration)。", "title": "郵件和包裹(第2步,共2步)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } }, "title": "Mail and Packages" @@ -42,7 +55,11 @@ "error": { "communication": "無法連接或登錄到郵件服務器。請檢查日誌以獲取詳細信息。", "invalid_path": "請將圖像存儲在另一個目錄中。", - "ffmpeg_not_found": "Generate MP4 requires ffmpeg" + "ffmpeg_not_found": "Generate MP4 requires ffmpeg", + "amazon_domain": "無效的轉發電子郵件地址。", + "file_not_found": "Image file not found", + "scan_too_low": "Scan interval too low (minimum 5)", + "timeout_too_low": "IMAP timeout too low (minimum 10)" }, "step": { "init": { @@ -66,11 +83,21 @@ "resources": "傳感器清單", "imap_timeout": "連接超時之前的時間(以秒為單位)(秒,至少10)", "amazon_fwds": "Amazon forwarded email addresses", - "allow_external": "為通知應用創建圖像" + "allow_external": "為通知應用創建圖像", + "amazon_days": "Days back to check for Amazon emails", + "custom_img": "Use custom 'no image' image?" }, "description": "通過根據您的電子郵件結構和Home Assistant安裝自定義以下內容來完成配置。 \n\n有關[郵件和軟件包集成](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration)選項的詳細信息,請查看[配置,模板和自動化部分](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) GitHub上的。", "title": "郵件和包裹(第2步,共2步)" + }, + "options_3": { + "data": { + "custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)" + }, + "description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif", + "title": "Mail and Packages (Step 3 of 3)" } } - } + }, + "title": "Mail and Packages" } \ No newline at end of file From b5a89d7aefa8660fb941ff53acf2236cf47af38e Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 10 May 2022 17:18:45 -0700 Subject: [PATCH 88/88] fix: remove quotes from SINCE date fixes #661 --- custom_components/mail_and_packages/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index e8b126ef..3357beda 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -482,7 +482,7 @@ def build_search(address: list, date: str, subject: str = None) -> tuple: Return tuple of utf8 flag and search query. """ - the_date = f'SINCE "{date}"' + the_date = f"SINCE {date}" imap_search = None utf8_flag = False prefix_list = None