core/homeassistant/components/imap/sensor.py

184 lines
5.8 KiB
Python
Raw Normal View History

"""IMAP sensor support."""
import asyncio
import logging
from aioimaplib import IMAP4_SSL, AioImapException
import async_timeout
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
2019-07-31 19:25:30 +00:00
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
2019-07-31 19:25:30 +00:00
CONF_SERVER = "server"
CONF_FOLDER = "folder"
CONF_SEARCH = "search"
CONF_CHARSET = "charset"
DEFAULT_PORT = 993
2019-07-31 19:25:30 +00:00
ICON = "mdi:email-outline"
2019-07-31 19:25:30 +00:00
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_SERVER): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_CHARSET, default="utf-8"): cv.string,
2019-07-31 19:25:30 +00:00
vol.Optional(CONF_FOLDER, default="INBOX"): cv.string,
vol.Optional(CONF_SEARCH, default="UnSeen UnDeleted"): cv.string,
}
)
2019-07-31 19:25:30 +00:00
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the IMAP platform."""
2019-07-31 19:25:30 +00:00
sensor = ImapSensor(
config.get(CONF_NAME),
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
config.get(CONF_SERVER),
config.get(CONF_PORT),
config.get(CONF_CHARSET),
2019-07-31 19:25:30 +00:00
config.get(CONF_FOLDER),
config.get(CONF_SEARCH),
)
if not await sensor.connection():
raise PlatformNotReady
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, sensor.shutdown)
async_add_entities([sensor], True)
class ImapSensor(Entity):
"""Representation of an IMAP sensor."""
def __init__(self, name, user, password, server, port, charset, folder, search):
"""Initialize the sensor."""
self._name = name or user
self._user = user
self._password = password
self._server = server
self._port = port
self._charset = charset
self._folder = folder
Added Search Configuration to IMAP Sensor (#19749) * Added Search Configuration to IMAP Sensor The IMAP sensor currently only counts unread emails in a folder. By exposing the IMAP search parameter, the sensor can be used to count other results: - All emails in an inbox - Emails sent from an address - Emails matching a subject - Other advanced searches, especially with vendor-specific extensions. Gmail in particular supports X-GM-RAW, which lets you use any Gmail search directly ("emails with X label older than 14 days with", etc) For my use case, I just wanted total emails in a folder, to show an "X/Y" counter for total/unread. I started work on a one-off script to throw the data in, but figured I'd try to extend Home Assistant more directly, especially since this IMAP sensor correctly handles servers that push data. This is my first Home Assistant contribution, so apologies in advance if something is out of place! It's a pretty minimal modification. * Added Server Response Checking Looks like no library exception is thrown, so check for response text before parsing out results (previous code just counts spaces, so an error actually returns a state value of 4). * IMAP Warning -> Error, Count Initializes to None IMAP search response parsing throws an error instead of a warning. Email count initializes as None instead 0. Email count is untouched in case of failure to parse response (i.e. if server is temporarily down or throwing errors, or maybe due to user updating their authentication/login/etc). Fixed line length on error so it fits under 80 characters. * Fixed Indent on Logger Error Sorry about the churn! Python is pretty far from my daily-use language. (I did run this one through pep8, at least)
2019-01-19 20:37:02 +00:00
self._email_count = None
self._search = search
self._connection = None
self._does_push = None
self._idle_loop_task = None
async def async_added_to_hass(self):
"""Handle when an entity is about to be added to Home Assistant."""
if not self.should_poll:
self._idle_loop_task = self.hass.loop.create_task(self.idle_loop())
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return the icon to use in the frontend."""
return ICON
@property
def state(self):
Added Search Configuration to IMAP Sensor (#19749) * Added Search Configuration to IMAP Sensor The IMAP sensor currently only counts unread emails in a folder. By exposing the IMAP search parameter, the sensor can be used to count other results: - All emails in an inbox - Emails sent from an address - Emails matching a subject - Other advanced searches, especially with vendor-specific extensions. Gmail in particular supports X-GM-RAW, which lets you use any Gmail search directly ("emails with X label older than 14 days with", etc) For my use case, I just wanted total emails in a folder, to show an "X/Y" counter for total/unread. I started work on a one-off script to throw the data in, but figured I'd try to extend Home Assistant more directly, especially since this IMAP sensor correctly handles servers that push data. This is my first Home Assistant contribution, so apologies in advance if something is out of place! It's a pretty minimal modification. * Added Server Response Checking Looks like no library exception is thrown, so check for response text before parsing out results (previous code just counts spaces, so an error actually returns a state value of 4). * IMAP Warning -> Error, Count Initializes to None IMAP search response parsing throws an error instead of a warning. Email count initializes as None instead 0. Email count is untouched in case of failure to parse response (i.e. if server is temporarily down or throwing errors, or maybe due to user updating their authentication/login/etc). Fixed line length on error so it fits under 80 characters. * Fixed Indent on Logger Error Sorry about the churn! Python is pretty far from my daily-use language. (I did run this one through pep8, at least)
2019-01-19 20:37:02 +00:00
"""Return the number of emails found."""
return self._email_count
@property
def available(self):
"""Return the availability of the device."""
return self._connection is not None
@property
def should_poll(self):
"""Return if polling is needed."""
return not self._does_push
async def connection(self):
"""Return a connection to the server, establishing it if necessary."""
if self._connection is None:
try:
self._connection = IMAP4_SSL(self._server, self._port)
await self._connection.wait_hello_from_server()
await self._connection.login(self._user, self._password)
await self._connection.select(self._folder)
2019-07-31 19:25:30 +00:00
self._does_push = self._connection.has_capability("IDLE")
except (AioImapException, asyncio.TimeoutError):
self._connection = None
return self._connection
async def idle_loop(self):
"""Wait for data pushed from server."""
while True:
try:
if await self.connection():
Added Search Configuration to IMAP Sensor (#19749) * Added Search Configuration to IMAP Sensor The IMAP sensor currently only counts unread emails in a folder. By exposing the IMAP search parameter, the sensor can be used to count other results: - All emails in an inbox - Emails sent from an address - Emails matching a subject - Other advanced searches, especially with vendor-specific extensions. Gmail in particular supports X-GM-RAW, which lets you use any Gmail search directly ("emails with X label older than 14 days with", etc) For my use case, I just wanted total emails in a folder, to show an "X/Y" counter for total/unread. I started work on a one-off script to throw the data in, but figured I'd try to extend Home Assistant more directly, especially since this IMAP sensor correctly handles servers that push data. This is my first Home Assistant contribution, so apologies in advance if something is out of place! It's a pretty minimal modification. * Added Server Response Checking Looks like no library exception is thrown, so check for response text before parsing out results (previous code just counts spaces, so an error actually returns a state value of 4). * IMAP Warning -> Error, Count Initializes to None IMAP search response parsing throws an error instead of a warning. Email count initializes as None instead 0. Email count is untouched in case of failure to parse response (i.e. if server is temporarily down or throwing errors, or maybe due to user updating their authentication/login/etc). Fixed line length on error so it fits under 80 characters. * Fixed Indent on Logger Error Sorry about the churn! Python is pretty far from my daily-use language. (I did run this one through pep8, at least)
2019-01-19 20:37:02 +00:00
await self.refresh_email_count()
self.async_write_ha_state()
idle = await self._connection.idle_start()
await self._connection.wait_server_push()
self._connection.idle_done()
with async_timeout.timeout(10):
await idle
else:
self.async_write_ha_state()
except (AioImapException, asyncio.TimeoutError):
self.disconnected()
async def async_update(self):
"""Periodic polling of state."""
try:
if await self.connection():
Added Search Configuration to IMAP Sensor (#19749) * Added Search Configuration to IMAP Sensor The IMAP sensor currently only counts unread emails in a folder. By exposing the IMAP search parameter, the sensor can be used to count other results: - All emails in an inbox - Emails sent from an address - Emails matching a subject - Other advanced searches, especially with vendor-specific extensions. Gmail in particular supports X-GM-RAW, which lets you use any Gmail search directly ("emails with X label older than 14 days with", etc) For my use case, I just wanted total emails in a folder, to show an "X/Y" counter for total/unread. I started work on a one-off script to throw the data in, but figured I'd try to extend Home Assistant more directly, especially since this IMAP sensor correctly handles servers that push data. This is my first Home Assistant contribution, so apologies in advance if something is out of place! It's a pretty minimal modification. * Added Server Response Checking Looks like no library exception is thrown, so check for response text before parsing out results (previous code just counts spaces, so an error actually returns a state value of 4). * IMAP Warning -> Error, Count Initializes to None IMAP search response parsing throws an error instead of a warning. Email count initializes as None instead 0. Email count is untouched in case of failure to parse response (i.e. if server is temporarily down or throwing errors, or maybe due to user updating their authentication/login/etc). Fixed line length on error so it fits under 80 characters. * Fixed Indent on Logger Error Sorry about the churn! Python is pretty far from my daily-use language. (I did run this one through pep8, at least)
2019-01-19 20:37:02 +00:00
await self.refresh_email_count()
except (AioImapException, asyncio.TimeoutError):
self.disconnected()
Added Search Configuration to IMAP Sensor (#19749) * Added Search Configuration to IMAP Sensor The IMAP sensor currently only counts unread emails in a folder. By exposing the IMAP search parameter, the sensor can be used to count other results: - All emails in an inbox - Emails sent from an address - Emails matching a subject - Other advanced searches, especially with vendor-specific extensions. Gmail in particular supports X-GM-RAW, which lets you use any Gmail search directly ("emails with X label older than 14 days with", etc) For my use case, I just wanted total emails in a folder, to show an "X/Y" counter for total/unread. I started work on a one-off script to throw the data in, but figured I'd try to extend Home Assistant more directly, especially since this IMAP sensor correctly handles servers that push data. This is my first Home Assistant contribution, so apologies in advance if something is out of place! It's a pretty minimal modification. * Added Server Response Checking Looks like no library exception is thrown, so check for response text before parsing out results (previous code just counts spaces, so an error actually returns a state value of 4). * IMAP Warning -> Error, Count Initializes to None IMAP search response parsing throws an error instead of a warning. Email count initializes as None instead 0. Email count is untouched in case of failure to parse response (i.e. if server is temporarily down or throwing errors, or maybe due to user updating their authentication/login/etc). Fixed line length on error so it fits under 80 characters. * Fixed Indent on Logger Error Sorry about the churn! Python is pretty far from my daily-use language. (I did run this one through pep8, at least)
2019-01-19 20:37:02 +00:00
async def refresh_email_count(self):
"""Check the number of found emails."""
if self._connection:
await self._connection.noop()
result, lines = await self._connection.search(
self._search, charset=self._charset
)
Added Search Configuration to IMAP Sensor (#19749) * Added Search Configuration to IMAP Sensor The IMAP sensor currently only counts unread emails in a folder. By exposing the IMAP search parameter, the sensor can be used to count other results: - All emails in an inbox - Emails sent from an address - Emails matching a subject - Other advanced searches, especially with vendor-specific extensions. Gmail in particular supports X-GM-RAW, which lets you use any Gmail search directly ("emails with X label older than 14 days with", etc) For my use case, I just wanted total emails in a folder, to show an "X/Y" counter for total/unread. I started work on a one-off script to throw the data in, but figured I'd try to extend Home Assistant more directly, especially since this IMAP sensor correctly handles servers that push data. This is my first Home Assistant contribution, so apologies in advance if something is out of place! It's a pretty minimal modification. * Added Server Response Checking Looks like no library exception is thrown, so check for response text before parsing out results (previous code just counts spaces, so an error actually returns a state value of 4). * IMAP Warning -> Error, Count Initializes to None IMAP search response parsing throws an error instead of a warning. Email count initializes as None instead 0. Email count is untouched in case of failure to parse response (i.e. if server is temporarily down or throwing errors, or maybe due to user updating their authentication/login/etc). Fixed line length on error so it fits under 80 characters. * Fixed Indent on Logger Error Sorry about the churn! Python is pretty far from my daily-use language. (I did run this one through pep8, at least)
2019-01-19 20:37:02 +00:00
2019-07-31 19:25:30 +00:00
if result == "OK":
Added Search Configuration to IMAP Sensor (#19749) * Added Search Configuration to IMAP Sensor The IMAP sensor currently only counts unread emails in a folder. By exposing the IMAP search parameter, the sensor can be used to count other results: - All emails in an inbox - Emails sent from an address - Emails matching a subject - Other advanced searches, especially with vendor-specific extensions. Gmail in particular supports X-GM-RAW, which lets you use any Gmail search directly ("emails with X label older than 14 days with", etc) For my use case, I just wanted total emails in a folder, to show an "X/Y" counter for total/unread. I started work on a one-off script to throw the data in, but figured I'd try to extend Home Assistant more directly, especially since this IMAP sensor correctly handles servers that push data. This is my first Home Assistant contribution, so apologies in advance if something is out of place! It's a pretty minimal modification. * Added Server Response Checking Looks like no library exception is thrown, so check for response text before parsing out results (previous code just counts spaces, so an error actually returns a state value of 4). * IMAP Warning -> Error, Count Initializes to None IMAP search response parsing throws an error instead of a warning. Email count initializes as None instead 0. Email count is untouched in case of failure to parse response (i.e. if server is temporarily down or throwing errors, or maybe due to user updating their authentication/login/etc). Fixed line length on error so it fits under 80 characters. * Fixed Indent on Logger Error Sorry about the churn! Python is pretty far from my daily-use language. (I did run this one through pep8, at least)
2019-01-19 20:37:02 +00:00
self._email_count = len(lines[0].split())
else:
2019-07-31 19:25:30 +00:00
_LOGGER.error(
"Can't parse IMAP server response to search '%s': %s / %s",
2019-07-31 19:25:30 +00:00
self._search,
result,
lines[0],
)
def disconnected(self):
"""Forget the connection after it was lost."""
_LOGGER.warning("Lost %s (will attempt to reconnect)", self._server)
self._connection = None
async def shutdown(self, *_):
"""Close resources."""
if self._connection:
if self._connection.has_pending_idle():
self._connection.idle_done()
await self._connection.logout()
if self._idle_loop_task:
self._idle_loop_task.cancel()