Skip to content

Commit

Permalink
feat: Add support for updating user Teams status
Browse files Browse the repository at this point in the history
  • Loading branch information
RogerSelwyn committed Feb 12, 2024
1 parent caa1402 commit 293bc52
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 11 deletions.
6 changes: 5 additions & 1 deletion custom_components/o365/classes/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
PERM_MINIMUM_USER,
PERM_OFFLINE_ACCESS,
PERM_PRESENCE_READ,
PERM_PRESENCE_READWRITE,
PERM_SHARED,
PERM_TASKS_READ,
PERM_TASKS_READWRITE,
Expand Down Expand Up @@ -275,7 +276,10 @@ def _build_autoreply_permissions(self):
def _build_status_permissions(self):
status_sensors = self._config.get(CONF_STATUS_SENSORS, [])
if len(status_sensors) > 0:
self._requested_permissions.append(PERM_PRESENCE_READ)
if status_sensors[0][CONF_ENABLE_UPDATE]:
self._requested_permissions.append(PERM_PRESENCE_READWRITE)
else:
self._requested_permissions.append(PERM_PRESENCE_READ)

def _build_chat_permissions(self):
chat_sensors = self._config.get(CONF_CHAT_SENSORS, [])
Expand Down
36 changes: 36 additions & 0 deletions custom_components/o365/classes/teamssensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,30 @@
import logging

from homeassistant.components.sensor import SensorEntity
from homeassistant.const import ATTR_NAME

from ..const import (
ATTR_ACTIVITY,
ATTR_AVAILABILITY,
ATTR_CHAT_ID,
ATTR_CONTENT,
ATTR_DATA,
ATTR_FROM_DISPLAY_NAME,
ATTR_IMPORTANCE,
ATTR_STATE,
ATTR_STATUS,
ATTR_SUBJECT,
ATTR_SUMMARY,
CONF_ACCOUNT,
CONF_CLIENT_ID,
DOMAIN,
EVENT_HA_EVENT,
EVENT_SEND_CHAT_MESSAGE,
EVENT_UPDATE_USER_STATUS,
PERM_CHAT_READWRITE,
PERM_MINIMUM_CHAT_WRITE,
PERM_MINIMUM_PRESENCE_WRITE,
PERM_PRESENCE_READWRITE,
SENSOR_TEAMS_CHAT,
SENSOR_TEAMS_STATUS,
)
Expand All @@ -34,6 +42,7 @@ def __init__(self, cordinator, name, entity_id, config, entity_type, unique_id):
"""Initialise the Teams Sensor."""
super().__init__(cordinator, config, name, entity_id, entity_type, unique_id)
self.teams = self._config[CONF_ACCOUNT].teams()
self._application_id = self._config[CONF_CLIENT_ID]

@property
def icon(self):
Expand All @@ -60,6 +69,33 @@ def __init__(self, coordinator, name, entity_id, config, unique_id):
unique_id,
)

def update_user_status(self, availability, activity, expiration_duration=None):
"""Update the users teams status."""
if not self._validate_status_permissions():
return False

status = self.teams.set_my_presence(
self._application_id, availability, activity, expiration_duration
)
self._raise_event(
EVENT_UPDATE_USER_STATUS,
{ATTR_AVAILABILITY: status.availability, ATTR_ACTIVITY: status.activity},
)
return False

def _raise_event(self, event_type, status):
self.hass.bus.fire(
f"{DOMAIN}_{event_type}",
{ATTR_NAME: self._name, ATTR_STATUS: status, EVENT_HA_EVENT: True},
)
_LOGGER.debug("%s - %s - %s", self._name, event_type, status)

def _validate_status_permissions(self):
return self._validate_permissions(
PERM_MINIMUM_PRESENCE_WRITE,
f"Not authorised to update status - requires permission: {PERM_PRESENCE_READWRITE}",
)


class O365TeamsChatSensor(O365TeamsSensor, SensorEntity):
"""O365 Teams Chat sensor processing."""
Expand Down
9 changes: 8 additions & 1 deletion custom_components/o365/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ class EventResponse(Enum):
Decline = "decline" # pylint: disable=invalid-name


ATTR_ACTIVITY = "activity"
ATTR_ALL_DAY = "all_day"
ATTR_ALL_TODOS = "all_todos"
ATTR_ATTACHMENTS = "attachments"
ATTR_ATTENDEES = "attendees"
ATTR_AUTOREPLIESSETTINGS = "autorepliessettings"
ATTR_AVAILABILITY = "availability"
ATTR_BODY = "body"
ATTR_CATEGORIES = "categories"
ATTR_CHAT_ID = "chat_id"
Expand All @@ -30,6 +32,7 @@ class EventResponse(Enum):
ATTR_END = "end"
ATTR_ERROR = "error"
ATTR_EVENT_ID = "event_id"
ATTR_EXPIRATIONDURATION = "expiration_duration"
ATTR_EXTERNAL_AUDIENCE = "external_audience"
ATTR_EXTERNALREPLY = "external_reply"
ATTR_FROM_DISPLAY_NAME = "from_display_name"
Expand All @@ -52,6 +55,7 @@ class EventResponse(Enum):
ATTR_SHOW_AS = "show_as"
ATTR_START = "start"
ATTR_STATE = "state"
ATTR_STATUS = "status"
ATTR_SUBJECT = "subject"
ATTR_SUMMARY = "summary"
ATTR_TODOS = "todos"
Expand Down Expand Up @@ -151,6 +155,7 @@ class EventResponse(Enum):
EVENT_REMOVE_CALENDAR_RECURRENCES = "remove_calendar_recurrences"
EVENT_RESPOND_CALENDAR_EVENT = "respond_calendar_event"
EVENT_SEND_CHAT_MESSAGE = "send_chat_message"
EVENT_UPDATE_USER_STATUS = "update_user_status"

LEGACY_ACCOUNT_NAME = "converted"
O365_STORAGE = "o365_storage"
Expand All @@ -173,13 +178,15 @@ class EventResponse(Enum):
PERM_MAIL_SEND_SHARED = "Mail.Send.Shared"
PERM_OFFLINE_ACCESS = "offline_access"
PERM_PRESENCE_READ = "Presence.Read"
PERM_PRESENCE_READWRITE = "Presence.ReadWrite"
PERM_TASKS_READ = "Tasks.Read"
PERM_TASKS_READWRITE = "Tasks.ReadWrite"
PERM_USER_READ = "User.Read"
PERM_MINIMUM_CHAT = [PERM_CHAT_READ, [PERM_CHAT_READWRITE]]
PERM_MINIMUM_CHAT_WRITE = [PERM_CHAT_READWRITE, []]
PERM_MINIMUM_GROUP = [PERM_GROUP_READ_ALL, [PERM_GROUP_READWRITE_ALL]]
PERM_MINIMUM_PRESENCE = [PERM_PRESENCE_READ, []]
PERM_MINIMUM_PRESENCE = [PERM_PRESENCE_READ, [PERM_PRESENCE_READWRITE]]
PERM_MINIMUM_PRESENCE_WRITE = [PERM_PRESENCE_READWRITE, []]
PERM_MINIMUM_TASKS = [PERM_TASKS_READ, [PERM_TASKS_READWRITE]]
PERM_MINIMUM_TASKS_WRITE = [PERM_TASKS_READWRITE, []]
PERM_MINIMUM_USER = [PERM_USER_READ, []]
Expand Down
2 changes: 1 addition & 1 deletion custom_components/o365/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/RogerSelwyn/O365-HomeAssistant/issues",
"requirements": [
"O365==2.0.28",
"O365==2.0.33",
"BeautifulSoup4>=4.10.0"
],
"version": "v4.6.2"
Expand Down
25 changes: 20 additions & 5 deletions custom_components/o365/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,22 @@
ATTR_TITLE,
)
from homeassistant.const import CONF_ENABLED, CONF_NAME
from O365.calendar import AttendeeType # pylint: disable=no-name-in-module
from O365.calendar import EventSensitivity # pylint: disable=no-name-in-module
from O365.calendar import EventShowAs # pylint: disable=no-name-in-module; pylint: disable=no-name-in-module
from O365.mailbox import ExternalAudience # pylint: disable=no-name-in-module, import-error; pylint: disable=no-name-in-module, import-error
from O365.calendar import ( # pylint: disable=no-name-in-module
AttendeeType,
EventSensitivity,
EventShowAs,
)
from O365.mailbox import ( # pylint: disable=no-name-in-module, import-error
ExternalAudience,
)
from O365.teams import Activity, Availability
from O365.utils import ImportanceLevel # pylint: disable=no-name-in-module

from .const import ( # CONF_DUE_HOURS_BACKWARD_TO_GET,; CONF_DUE_HOURS_FORWARD_TO_GET,
from .const import (
ATTR_ACTIVITY,
ATTR_ATTACHMENTS,
ATTR_ATTENDEES,
ATTR_AVAILABILITY,
ATTR_BODY,
ATTR_CATEGORIES,
ATTR_CHAT_ID,
Expand All @@ -27,6 +34,7 @@
ATTR_EMAIL,
ATTR_END,
ATTR_EVENT_ID,
ATTR_EXPIRATIONDURATION,
ATTR_EXTERNAL_AUDIENCE,
ATTR_EXTERNALREPLY,
ATTR_IMPORTANCE,
Expand Down Expand Up @@ -107,6 +115,7 @@
STATUS_SENSOR = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_ENABLE_UPDATE, default=False): bool,
}
)
CHAT_SENSOR = vol.Schema(
Expand Down Expand Up @@ -240,6 +249,12 @@
}


STATUS_SERVICE_UPDATE_USER_STATUS_SCHEMA = {
vol.Required(ATTR_AVAILABILITY): vol.Coerce(Availability),
vol.Required(ATTR_ACTIVITY): vol.Coerce(Activity),
vol.Optional(ATTR_EXPIRATIONDURATION): cv.string,
}

TODO_SERVICE_NEW_SCHEMA = {
vol.Required(ATTR_SUBJECT): cv.string,
vol.Optional(ATTR_DESCRIPTION): cv.string,
Expand Down
21 changes: 21 additions & 0 deletions custom_components/o365/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
CONF_KEYS_SENSORS,
CONF_PERMISSIONS,
CONF_SENSOR_CONF,
CONF_STATUS_SENSORS,
DOMAIN,
PERM_MINIMUM_CHAT_WRITE,
PERM_MINIMUM_MAILBOX_SETTINGS,
PERM_MINIMUM_PRESENCE_WRITE,
SENSOR_AUTO_REPLY,
SENSOR_TEAMS_CHAT,
SENSOR_TEAMS_STATUS,
Expand All @@ -32,6 +34,7 @@
AUTO_REPLY_SERVICE_DISABLE_SCHEMA,
AUTO_REPLY_SERVICE_ENABLE_SCHEMA,
CHAT_SERVICE_SEND_MESSAGE_SCHEMA,
STATUS_SERVICE_UPDATE_USER_STATUS_SCHEMA,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -114,10 +117,28 @@ def _email_entities(conf):

async def _async_setup_register_services(config):
perms = config[CONF_PERMISSIONS]
await _async_setup_status_services(config, perms)
await _async_setup_chat_services(config, perms)
await _async_setup_mailbox_services(config, perms)


async def _async_setup_status_services(config, perms):
status_sensors = config.get(CONF_STATUS_SENSORS)
if not status_sensors:
return
status_sensor = status_sensors[0]
if not status_sensor or not status_sensor.get(CONF_ENABLE_UPDATE):
return

platform = entity_platform.async_get_current_platform()
if perms.validate_minimum_permission(PERM_MINIMUM_PRESENCE_WRITE):
platform.async_register_entity_service(
"update_user_status",
STATUS_SERVICE_UPDATE_USER_STATUS_SCHEMA,
"update_user_status",
)


async def _async_setup_chat_services(config, perms):
chat_sensors = config.get(CONF_CHAT_SENSORS)
if not chat_sensors:
Expand Down
55 changes: 54 additions & 1 deletion custom_components/o365/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -464,4 +464,57 @@ send_chat_message:
example: Hello team
required: true
selector:
text:
text:

update_user_status:
name: Update user Teams status
target:
device:
integration: o365
entity:
integration: o365
domain: sensor
fields:
availability:
name: Availability
description: "The base presence information"
example: Busy
required: true
selector:
select:
mode: dropdown
options:
- label: "Available"
value: "Available"
- label: "Busy"
value: "Busy"
- label: "Away"
value: "Away"
- label: "Do Not Disturb"
value: "DoNotDisturb"
activity:
name: Activity
description: "The supplemental information to availability"
example: InACall
required: true
selector:
select:
mode: dropdown
options:
- label: "Available"
value: "Available"
- label: "In a Call"
value: "InACall"
- label: "In a Conference Call"
value: "InAConferenceCall"
- label: "Away"
value: "Away"
- label: "Presenting"
value: "Presenting"
expiration_duration:
name: Expiration Duration
description: "The expiration of the app presence session. The value is represented in ISO 8601 format for durations"
example: PT1H
required: false
selector:
text:
2 changes: 2 additions & 0 deletions custom_components/o365/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
CONF_ACCOUNT_NAME,
CONF_AUTO_REPLY_SENSORS,
CONF_CHAT_SENSORS,
CONF_CLIENT_ID,
CONF_CONFIG_TYPE,
CONF_COORDINATOR_EMAIL,
CONF_COORDINATOR_SENSORS,
Expand Down Expand Up @@ -36,6 +37,7 @@ async def do_setup(hass, config, account, account_name, conf_type, perms):
enable_update = config.get(CONF_ENABLE_UPDATE, True)

account_config = {
CONF_CLIENT_ID: config.get(CONF_CLIENT_ID),
CONF_ACCOUNT: account,
CONF_EMAIL_SENSORS: email_sensors,
CONF_QUERY_SENSORS: query_sensors,
Expand Down
26 changes: 25 additions & 1 deletion docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,31 @@ context:
parent_id: null
user_id: null
```
## Chat Events
## Teams Status Events

Events will be raised for the following items.

- o365_update_user_status - User teams presence updated

The events have the following general structure:

```yaml
event_type: o365_update_user_status
data:
name: Joe Teams Status
status:
availability: Available
activity: Available
ha_event: true
origin: LOCAL
time_fired: "2024-02-12T18:22:36.694771+00:00"
context:
id: 01HPF8X14PYZ1QRZ8V199JSQTQ
parent_id: null
user_id: null
```

## Teams Chat Events

Events will be raised for the following items.

Expand Down
1 change: 1 addition & 0 deletions docs/installation_and_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Key | Type | Required | Description
Key | Type | Required | Description
-- | -- | -- | --
`name` | `string` | `True` | The name of the sensor.
`enable_update` | `boolean` | `False` | If True (**default is False**), this will enable the services update user status

#### chat_sensors (not for personal accounts)

Expand Down
3 changes: 2 additions & 1 deletion docs/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Under "API Permissions" click Add a permission, then Microsoft Graph, then Deleg
* AutoReply - For Auto reply/Out of Office message configuration


If you intend to send emails use calendar update functionality, then set [enable_update](./installation_and_configuration.md#configuration_variables) at the top level to `true`. For To-do List set [enable_update](installation_and_configuration.md#todo_lists) to true. Then for any sensor type, add the relevant `ReadWrite` permission as denoted by a `Y` in the update column.
If you intend to send emails use calendar update functionality, then set [enable_update](./installation_and_configuration.md#configuration_variables) at the top level to `true`. For other sensors set [enable_update](installation_and_configuration.md) to true for each sensor supporting it. Then for any sensor type, add the relevant `ReadWrite` permission as denoted by a `Y` in the update column.


| Feature | Permissions | Update | O365 Description | Notes |
Expand All @@ -32,6 +32,7 @@ Under "API Permissions" click Add a permission, then Microsoft Graph, then Deleg
| Email | Mail.Read.Shared | | *Read user and shared mail* | For shared mailboxes |
| Email | Mail.Send.Shared | Y | *Send mail on behalf of others* | For shared mailboxes |
| Status | Presence.Read | | *Read user's presence information* | Not for personal accounts/shared mailboxes |
| Status | Presence.ReadWrite | Y | *Read and write a user's presence information* | Not for personal accounts/shared mailboxes |
| Chat | Chat.Read | | *Read user chat messages* | Not for personal accounts/shared mailboxes |
| Chat | Chat.ReadWrite | Y | *Read and write user chat messages* | Not for personal accounts/shared mailboxes |
| ToDo | Tasks.Read | | *Read user's tasks and task lists* | Not for shared mailboxes |
Expand Down

0 comments on commit 293bc52

Please sign in to comment.