Repository: RogerSelwyn/O365-HomeAssistant
Branch: master
Commit: e1a84431db5a
Files: 57
Total size: 336.1 KB
Directory structure:
gitextract_pg2eyhpl/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── codeql.yml
│ ├── hacs.yaml
│ ├── hassfest.yaml
│ ├── lint.yaml
│ ├── o365release.yaml
│ └── stale.yaml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── custom_components/
│ └── o365/
│ ├── __init__.py
│ ├── calendar.py
│ ├── classes/
│ │ ├── __init__.py
│ │ ├── entity.py
│ │ ├── mailsensor.py
│ │ ├── permissions.py
│ │ └── teamssensor.py
│ ├── const.py
│ ├── helpers/
│ │ ├── __init__.py
│ │ ├── coordinator.py
│ │ ├── migration.py
│ │ └── setup.py
│ ├── icons.json
│ ├── manifest.json
│ ├── notify.py
│ ├── repairs.py
│ ├── schema.py
│ ├── sensor.py
│ ├── services.yaml
│ ├── strings.json
│ ├── todo.py
│ ├── translations/
│ │ ├── en.json
│ │ └── sk.json
│ └── utils/
│ ├── __init__.py
│ ├── calendar_utils.py
│ ├── filemgmt.py
│ └── utils.py
├── docs/
│ ├── _config.yml
│ ├── authentication.md
│ ├── calendar_configuration.md
│ ├── calendar_panel.md
│ ├── errors.md
│ ├── events.md
│ ├── index.md
│ ├── installation_and_configuration.md
│ ├── migration.md
│ ├── permissions.md
│ ├── prerequisites.md
│ ├── sensor.md
│ ├── services.md
│ ├── todo.md
│ ├── todos_configuration.md
│ └── token.md
├── hacs.json
├── requirements.txt
└── requirements_release.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
custom: ["https://www.buymeacoffee.com/rogtp", "https://www.paypal.com/donate/?hosted_button_id=F7TGHNGH7A526"]
================================================
FILE: .github/workflows/codeql.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '37 0 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v5
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
================================================
FILE: .github/workflows/hacs.yaml
================================================
name: HACS Validate
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
hacs:
name: HACS Action
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v5"
- name: HACS validation
uses: "hacs/action@main"
with:
category: "integration"
================================================
FILE: .github/workflows/hassfest.yaml
================================================
name: Validate with hassfest
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
hassfest:
name: hassfest Action
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v5"
- uses: home-assistant/actions/hassfest@master
================================================
FILE: .github/workflows/lint.yaml
================================================
name: "Lint"
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
lint:
name: Lint
runs-on: "ubuntu-latest"
steps:
- name: "Checkout the repository"
uses: actions/checkout@v5
- name: "Set up Python"
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: "pip"
- name: "Install requirements"
run: python3 -m pip install -r requirements_release.txt
- name: "Run"
run: python3 -m ruff check .
================================================
FILE: .github/workflows/o365release.yaml
================================================
name: O365 Release
on:
release:
types: [published]
jobs:
release_zip_file:
name: Prepare release asset
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v5"
- name: Release Asset
uses: "rogerselwyn/actions/release-asset@main"
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
component: o365
releasenotes:
name: Prepare release notes
needs: release_zip_file
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v5"
- name: Release Notes
uses: "rogerselwyn/actions/release-notes@main"
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: master
================================================
FILE: .github/workflows/stale.yaml
================================================
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v10
with:
days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io/api/python,visualstudiocode
# Edit at https://www.gitignore.io/?templates=python,visualstudiocode
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
### VisualStudioCode ###
.vscode/*
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.gitignore.io/api/python,visualstudiocode
.DS_Store
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## v5.3.5 (2025/10/17)
### 🐛 Fixes
- [Include oauthlib dependency](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/febda93a41270306c887bf5bfe5e59d271792df1) - @RogerSelwyn
### ⬆️ Dependencies
- [bump ruff from 0.12.8 to 0.13.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/84ef2164fdc7468b6ea98bd3cdca5bdfefdca748) - @dependabot[bot]
- [bump ruff from 0.13.0 to 0.13.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5c31a77bf4e04276bbd606fe3d4c7b897985baf3) - @dependabot[bot]
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/aa94804e29e3bc944536156b2562528d9f5aa9e0) - @actions-user
### 🔖 Release
- [Release v5.3.5](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/57e92669d9f06ae5c50a1aa4e53b85ae70219b8e) - @RogerSelwyn
## v5.3.4 (2025/09/10)
### 🐛 Fixes
- [Fix error in ToDo migration](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/444b8fa155b59af4b04bc7cb7dd74ca3b0a7eb13) - @RogerSelwyn
### 🔖 Release
- [Release v5.3.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/80a75b7e567ebf8f4806022b4df348d0515efb85) - @RogerSelwyn
## v5.3.3 (2025/09/09)
### 🐛 Fixes
- [Support migration to MS365 by providing QueryBuilder support](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/10e5999a1f4a33f9581e8574c9061e334cce87dd) - @RogerSelwyn
### ⬆️ Dependencies
- [bump ruff from 0.11.4 to 0.12.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0ac28dd269dde7e6333b4b61749f5803c75cf541) - @dependabot[bot]
- [bump ruff from 0.12.2 to 0.12.8](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8bf0fb73a70e88896611acd32dea70a9f2629411) - @dependabot[bot]
- [bump actions/checkout from 4 to 5](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e1e45138ccd0e042fe39986beb8ec1688af2e8e0) - @dependabot[bot]
- [bump actions/stale from 9 to 10](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/061023cb9feedfd2649a62d4175481e7d428563e) - @dependabot[bot]
- [bump actions/setup-python from 5 to 6](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ad6f1f1cd8e5d08baf519ec4248d29c46fed3c93) - @dependabot[bot]
### 🔖 Release
- [Release v5.3.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/91696ce4a56a85458937d3cfb276e410f1a621f7) - @RogerSelwyn
## v5.3.2 (2025/06/09)
### 🐛 Fixes
- [Revert part of change in v5.3.1 causing 500 error](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ed0832b517fbbfa9855323a126737bf762c0611d) - @RogerSelwyn
### 📚 Documentation
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/895dac826f4c96e039b15719e4aba67c9382a284) - @RogerSelwyn
### 🔖 Release
- [Release v5.3.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/35cbb15664f5a7874e7ddcc1b156cc5b29f2bcf8) - @RogerSelwyn
## v5.3.1 (2025/06/09)
### 🐛 Fixes
- [Fix calendar colour missing](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/387e0030dea47a435b383c5f906cec6aa3d40f15) - @RogerSelwyn
### 🔖 Release
- [Release v5.3.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/90ffae10c8b4effc27b13342a9687fccdf5d5c95) - @RogerSelwyn
## v5.3.0 (2025/06/03)
### 🐛 Fixes
- [Implement workaround for MS 500 error response](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/46b7ad0396732e383152c7abc655a5d640859710) - @RogerSelwyn
### ⬆️ Dependencies
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/246be98f3dec7816b9eb9056c4c8479e7a8cfc36) - @actions-user
### 🔖 Release
- [Release v5.3.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/dfdcc82be4afd741a4fc9131709f66f60edcdc44) - @RogerSelwyn
## v5.2.2 (2025/05/22)
### 🧰 Maintenance
- [Spelling corrections](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/fd7c237b326741288b45e9ddac74c8d8fa6b2901) - @RogerSelwyn
- [Unpin MSAL](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2243ac1fce31d53e7d8ca283bda73f91149d870f) - @RogerSelwyn
### ⬆️ Dependencies
- [Update ruff to 0.11.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7de7915c0b52b8d8daffc71921022fe222773bb0) - @RogerSelwyn
- [Bump python-o365 to 2.1.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ef6df1a3c5eed12ce2a907d9f95a9a8cb9e44ace) - @RogerSelwyn
- [Pin python-o365==2.1.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2d2c01239ce63315936702221f2386fb7abb3aa3) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/26836d84fe9c6ecf34b742b722bda37cff750396) - @actions-user
### 📚 Documentation
- [Slight tweak to text since it obviously looks different for personal accounts](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/45c669faf5be0f50cb581dca6dd1e94f954efc41) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/41473d84289e386e693c6a31b86d73e75b23f755) - @RogerSelwyn
- [Update migration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a20f59ce59e3afa5ce053d0c8a10d65ed8a42bd9) - @RogerSelwyn
- [Update migration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b24ee36d486b456c947fca84e2b79ee2eb72acd3) - @RogerSelwyn
### 🔖 Release
- [Release v5.2.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/82498e2e0874f2d3ea1f0cb9c1a902f4c59fcc9f) - @RogerSelwyn
### Other
- [Update migration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b1c90cc8367ecf3103b72f474b5326bb090df6e0) - @GitHubGoody
- [build(deps): bump ruff from 0.11.0 to 0.11.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d26d2ad2c10685de5c60e4b49a758b7f018f25f9) - @dependabot[bot]
## v5.2.1 (2025/03/14)
### 🐛 Fixes
- [Fix issue with token refresh - pin MSAL to 1.13.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/882db03e2038f421e924f05ca9696e5b8aec6c4e) - @RogerSelwyn
### ⬆️ Dependencies
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/732bc34b7fd68242713af7fbd23b3c2cda996f71) - @actions-user
### 🔖 Release
- [Release v5.2.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/de3b8dd6bbb8e13da2cc08934637ecd05144b392) - @RogerSelwyn
## v5.2.0 (2025/03/10)
### 🧰 Maintenance
- [Add deprecation warning for Teams](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9d94bee6818a5baf64990c2af16b7584251424e9) - @RogerSelwyn
### 📚 Documentation
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6c33735601e955a2ac89cafd79289ccc7ef9c1f9) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/26fa50a8fc10bfab294a2ef2f6680c0dda6b03e9) - @RogerSelwyn
### 🔖 Release
- [Release v5.2.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7c154aeba04f01ec510d9a24c8be60e90a0760a0) - @RogerSelwyn
## v5.1.1 (2025/02/27)
### 🐛 Fixes
- [Fix tasks not being marked as complete/uncomplete via HA](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/59343f4991543b38ec011cdfdb5e8975334748a8) - @RogerSelwyn
### 🧰 Maintenance
- [Workflow ordering](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b4f7ab018a7939a450cfaa4281a054a19943b1c4) - @RogerSelwyn
- [Use master branch](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f85142c2927111a8811dc9a7aa8a84381792f066) - @RogerSelwyn
### 📚 Documentation
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e1b6783314802e270322624c890839f118ec43ae) - @RogerSelwyn
- [Update the docs](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2fc2212e2fd8eca5d4639dc6063b27fd954b677d) - @RogerSelwyn
### 🔖 Release
- [Release v5.1.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e92c873929b15eeb4ee51de9f83eceb277b2b797) - @RogerSelwyn
## v5.1.0 (2025/02/18)
### 🧰 Maintenance
- [Add deprecation warning for Calendar, Mail and ToDo](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d13bd3838a2ebfcaf945395fbf8ed2c04d59b69d) - @RogerSelwyn
### 📚 Documentation
- [Update migration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/278e708b895e68744dce92183aabc798647bcb22) - @RogerSelwyn
### 🔖 Release
- [Release v5.1.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/dd0710391d0bbf4511af3c8fb069a7dddcfc183f) - @RogerSelwyn
## v5.0.0 (2025/02/17)
### 💥 Breaking Changes
- [Update in support of python-o365 change to MSAL](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/81ce1774d62ac588d8e4c74680e8f09dbab7a55c) - @RogerSelwyn
### ⬆️ Dependencies
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ee9d56982feec90601000532fe6de5f551a8aa67) - @actions-user
### 📚 Documentation
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d54ee04480f1802e6daa891f892afddc98f96b79) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c3886352a696f61a49abcbecfb2c5571bc8fc347) - @RogerSelwyn
### 🔖 Release
- [Release v5.0.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7a92af9669c2102b418f12605605b7d40c96333c) - @RogerSelwyn
## v4.9.0 (2024/11/30)
### ✨ Enhancements
- [Add support for migration to MS365 integrations](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8a64b0334c6bcfffe49d77a5c3d21b5bac02b6e4) - @RogerSelwyn
### 📚 Documentation
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6fd5fd2fdec8baa5084f46c7129c507bb5b31d08) - @RogerSelwyn
### 🔖 Release
- [Release v4.9.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/71c50bd550b8089c72ebed30f550b702706ddf0f) - @RogerSelwyn
## v4.8.7 (2024/11/23)
### 🐛 Fixes
- [Ensure all calls to O365 library methods are async](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/bc3e3fddc2e0689e7ec34807777d8ade633402b7) - @RogerSelwyn
- [Correct more O365 library call to async](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/40b16b759c52703424d2c1676762e4a1a7f55a7f) - @RogerSelwyn
- [Fix typo](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4a253b2faa2e57928a27dece7fe0c288cde92bcb) - @RogerSelwyn
- [Don't update todo if status is being changed](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6187f92015aed7259a0b80f7e29429801b972231) - @RogerSelwyn
- [Fix error in notification send](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2587e0c787b1519df14b1aacb2e1348e02a8385f) - @RogerSelwyn
### 🧰 Maintenance
- [Bump HA dependency](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/945e2cc19fae2733d5be927ebcd11cbbe9925fbe) - @RogerSelwyn
### ⬆️ Dependencies
- [Bump python-o365 to 2.0.38](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b1b6480d7ab00190c95093ad834a9abcd599f7e4) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8620bdef11d046bd724d95c5acb2ed266a6b2f3d) - @actions-user
### 📚 Documentation
- [Add show_body config attribute](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ad0adf40e7b9dc7bb0711671ec83789f605a85ea) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1aade71209d1f980dc6a5908196bce6cf6f62802) - @RogerSelwyn
- [Update docs](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/98c83c2f1ba5f41e85aa83a10d856b04569beabb) - @RogerSelwyn
### 🔖 Release
- [Release v4.8.7](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/46d5c93df441db9fba0c73b0d902516f6cf9ceb1) - @RogerSelwyn
## v4.8.5 (2024/09/04)
### 🐛 Fixes
- [Fix issue of o365 library accessing token within the event loop](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/210ef0ac22131a19ff513d9170ad913f992b760e) - @RogerSelwyn
### 🧰 Maintenance
- [Bring code into line with MS365 integrations](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/be75195aa51fea09e583176a539644d910c42451) - @RogerSelwyn
### 🔖 Release
- [Release v4.8.5](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/de9f3b0fe38041e5d195d959a9114c923965d609) - @RogerSelwyn
## v4.8.4 (2024/08/13)
### 🐛 Fixes
- [Handle corrupted token file gracefully](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d3021dcb02a9951657d819a169a5802ca1d175fc) - @RogerSelwyn
### 🔖 Release
- [Release v4.8.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/af1944bde46911b0a0657edb08ed29905dd94dce) - @RogerSelwyn
## v4.8.3 (2024/08/05)
### 🐛 Fixes
- [Capture errors from fetching events so as not to provide empty data attributes](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c94e7c98731a1ff427aa61fc61766a2ce176680c) - @RogerSelwyn
### 🔖 Release
- [Release v4.8.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/636f8716ba32542f01a5a9a615a854703843577b) - @RogerSelwyn
## v4.8.2 (2024/07/07)
### 🐛 Fixes
- [Fix incorrect Due date shown for To Do](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/11e65fead77edb6f585a1f81f825ba9871499b93) - @RogerSelwyn
### 🧰 Maintenance
- [Remove use of internal attribute](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3d17973987985c36dd9ca1a02f3a535c8499dc9d) - @RogerSelwyn
- [Remove update of tasks yaml file from the event loop](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4133107efd74844493f0840502a7835f18cd31aa) - @RogerSelwyn
### ⬆️ Dependencies
- [Bump O365 to 2.0.36](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/424b93d17e5cf731cb666f2e5d128197ae6d93ab) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3620109d36ca941e57a499fe965b059e162960be) - @actions-user
### 🔖 Release
- [Release v4.8.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2b17c173219ef0f531154f0b632ca488c3bfbaef) - @RogerSelwyn
## v4.8.1 (2024/06/06)
### 🧰 Maintenance
- [Move writing of calendar file outside the thread](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1279922fa84634187660a676e376ee8e5202f0bc) - @RogerSelwyn
- [Remove use of `DEFAULT_TIME_ZONE` constant](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/66d876705e523d88a2d9a12dfe4aba70e966dffa) - @RogerSelwyn
### 📚 Documentation
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/35abd416e0e8bc636b0896e9b71db464c351b91f) - @RogerSelwyn
### 🔖 Release
- [Release v4.8.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d9d9e237617ef008fc71c93da2607241f2f840f5) - @RogerSelwyn
## v4.8.0 (2024/05/27)
💥 Breaking Changes (Potentially)
**Note** I've decided with this release to remove a whole raft of complex logic around minimum permissions. When I took this on and made permissions more granular I maintained a capability such that if you set the configuration to enable updates, but actually only granted read permissions on the Azure App the integration would still create the sensors.
To be honest, this is a pain to maintain and makes the code confusing. Since it is now possible to set the permissions you require to the right level of granularity via the configuration, I've removed this code complexity.
I have maintained an ability whereby if the config is set to have Read only permissions, but the Azure app has been granted ReadWrite, then the Read functionality will still work. This enables the situation where the same Azure App is used in multiple configurations with different permission sets.
I've tested as thoroughly as I can, butI've set this as Beta for now, so a slow take up picks up any remaining bugs. If you get an error in your logs along the lines of `Minimum required permissions: 'Calendars.ReadWrite'. Not available in token 'o365_primary.token' for account 'primary'`, then check your configuration to ensure it matches what you intended and what your Azure App permissions enable.
- [Remove complex logic around minimum permissions](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/aaffd73da7e492bc33d76af57f96fd80f9e18b38) - @RogerSelwyn
The below fix may break your setup, the `enable_update` parameter at the top level was incorrectly defaulting to True, when it should be False. If you were relying on the default, you will have to add `enable_update: true` to your config.
- [Correct default of calendar enable_update to False](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/64a113696ce2ebecbfdc14a495c6fc89c6b1cfa9) - @RogerSelwyn
### ✨ Enhancements
- [Add ability to disable calendar](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/72d66178745267de3417154c4e7a9af07c0db21c) - @RogerSelwyn
The rational for adding this enhancement is that the integration is becoming to unwieldy, so I'm moving to a point where I break it out into 3 integrations, calendar, email and other. These could all use the same azure app, but would potentially run from ui based setup (running both yaml and UI is a pain). The first one I would break out is calendar, which is I think how this integration started. So to create a painless transition, there is a need to be able to disable calendar in this integration.
### 🐛 Fixes
- [Correct modify calendar schema](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/341e1ac1448adf0b419ebdeda0acbf1eca640180) - @RogerSelwyn
- [Correct modify todo service](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/83d6c34cb0f2deebe564f6fa74e1c88fbd45c6c1) - @RogerSelwyn
- [Fix error in repair permissions checks](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/190dc7ed36c04df21c433bb6086bbad40dd7fa40) - @RogerSelwyn
- [Remove unrequired import](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d23a20158441ee50565ca928399969e030bae6c9) - @RogerSelwyn
- [Fix handle failed retrieval of events](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f4ff89497fb7e593a558f81b164a0978b4eda58c) - @RogerSelwyn
- [Trapping of connection error](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8b8c06deb3ecc2aa53e5e35f6dd50f11d56fd194) - @RogerSelwyn
### 🧰 Maintenance
- [Remove unrequired import](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f6bdc31406d4d6015db38d4daf9a75afc0f848b1) - @RogerSelwyn
- [Update requirements_release.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4636c377094671cafee1ac6da878878b06cdd058) - @RogerSelwyn
- [Create lint.yaml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4804930c0814b02f9680041aa613c83cd13a1965) - @RogerSelwyn
- [Add extra debugging](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1c25de0e9264704260c2195ef47a266626eec818) - @RogerSelwyn
- [Capture errors from calendar retrieval](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1cd8ae2153bbc9f84eb61aa38c154cca6092f167) - @RogerSelwyn
### 📚 Documentation
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6b62939face0088fa19f9c5da0f6f62ed407181f) - @RogerSelwyn
- [Update installation_and_configuration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/dfa1cc266c1e1422b80f7cc60e4f6fa80525166c) - @RogerSelwyn
### ⬆️ Dependencies
- [Bump ruff](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/baf03486f0090d48f154444ef6a3975fab1f74fb) - @RogerSelwyn
### 🔖 Release
- [Release v4.8.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6bf79b739349fc2fab17515bfe0317400e0b2081) - @RogerSelwyn
## v4.7.4 (2024/05/13)
### 🐛 Fixes
- [Fix broken create/update of events - Changed datetime format](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a1e55b538497dbabe4d777b18e5d7457249dd8a6) - @RogerSelwyn
- [Fix error when updating event using o365 service](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/14f23a60493585b740961e2482fc262569613c20) - @RogerSelwyn
### 🧰 Maintenance
- [Parallel run setup of sensors and mail](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2b23ee5313331fa9af20630eed93ae67183de2cb) - @RogerSelwyn
- [Minor code re-organisation](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4e6fc50459049ce5fe36b9c7ddd0793e461a1161) - @RogerSelwyn
- [Move file management calls to be outside the event loop](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3f10991569cdf538372f16891af92ba7edb21d02) - @RogerSelwyn
### 🔖 Release
- [Release 4.7.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/cf507c3f7cc76a6166a49b20a4764c159178e2a9) - @RogerSelwyn
## v4.7.3 (2024/05/07)
### ✨ Enhancements
- [Add support to send Teams messages as HTML contents](https://github.com/RogerSelwyn/O365-HomeAssistant/pull/224) - @pantherale0
### 🐛 Fixes
- [Fix todo creation to correctly accept reminder date/time](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/cd1fcc1b9b25bbb828087c9b44ef1b3ef891fa3f) - @RogerSelwyn
### 🔖 Release
- [Release v4.7.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c0a3a184d65772173a2ad61e41f1db1c8216b936) - @RogerSelwyn
## v4.7.2 (2024/05/06)
### 🐛 Fixes
- [Handle situation where chat member has no display_name](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2cc463d097ed6fc74c5dc8713542b3d9557378d2) - @RogerSelwyn
### 🧰 Maintenance
- [Update hacs.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a2dd88024aa8dcabd2a2b24d8b1e80e8f3c53365) - @RogerSelwyn
### 🔖 Release
- [Release v4.7.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5e01e9e8a10dce13fe1854f7734dd8aeb0cce6ad) - @RogerSelwyn
## v4.7.1 (2024/03/25)
### 🐛 Fixes
- [Enable clearing of ToDo description from HA ToDo panel](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/68660eb213844476dca3cc0fd334ba7aef439dff) - @RogerSelwyn
### 🔖 Release
- [Release v4.7.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b10a7afdee132599793f2d122fd2e559d2eb5305) - @RogerSelwyn
## v4.7.0 (2024/03/06)
### ✨ Enhancements
- [Add flag status to emails](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/caa14025f05620813a30982141ce09ba92637ede) - @RogerSelwyn
- [Add support for updating user Teams status](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/293bc5236c1a1684458cf4ad8f87b740086c919e) - @RogerSelwyn
- [Add ability to monitor another user's Teams status](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5dac2f832667173c1b69a328370e99fbb457a0e8) - @RogerSelwyn
- [Add support for icons.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/76588df288fd8155fef97fe7775cbd965de3846e) - @RogerSelwyn
- [Add set user preferred status service](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/481caad2743554767eef3afa734bd6b79a7fbe52) - @RogerSelwyn
- [Add task status as a status attribute](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/429d30425f21b53bb3bbeb0237f7bc9485ad2eab) - @RogerSelwyn
### 🐛 Fixes
- [Add missing service description](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a27db7b416df4c896f22e4aec73fc74fdd34234a) - @RogerSelwyn
- [Add missing CONFIG_SCHEMA](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4523fbcfd74948727f9c92f7e5e367fdad4a17d7) - @RogerSelwyn
- [Fix display of auto-reply state](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/52a40821f636db3208ab6641f63c8ce87dd1f632) - @RogerSelwyn
- [Fix hassfest error in icons.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9d528fc94047273e8c14d7523bf37e16b0ef4dfe) - @RogerSelwyn
### 🔨 Maintenance
- [Remove `Integration` from name](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b566ce149aa589bf13cc94e1379e9788632d6828) - @RogerSelwyn
### ⬆️ Dependencies
- [Bump O365 to 2.0.33](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/335e36d1cbfee43d6ae23a06f7f7c20988513cb9) - @RogerSelwyn
- [Bump O365 to 2.0.34](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ad1a625244c705e9922120321797dca09266ee0e) - @actions-user
### 📚 Documentation
- [Add documentation for `track_new` for To-Do lists](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f9cc1b8f2dead8306ff372e534b5167fadb1fa3f) - @RogerSelwyn
- [Update todo.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1f22bc5a5581bce7f9030d4c8f8cab62d7093f30) - @uSlackr
- [Update todo.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/735abbfe73fe0544b5c5834555a835f19b109a6b) - @RogerSelwyn
- [Update services documentation for user status setting](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ab554291c38d91fcf7be8f21da2fc9064cddebc1) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ac43e4c3d7eb982a651ab73507aee0d6c7a700ed) - @RogerSelwyn
- [Add example for exclude attribute](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b14c4140ece7696e2e814f656f82111221da944d) - @RogerSelwyn
### 🔖 Release
- [Release v4.7.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/64dfb15d9d851040ab862146da3886de8c7f1948) - @RogerSelwyn
## v4.6.2 (2024/01/21)
### 🐛 Fixes
- [Fix error in sending event for new_todo](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/42858754edaa7aa232aa8df90d9e8e05ca813c13) - @RogerSelwyn
### 🔖 Release
- [Release v4.6.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d4b621d692e384b34721eeee4e8fcc30b32bcf05) - @RogerSelwyn
## v4.6.1 (2024/01/06)
### 🐛 Fixes
- [Fix unique_id error for new email sensor](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8a110221822207b634ff71d222d7cb068a707925) - @RogerSelwyn
### 🔖 Release
- [Release v4.6.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/594a0cec04006685e7e016e01288bba111125929) - @RogerSelwyn
## v4.6.0 (2024/01/06)
### 💥 Breaking Changes
- [Remove support for deprecated legacy config method](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b57bbe30cb418eb304450c013d17af4a52e72d03) - @RogerSelwyn
### ✨ Enhancements
- [Add support for updating events via HA Calendar pane](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c321f976e9ac5c4cb5a9f02881fa4ba30a36cc6d) - @RogerSelwyn
### 🐛 Fixes
- [Ensure uniqueness of email sensor unique_id](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c02f9ba9997d482a16e897b3aff96595496dfdbc) - @RogerSelwyn
### 🔨 Maintenance
- [Remove redundant code](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d35f39b849b9e275eb9aa5e1226368b4c3c6bb26) - @RogerSelwyn
### 📚 Documentation
- [Update docs for removal of legacy config format](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/491f03721ca5e2fbd542cc0817d547fa15908c0e) - @RogerSelwyn
### 🔖 Release
- [Release v4.6.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/96d10959aee4affd9a5abea310c7d1069544b195) - @RogerSelwyn
## v4.5.4 (2024/01/04)
### ✨ Enhancements
- [Add ability to supress body in email sensors](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e7570b111bea6542d3d82eaf3f235175bc194bf4) - @RogerSelwyn
### 🔨 Maintenance
- [Hide pylint abstract-method](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4a1df9baa5ecff4230cfcb07c82b5d714356e898) - @RogerSelwyn
### 🔖 Release
- [Release v4.5.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/56e1840488d86648a28ae88b42ca845c33fdf5e1) - @RogerSelwyn
## v4.5.3 (2024/01/03)
### 🐛 Fixes
- [Fix non-update of description and due date](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/dda07d4d9a5172c1d7adf4f92de55b985678faf6) - @RogerSelwyn
- [Fix non-display of description and due date](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e0d93b25fd3decc7d1e973e922321d6585d9ddbf) - @RogerSelwyn
### 🔖 Release
- [Release v4.5.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/15f9b247c25112a21ccd3e1012ec31c515547579) - @RogerSelwyn
## v4.5.2 (2024/01/01)
# 💥 Breaking Changes - Requires HA 2023.12
**This release adds support for the new ToDo entity in HA 2023.11. However it also removes the equivalent sensor entity previously created.**
**This release adds support for the new ServiceValidationError in HA 2023.12.**
- [Task services, service attributes and events renamed to todo](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d9a208291963a7e0a03a548b8b3708ae335d7922) - @RogerSelwyn
- [Implementation of support for Todo entities released in HA 2023.11](https://github.com/RogerSelwyn/O365-HomeAssistant/pull/177) - @RogerSelwyn
- [Use new ServiceValidationError available in 2023.12](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4292e46f54cca6993420f21536f1e49c29f20ac3) - @RogerSelwyn
### ✨ Enhancements
- [Add support for due date and description setting in HA ToDo](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e624bd5dcc7bcc5e56e3ddf3b318a860acb95c06) - @RogerSelwyn
### 🐛 Fixes
- [Enable install onto older versions of HA](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/86b0e81054459e29146668a2b15070176de0f1f8) - @RogerSelwyn
- [Fix setup issue v4.4.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0be5cd6028e50788cb0b0bcfeda8662fca62b46b) - @RogerSelwyn
- [Fix color attribute handling for group calendars](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4d640d02fc542880ed2f81d1ca1d5a46d79b421f) - @RogerSelwyn
### 🔨 Maintenance
- [Delete sensor entities that have been replaced by ToDo entities](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/80ba0f6b5291d6694eefcc76a0949565b2350c25) - @RogerSelwyn
- [Custom icon no longer needed for ToDo](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f5db5bc9014316a19a6178b691a4427422e29f3c) - @RogerSelwyn
- [Break out email into separate coordinator for performance](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3dc89a0b191718a7fc513acd87bc886dc2b366e3) - @RogerSelwyn
- [Show datetime selector for reminder on To Do](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/18af3ce9ffe8f61b667857affcf1a92b4cba47ac) - @RogerSelwyn
- [Remove linting errors](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8fa0c923a1a343d82641666818c60e40bff83e4d) - @RogerSelwyn
- [Clarify attribute naming for future maintainability](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e0d73eece639034c9646186940d31585bae19368) - @RogerSelwyn
- [Add warning to highlight permission differences](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/29fadf061d0bcf3548763f8697c752baf6aee187) - @RogerSelwyn
- [Update sk.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2be07eba3d8576bb34490e46c8fd4cf94d2397b2) - @misa1515
- [json file formatting](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0750ea0b222c42d7a098089d6a8ea2889f111b81) - @RogerSelwyn
### 📚 Documentation
- [Update todo docs](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/978c9984a7403bca22b4627faa87e65e68569014) - @RogerSelwyn
### 🔖 Release
- [Release 4.5.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d2c96f153e3a7031c0420c99d0afc2182665d800) - @RogerSelwyn
## Past Changes
Changes 2023
## v4.4.4 (2023/11/03)
### 🐛 Fixes
- [Fix setup issue v4.4.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4bf6f6774b5b89201b4e81ed79f75909d226414b) - @RogerSelwyn
### 🔖 Release
- [Bump to v4.4.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/36b69937c038c88b5a09da7363a35056800043e5) - @RogerSelwyn
## v4.4.3 (2023/11/02)
### 🐛 Fixes
- [Fix invalid task handling (as a result of code re-org)](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b91b1fd319319bca703614c6de14154ecccba14d) - @RogerSelwyn
- [Fix error in mail retrieval (as a result of code re-org)](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/73f705494505ab3a8213604623023d2529dc2831) - @RogerSelwyn
- [Fix incorrect sensor setup (as a result of code re-org)](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/219367856d00407011a9327f484267a8b7b8558a) - @RogerSelwyn
### 🔨 Maintenance
- [Mark colors as unrecorded](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/db068bb0f2a2cbcea3475021fbf85a3adeaac21a) - @RogerSelwyn
- [Major coordinator re-organisation to enable simpler maintenance](https://github.com/RogerSelwyn/O365-HomeAssistant/pull/175) - @RogerSelwyn
- [Add warning indicating corrupt token](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3da9de825dc24fe1a55c52aa63d02d9ecbcc8d9c) - @RogerSelwyn
### 📚 Documentation
- [Update installation_and_configuration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/94a32a385735f9daf6fbfff8f91375603ce657a4) - @the-smart-home-maker
### 🔖 Release
- [Bump to v4.4.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/90797b381806b692c293cd6138e4dfb6c79d75de) - @RogerSelwyn
## v4.4.2 (2023/10/29)
### ✨ Enhancements
- [Add color attributes to calendar](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b2622a29e17576b4ffdc56625adb3d0906af69ad) - @RogerSelwyn
### 🔨 Maintenance
- [Reduce attributes stored in recorder](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/bd47a103ccb9c2fe2d9b238317a0ecd34456be79) - @RogerSelwyn
- [Imports sorted](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4079894631f1156c925af91c3ba307ca589bffaf) - @RogerSelwyn
- [Limit data attribute recording for all sensor entities](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/193b255f44662361ebd125e167f930d893370c87) - @RogerSelwyn
- [Remove linting error](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/94004753d4bbdbeb35c0908cafeb48f22335e303) - @RogerSelwyn
### 📚 Documentation
- [Update sensor.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/93c08d4c9c7ddc05edf354d83bc63e1242845f84) - @RogerSelwyn
### 🔖 Release
- [Bump to v4.4.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/41a31b00523852af37810b5432c4f75a891dc634) - @RogerSelwyn
## v4.4.1 (2023/10/06)
### ✨ Enhancements
- [Slovak translation - Thanks!!](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/76cf7030f69e226b03beabafd3c16f25219fd38b) - @misa1515
### 🔨 Maintenance
- [Bump to v4.4.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3ec08ca74fe00c93546af698a381734e178e3ac8) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7faded291068b194a09c104e870f6e3adc7af8bd) - @actions-user
### ⬆️ Depenencies
- [Bump python-o365 to 2.0.28](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c8bfc090644ca5216c2a2586f2ec7ea8d69d7417) - @RogerSelwyn
-
## v4.4.0 (2023/10/05)
### ✨ Enhancements
- [Add basic calendar permission support](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0739e510afc336639544dd52d2eac4aa1d4107e0) - @RogerSelwyn
- [Update calendar entity quicker after add/change/delete](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7036b2c08fbc29a61286baae48809c1868fdd4f6) - @RogerSelwyn
### 🐛 Fixes
- [Logger fix](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/07d20115f725dd89c38bd430c7053d4079d759b2) - @RogerSelwyn
- [Fix issue with deleting events using service](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d39de0390022e105cfd2d3bc59d87757bfe00384) - @RogerSelwyn
- [Fix token filename creation](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3169f9d065e517f6faf285f0c891552a0af14422) - @RogerSelwyn
- [Fix calendar entity not updating after last event delete](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3b3c1f664d80c72845ec9282db1492b4e42eb8cb) - @RogerSelwyn
### 🔨 Maintenance
- [Restructure permission code for maintainability](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2270f0359a8cd396bc8b3b1c66c27b6ad37489ce) - @RogerSelwyn
- [Minor code tidy up](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/04a7c8bc596118e5a4a12b336d4b3994b0afffe2) - @RogerSelwyn
- [Remove redundant check for file location](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1c535a0e0e0d25f1b8adf401c4572935dde61731) - @RogerSelwyn
- [Further refactoring](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0f6075282d8d8706930b5803d6eccf84c2e35b1d) - @RogerSelwyn
- [Update dependabot.yml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/824e6a270b782b095c459005cee70176c5c36425) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/372c0c890e08e2b89e977e02c0e6e286d57e9c9f) - @actions-user
- [Pull all permissions methods into Permissions class](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/882eddf229b951d9c096293b959b45b816bf534b) - @RogerSelwyn
- [Bump actions/checkout from 2 to 4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e6dc0816067c0f8351a635e3a1787cb7b1ec00a4) - @dependabot[bot]
### 📚 Documentation
- [Add clarification on events for external task closure](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/33edd56d84fa6c70f58dc46ff954318073725bf1) - @RogerSelwyn
- [Make it clear that Client Secret Value is required, not ID](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/55e7930664493622f44c36e1ec1b44a3bd60bdac) - @RogerSelwyn
## v4.3.4 (2023/08/21)
### Maintenance
- [Added extra validation in support of issue 155](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/87ee2d11c2c1d85b97eec369ccf9c7b3a17dcb00) - @RogerSelwyn
- [Update __init__.py](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/04e5a5b5cb10849fa91c41e22ff12312a3693fa7) - @RogerSelwyn
- [Refine messaging for authentication/token errors](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6a65d11cbb07101e43bcabc6e95286f23fe9b2a9) - @RogerSelwyn
- [Bump to v4.3.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9f5a6ec708c8f35dd08a2c5aa7d4c2b93d6013e9) - @RogerSelwyn
### Documentation
- [Adjust installation restart steps for 2023.6.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/48e1c5abd72030e1a7739405c0c9fd7933a299fc) - @lunmay
- [Added possible teams status, from microsoft graph documentation, fix typo on user permissions table](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/cac42f4a355efd01f1675aec7a367d84cec6b406) - @fixtse
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/02d85719fd98ef2794a7de70d2089d4b484c2cf3) - @RogerSelwyn
## v4.3.3 (2023/06/14)
### Enhancements
- [Add capture of expired client secret error](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8324414889fb2be3b3c7142705a54d0bbf4ff3cf) - @RogerSelwyn
### Maintenance
- [Break up utilities for readability](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2fee043a6d0496f6a3a06f2ed4432c40b61afaa0) - @RogerSelwyn
- [Code tidy up](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/39ecebf34190a300c1639f5bfc624253628d89a6) - @RogerSelwyn
- [Code split up](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/06f72a17693114bf6ce979428e857dafaefad670) - @RogerSelwyn
- [Revert "Code split up"](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ace798c7afb90a868ddac91f88e317a51cfb60cd) - @RogerSelwyn
- [Remove redundant utils.py](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9adb83b62b81c04c8551404dd363bdee9891e2ff) - @RogerSelwyn
- [Bump to v4.3.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7e7b3a31223c7a13d2db8b05aa989751a9605ed5) - @RogerSelwyn
### Documentation
- [Add chat event docs](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7dc9b86306b52bb0150ed543a2632461e34282a8) - @RogerSelwyn
- [Add re-authentication info](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d0115eaf92cbdc18db91956ec26ebdc3ff00a89f) - @RogerSelwyn
## v4.3.2 (2023/05/30)
### Enhancements
- [Enable filter for upcoming tasks](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9860cf2138b3ebbf78efdec930785074877f5600) - @RogerSelwyn
### Maintenance
- [Bump to v4.3.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5b68110108d1b350a23e0e8c082c3d8f97fcf78b) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/724da269980141b5b8bd3e12d9b88ac26ef37e63) - @actions-user
## v4.3.1 (2023/05/30)
### Enhancements
- [Add ability to send chat message](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/07c8a848bb73a6968ba36297af20860de2dd6e7c) - @RogerSelwyn
### Fixes
- [Spelling correction](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d3cad5c38dde37c3b3978efb31236fa02fba83a8) - @RogerSelwyn
### Maintenance
- [Convert strings to constants](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3d77e080406dd805ea9ed34a40b417db5a68a986) - @RogerSelwyn
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7c6fda46e384721134612b466aad48eb289c3249) - @actions-user
## v4.3.0 (2023/05/22)
### Enhancements
- [Add support for shared mailboxes](https://github.com/RogerSelwyn/O365-HomeAssistant/pull/138) - @jgrieger1 / @RogerSelwyn
### Maintenance
- [Bump to v4.3.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a34675b599bd2e3f5fd78ca8eccbfddd3e477001) - @RogerSelwyn
### Documentation
- [Update documentation for shared mailboxes](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2bf828be64b544f53b44b47fc796a131d17092f8) - @RogerSelwyn
- [Update permissions.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f1bb670ee692503a170f4410b0ef2b16bb27d774) - @RogerSelwyn
- [Update permissions.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f695a1b898941a5f44f88d669b384e63e972069f) - @RogerSelwyn
## v4.2.12 (2023/05/08)
### Fixes
- [Fix error when no events returned to calendar view](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f16328c30c2e869f6c1846e93056f3a957ce5828) - @RogerSelwyn
### Maintenance
- [Bump to v4.2.12](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6dd5783d945ebc840fc80e6bc16cf960b9b1145d) - @RogerSelwyn
## v4.2.11 (2023/05/07)
### Fixes
- [Fix incorrect creation of auto_reply services](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/243612d77fe02c55166c76fa9d9c523acaf2cfd0) - @RogerSelwyn
- [Apply consistent sorting and return date instead of datetime for all day events](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/19b6e7cda822d69f52a45e8932a7bb3b4fbeeefe) - @RogerSelwyn
- [(Correct) Update schema.py](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/78bf4ea42a9d0151a72b3d904450f5087a799c5c) - @RogerSelwyn
### Maintenance
- [General code improvements](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e3f42d17d040c59839cac5631a9721f4b7dfcc4b) - @RogerSelwyn
- [More code tidy up](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/64a29a560da92c2500a8dcb8cde6809ad884200f) - @RogerSelwyn
- [Re-organise code](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a6e3e8217d484f093b8f4306cf980ce39fc01bf9) - @RogerSelwyn
- [Bump to v4.2.11](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8f773c2df7bc925d4a928f08d7e381844a6e2649) - @RogerSelwyn
### Documentation
- [[Typo] Fix for o365_calendars_convetted](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/fd4655ba47330cadd570d837b940d2590c1ac87b) - @rdeveen
## v4.2.10 (2023/04/24)
### Enhancements
- [Add service to mark task complete/incomplete](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6b6f1da3f0f022a9b9fed77e624b3d29b3729bc4) - @RogerSelwyn
### Fixes
- [Handle errors raised by core CalendarEvent](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f194d4d930eabc74963bfc001635c9053489ce7a) - @RogerSelwyn
### Maintenance
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4d3d14bc472807b567594413f6e8d53b26f5a377) - @RogerSelwyn
- [Bump to v4.2.10](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2a9f2d827a03dd9b51d9639d436c2c7cb0fa4e47) - @RogerSelwyn
## v4.2.9 (2023/04/03)
### Enhancements
- [Improve performance by reducing retrieved data](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/822c7ab445ec52e2996f73a0b1ed6e86cddacd76) - @RogerSelwyn
- [Add permission granularity for ToDo Tasks](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/73cd2609544af6454dc470e8e5ffe150ac677c66) - @RogerSelwyn
### Maintenance
- [Minor code improvements](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4dccb9ec1302570dab89de4f8e7a7a92409cc56c) - @RogerSelwyn
- [Update events.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c2d0a835790861f575b7e7e25db17a40c4cd2a00) - @RogerSelwyn
- [Bump to v4.2.9](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/36ec710bccab06c37612df027a0cdd401a5389ba) - @RogerSelwyn
## v4.2.8 (2023/03/18)
### Enhancements
- [Migrate authorization to Repair UI](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0c6fc8c4dfe5b84d7f9cf1c43a7be6f8e3acb0fb) - @RogerSelwyn
### Maintenance
- [Update docs for installation problems](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6dc8bfc39a01ba91bda48ff47d4538503561fc24) - @RogerSelwyn
- [Update prerequisites.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9af30e5ca0065bba47ae4cfe85d6293db6c0dacf) - @RogerSelwyn
- [Remove redundant constants](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d59b3345a799e84648bae9cc83740c306e758149) - @RogerSelwyn
- [Fix issue with validating token](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c874c1d3aff79bec96072f5a62c63c9054d8907e) - @RogerSelwyn
- [Code tidy up](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f95012fb0f15a03baaea28def97d9f97a4beaa5e) - @RogerSelwyn
- [Bump to v4.2.8](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/76f0c8e73dd6019f938cf072b59318e20b77fb8c) - @RogerSelwyn
- [Sourcery refactoring](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c20a93d3acf175dd5b1b0ec3cc38583a7d7ede53) - @RogerSelwyn
## v4.2.7 (2023/03/12)
### Fixes
- [Fix issue with use of subdirectories in attachments and photos](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/417eb5c4eb8a72a6f0a13beb5404fa680ecd0987) - @RogerSelwyn
### Maintenance
- [Bump to 4.2.7](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5524fc8acf8be8f6f2624a8fe48aae6f572e3615) - @RogerSelwyn
## v4.2.6 (2023/03/07)
### Enhancements
- [Enable recurrence delete via calendar UI](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/028c2763e245ca7edcabbae858e041cd39e6771b) - @RogerSelwyn
### Maintenance
- [Add information on calendar panel](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/af1c4b0598fa2517288a1b3d2d63ca0699e5a1ef) - @RogerSelwyn
- [Bump to v4.2.6](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c3fb444afeaab1b965fc8fd5ce4291f51bc68c3a) - @RogerSelwyn
## v4.2.5 (2023/03/07)
### Enhancements
- [Add ability to add/delete events via calendar UI](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0d078174809c991cf24ed7412e028fb62e730b85) - @RogerSelwyn
### Maintenance
- [Bump to v4.2.5](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/216d4dc1f00e628665cbbd2189cc9fc8d0292182) - @RogerSelwyn
## v4.2.4 (2023/02/23)
### Enhancements
- [Add option to output email as html](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5981450b1f4a9f13c3e0a32b6ce7c6abdd9e5abc) - @RogerSelwyn
### Maintenance
- [Update hassfest.yaml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6cc77d6794b053e7594ce4f7b92f617ef61ea41c) - @RogerSelwyn
- [Update hacs.yaml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/58464bca85162b685b483c4a4b51e6df0fee971b) - @RogerSelwyn
- [Update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/950cce020d532ce9bfe7a835e3e03865add61ba4) - @RogerSelwyn
- [Bump to v4.2.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0a68bdd4b1f6183a47cd38fe6220a5d24b10104b) - @RogerSelwyn
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/50a4e2f274c67bc7cf73025c6c45eb6e6be16887) - @actions-user
## v4.2.3 (2023/02/19)
### Enhancements
- [Add event raising for task/calendar actions. Also add ability to show completed tasks](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/037ec04f22cbf67892e82651d82744d8747a278d) - @RogerSelwyn
### Maintenance
- [Bump to v4.2.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0a9aebe0be07172116ecad1f6570e2d1ae84be0e) - @RogerSelwyn
## v4.2.2 (2023/02/09)
### Enhancements
- [Add importance as option for send email](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/08da2c0774f98bd6fb7abde10f97d3f523df4423) - @RogerSelwyn
### Maintenance
- [Bump to v4.2.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/910942819a14a14d65b33d396dcf852ad0031dfe) - @RogerSelwyn
- [Update service details](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/30f2eafd9aac16e0c001b0b90cc8f26a41d24de6) - @RogerSelwyn
## v4.2.1 (2023/02/09)
### Enhancements
- [Add regex support to calendar excludes](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7cd1d397ad9b7dd577cec3898df8339c061292df) - @RogerSelwyn
### Maintenance
- [Bump to v4.2.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/686e0332259a8fb1ae0609d1ebda65895b88859b) - @RogerSelwyn
## v4.2.0 (2023/02/08)
### Enhancements
- [Add ability to exclude events containing string from list](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ebe861eede9da2ffceaf5c0cc702468c1f9f46b8) - @RogerSelwyn
### Maintenance
- [Bump o365 from 2.0.25 to 2.0.26](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/21d8e16d99f04368bed641036c69fdd2a2f69896) - @dependabot[bot]
- [Bump to 4.2.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/fdc2e46ff27fd8ec09f7415d7ba952311eb82e88) - @RogerSelwyn
- [Bump O365 to 2.0.26](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/10b037e8ac54c5371291f49d43b5dffff255968d) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b632dc60711739bf1d978bcaf11df85aeae1fc26) - @actions-user
## v4.1.2 (2023/01/22)
### Fixes
- [Fix unique_id for new calendars](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7f76812cb43ae000d32e32981fcbec77d87f74ff) - @RogerSelwyn
### Maintenance
- [Bump to v4.1.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d26ba9c4ce6cb8783b29eceb5334ab8d2f6e9b6b) - @RogerSelwyn
## v4.1.1 (2023/01/22)
### Fixes
- [Fix for non-unique unique_ids](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2f685bed0af709a157fe1fde0adc4a10bc4b417a) - @RogerSelwyn
### Maintenance
- [Update sensor.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a0a28231c747faba123973d147c7e4ced9ae335a) - @RogerSelwyn
- [Bump to v4.1.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9e9bedcf0e0db85719909a38d29fdad16fb1973c) - @RogerSelwyn
## v4.1.0 (2023/01/17)
### Enhancements
- [Add existing auto reply settings display](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/84132fabac2144d2bed7ce080f91de6c17781a5c) - @RogerSelwyn
### Maintenance
- [Bump to v4.1.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2cbbf3edf27437a8adb24c5f2661ef073dcf4e5b) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/63c89fc4054ae35d4f57748b7d9e01511b4a942a) - @actions-user
## v4.0.8 (2023/01/10)
### Enhancements
- [Added sensor for targeting auto-reply](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/bdb4e20055fbaafdcf6e04b98e5ab2621c3e67b4) - @RogerSelwyn
### Maintenance
- [Update index.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ffe0491077819e92499c616f1483edf637fb97dd) - @RogerSelwyn
- [Add documentation for Tasks and new security permissions](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5ab0dae2be4e3a4b50d8f393b050a943f25909fd) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a97c49be9904302db29b9f832c0e3bc3f237f6e8) - @RogerSelwyn
- [Added separate permissions page](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3022f20d80cba0aec37ac1fad114d8f3d919b485) - @RogerSelwyn
- [Correction](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a7c1d4095e3fb1aab0b406b33991bbf3d5685e04) - @RogerSelwyn
- [Update sensor.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/38967f9a0b8d29e039110bb98627d8e4024cd833) - @RogerSelwyn
- [Update docs](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3ff2927d13bb908ce9aec5a11611eb19edc9bde8) - @RogerSelwyn
- [Bump to v4.0.8](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a5164a6ab20ed932530ce13b3c8d2d63ebe48aca) - @RogerSelwyn
Changes 2022
## v4.0.7 (2022/12/22)
### Fixes
- [Fix notify service name for converted accounts](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/cd6b14c6c908ab67e3c7b7aaf4316243ffc10ba7) - @RogerSelwyn
### Maintenance
- [Update prerequisites.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e51d714dc4ac4d0a94097f9d0fdd63b23f2d02a5) - @RogerSelwyn
- [Update prerequisites.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/faa1f88c6a9314096266b2bc9dba2089f02bb122) - @RogerSelwyn
- [Update errors.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/898067cbd375028fd8c24b8e1c78e727cd84c8a4) - @RogerSelwyn
- [Bump to v4.0.7](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ca9631e50f300f3de7d2731b36e4a15e0da722c8) - @RogerSelwyn
## v4.0.6 (2022/12/18)
### Fixes
- [Fix Todo Entity Names](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/27b81b0bceccd64c356b1cc737b81cb67994e87a) - @RogerSelwyn
- [Fix Todo entity name for converted accounts](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/05ecf1a7d8489bba61676789bd90722770e42e06) - @RogerSelwyn
## v4.0.5 (2022/12/18)
### Enhancements
- [Add unique_id](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7386123d30a4a81d303f0db9965309d28b8e118d) - @RogerSelwyn
### Maintenance
- [Update services.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2a5feb6dcbbf96b4eb8b08a2f41bad29e1c1d4ff) - @RogerSelwyn
- [Update index.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4e1ab05be189838177bba63279fdaf544563d69e) - @RogerSelwyn
- [Bump to v4.0.5](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d2dd978365665862500daf3cfb2a7ffacaedb376) - @RogerSelwyn
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6bbf74c5378cf38dabb8dedc2fde7744248e708a) - @RogerSelwyn
- [Correct unique_id](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/316adcb1aa27826fe13cd1582daa4ef8f773a870) - @RogerSelwyn
## v4.0.4 (2022/12/14)
### Enhancements
- Add update/delete task services
- Add enable/disable auto reply services
- Add data co-ordinator to reduce parallel calls to O365 api
- Improve quality of service calls. Provide proper inputs via UI.
### Maintenance
- Break out sensors into separate class files for simplification.
- Change name of module to `Office 365` rather than `Office 365 Calendar`
Full list of changes
- [Split out sensors](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ee1a2307612ebff87e7bdeda370be791d3894fa2) - @RogerSelwyn
- [Incorporate conflicting changes](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/54cb00217701a70f62e4b890facb5316f42f9b24) - @RogerSelwyn
- [Remove blank lines](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/cc2573bdab052c891d5d64d1b8d9b03f06d255d0) - @RogerSelwyn
- [Refit previous changes](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/acd9ab231d76a1567e26c83d9cb871b8967638e6) - @RogerSelwyn
- ['Refactored by Sourcery'](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/adb49403c8b5aa3351fd604fa8b2002c3f6b2532) - @None
- [Convert to inclusion as a platform service on Inbox and Query sensor](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c2ea6093ba85597c7bec006927f77358fcded76e) - @RogerSelwyn
- [Remove necessary parameter](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/09721ad4a47f6d54d200f202ae88ad86e78b1f88) - @RogerSelwyn
- [Change name of module since it it no longer just a calendar](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4a0bf8c1090aca3459bace40a492c4c70027247d) - @RogerSelwyn
- [Fix setup of services](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/71611761ef34bcda2c5d78c362d86c4071f814af) - @RogerSelwyn
- [Add description](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b1f72337aef6b82b14bdbc2137b534b8030f6c6d) - @RogerSelwyn
- [Minor tweaks](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e630bbcf9806bdb35c446b9befb83b7a4100825c) - @RogerSelwyn
- [Add services for update/delete task](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7f550969697b62e8e576b6391977ad1df74bad47) - @RogerSelwyn
- [Extend autoreply capabilities](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ed678c1cedd9d7d06bead73f1a5c0c261399915e) - @RogerSelwyn
- [Improve quality of service calls via UI](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1d107b61234868061e35c37288549275a53f5e4e) - @RogerSelwyn
- [Bump to v4.0.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b9d4332cdfdaba8095c2f43710650b0c888751c2) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8aa0158478b47dbc9a4fa7da5cf56f0238a7795a) - @actions-user
Full list of changes
- [Tidy up file storage location](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6dbf28a5cda2f5d3beef7fa3fc5e3c8e6184ffb9) - @RogerSelwyn
- [Rename](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/81da6cc7e4ea9c46cb1d128aa1306710cbf57a61) - @rdeveen
- [Add ATTR_ICON](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5bc5afae286608b7b0528c89c60feb55af272edb) - @rdeveen
- [Add const ATTR_ICON](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/069f3fb135546b1ba2f77ca90618aa4f3a06b965) - @rdeveen
- [Change Icon](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/daa327f377b74a055d3966e8b31fee6fc5536b0b) - @rdeveen
- [Use Icon property (#1)](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8bd25027b83deb9633bafd386b966ab1db551dbf) - @rdeveen
- [Add icons for chats and inbox](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9e6fd7d2e207d60fce8f0e7d8423dc60fb48eaec) - @RogerSelwyn
- [Initial draft (awaiting O365 update)](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/fd2d642a811f96841953635836af6488a38abffc) - @RogerSelwyn
- [Code simplification](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2067cf72e5f1fb91fa7a5fb341481984fcc182bb) - @RogerSelwyn
- [Improve service validation and remove deprecated calendar_id](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0abd3a46f2e827a5dacf2147e7c1073f50a1103f) - @RogerSelwyn
- [Move schema to schema.py](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ae31ca548646d622316285b03ce13b5c9d9c2525) - @RogerSelwyn
- [Change service errors to vol.Invalid](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f477f4d000b6620dd2b4520226b366d94e214b90) - @RogerSelwyn
- [Update services.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4be969c9f781668a3418314f68d791a72767d882) - @RogerSelwyn
- [Update index.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/596558175b0116c3099ab73c38064585ae46b26c) - @RogerSelwyn
- [Update and rename sensor_layout.md to sensor.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f980c3da65eb392c91413b63abb47661fb33c1c7) - @RogerSelwyn
- [Update sensor_layout.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0550bdc0d5132a08e00a28bcbf3761980add125d) - @RogerSelwyn
- [Update installation_and_configuration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f011f015abd90d23b0f584acb9d1b76f64c21ee7) - @RogerSelwyn
- [Update calendar_configuration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0a2b426a880511f1598b220ea49b5306f1eeca34) - @RogerSelwyn
- [Rename title to subject in line with O365 module.](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f1c920a964c594d2b110a17e996c94a4c9685e22) - @RogerSelwyn
- [Add parallel_updates to restrict number of calls](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0924c2904cf0d4217ea60117b3852809b0403110) - @RogerSelwyn
- [Add error catch and bump O365 to 2.0.22](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1ccb274a34f780a6d634def370e20f941a092177) - @RogerSelwyn
- [Tweak error message](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/198a4e79d802befb096d7c8d95dea35746dfdd27) - @RogerSelwyn
- [Bump to v4.0.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7f2b649f40efcfc1103c36f041dfada24ea59bc8) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/da7f5832ea8d4f9a37a7e94674a02f7784f63f4a) - @actions-user
- [Fix event create for group calendars](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5c273a6396e946b810b771ec696c88d2fdd37f60) - @RogerSelwyn
- [Change release to v4.0.0 Beta 1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5c5ff38e4290293534bc900eabcc560372751d31) - @RogerSelwyn
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ebd24ad2784adea04f053b44474524f959e729ad) - @RogerSelwyn
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/be6447f909f2c37462a6c7fd8f345099ab51de4e) - @RogerSelwyn
- [Make tasks_lists configureable](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1afa7c6141e942a8b15dc01d528f9ee93420a291) - @RogerSelwyn
- [Bump to v4.0.0.b2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8d46eafad84d60783a50da0a27f2ffa09e1e002f) - @RogerSelwyn
- [Remove deprecated alt_auth_flow config parameter](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a22d6f6f92b2e082630d3ba710273dac89269bd9) - @RogerSelwyn
- [Add deprecation warning for secondary configuration.](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ddfd2785ea27a58f0357f2dc6a8a407bbb8c2bd1) - @RogerSelwyn
- [Add more support for legacy account migration](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d28efc3e1602b9264f2bc387547b64de6b328dbe) - @RogerSelwyn
- [At repair description text](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/890a181b3a9ca6d5ed743c510bb54ec39d23c893) - @RogerSelwyn
- [Bump to v4.0.0b3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b12801a612b90f2d940152bdb2950e6f5cc2e267) - @RogerSelwyn
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4c94d8c0f59522f07979f02e707bd992617d5999) - @RogerSelwyn
- [Create codeql.yml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/cf37942079ea6afc3e1d1d15508c30721dcea9fa) - @RogerSelwyn
- [Fix folder parameter usage](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8b5c7b982aca10c9bcf09a0f0fd6f9523c66ea4e) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1eb935d098d66f0b19b6ee1ebc18101afcf6fe82) - @actions-user
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d054a4671e64e41b9bcb352caade9dec5a0e2e61) - @RogerSelwyn
- [Fix incorrect service name created after conversion](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/41ccbdeebbe4ed700aa582de3be05cbca25ce22c) - @RogerSelwyn
- [Bump to v4.0.0b5](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/442fcef341149558a862fae5e1127bef34002696) - @RogerSelwyn
- [Bump to v4.0.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4dacdd9aeca9c01990dca2dc56a5c16f3ae96e34) - @RogerSelwyn
Changes 2021
## v2.2.7 (2021/12/12)
### Fixes
- [Fix device_state_attributes warning](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4ba056b01c8dcc824cd39256d0751009ce49740a) - @RogerSelwyn
### Maintenance
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/25a9e248362042daa0fad8541ea77f17c82f9a59) - @RogerSelwyn
- [Bump to 2.2.7](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/674d398e5af1c92764e270c15e6a28d085c9715a) - @RogerSelwyn
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/296fc51bad89cc0f57ad32c04e0f9bcbd91d8530) - @actions-user
## v2.2.6 (2021/09/28)
### Fixes
- [Fix incorrect handling of all days events](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a8d02bf63e727f27e32ac88f3295e4ba2df3643c) - @RogerSelwyn - [#6](https://github.com/RogerSelwyn/O365-HomeAssistant/issues/6)
### Maintenance
- [Remove unrequired iot_class](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d56fcf68832979a82df7de4de732f417c438f409) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1ea49358593dddf588c261075ddb8365a58c8e20) - @RogerSelwyn
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/03a9de89f00a1718840c1b9df397d10b4609b686) - @actions-user
- [Handle beta releases](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/24202179acaba12bfd49423e286717aaf5830e98) - @RogerSelwyn
- [Update to use rogerselwyn/actions](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ae2b49790fbc462e2562b00d15408515190ef411) - @RogerSelwyn
- [Correct step name in release](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ddd085f8f8db38b84c7c42ad8b7d6598afd65bed) - @RogerSelwyn
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b1d575622660e866a2a743c9e08426d9c82be469) - @actions-user
## v2.2.5 (2021/09/13)
### Fixes
- [Prefer external url for authentication over internal](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5a949bb78b20c80e69994537c9774b614f2e3e09) - @RogerSelwyn - [#5](https://github.com/RogerSelwyn/O365-HomeAssistant/issues/5)
### Maintenance
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6c1f569be2d846748c9ec7039704b0537462555f) - @RogerSelwyn
- [Bump o365 from 2.0.15 to 2.0.16](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/01f4124e4f2a6b99d24cfdead8f3f3376e0da8cc) - @dependabot[bot]
- [Bump to 2.2.5](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b1957443ade157614a84492e953905e718d318a7) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/76c1fc94b654c02e5bd7eef02d72750c308bf8c1) - @actions-user
## v2.2.4 (2021/09/12)
### Maintenance
- [Update for recommendations by sourcery](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/475cc4de0a5624dc9e320afaca44c16e6b184663) - @RogerSelwyn
- [Code recommendations from codefactor](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d290dadfe7e67e53bb3bb15a7e04d3b892168f31) - @RogerSelwyn
- [Bump to 2.2.4](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7315271fed296d60c67f51848aac808c0167181c) - @RogerSelwyn
## v2.2.3 (2021/09/10)
### Maintenance
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2aaad7bcb86ddd5c700c59b171c2866f6e6b7c3b) - @RogerSelwyn
- [Create dependabot.yml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/601fce70844b150ea5c8bf1d1f654a8e35e52e99) - @RogerSelwyn
- [Correct dependency versions](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/48d0634123cb30ad878b8c2565af911760e964ec) - @RogerSelwyn
- [Bump to v2.2.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/792872bdf9db8adf8fb9028f83d10068a6871cfa) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/973c13e500a55d6d7b11856f3c2cb85dd376e1bc) - @actions-user
## v2.2.2 (2021/09/09)
### Maintenance
- [Change code owner](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c31333fd0b9fd8115f92a776e025b9d78bc5b07b) - @RogerSelwyn
- [Update update_version.py](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e47746b0055a11870b4bd106681cf3c201d9451c) - @RogerSelwyn
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6326d079b97c52f91d60f9de2a6b96894a56402c) - @RogerSelwyn
- [Correct hacs.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/61edff5bcb2a32463d8c3d32ae5bd7791ced912a) - @RogerSelwyn
## v2.2.1 (2021/09/07)
### Fixes
- [Fix issue with authentication I/O within the event loop](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2dfa7859c1a29f775965b3d73b323093bb8848db) - @RogerSelwyn
### Maintenance
- [Deconstrain requirements](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3a546ec608eb07bfdca22645481911ad41a8b43b) - @RogerSelwyn
- [Correct version](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7631c0a4a64f55d198a4b0cda1ba7db2bad766d8) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f4690f8a133c652cdaa15f95c62f6d15bf7d4c03) - @actions-user
## v2.2.0 (2021/09/06)
### Maintenance
- [Updated to remove deprecation warning on base_url use](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/afb9e98203c405cfdb41b98c3bb46aedc946279f) - @RogerSelwyn
- [Fix for all day_event](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/debf019e085d445fae1749f31c872592bd32ad7d) - @PTST
- [Now actually implements the offsets](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3fc4c9af68435156e77e03cf0aaf97bfbd2d20a8) - @PTST
- [Black formatting](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d61a26d607ff080b880d02854de4476cbeec9587) - @PTST
- [Update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/619c733475e5814048f2c76edf7d39d1fcbaab08) - @RogerSelwyn
- [Create o365release.yaml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b1182ff33b58a1c02217cc43bc75fedaf0314f70) - @RogerSelwyn
- [Create pushpull.yaml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0a8579b265c123c0dbaac5351561ba90d0ec68f4) - @RogerSelwyn
- [Create pushpull.yaml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ecd897fb82385776f516a9d7a725303f1932e13d) - @RogerSelwyn
- [Move](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ab2ed7b96927b54da4ea3f9871c643b9a8205680) - @RogerSelwyn
- [Update .gitignore](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/881f10baf9e727cf719ab26814d80736204b52b5) - @RogerSelwyn
- [Add management components](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0087c426a2613ad037a3ac078e5776cf2b8ece55) - @RogerSelwyn
- [Update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b53202150473153c6f94ad833784fe14bfa03e01) - @RogerSelwyn
- [Hassfest corrections](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7a3afd87b4d103377c573e3f9b399d671fc3c432) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8a1633a88ea1ba803b8cddc265dad6a561c51f17) - @actions-user
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e0421b126d600503a97174e511078ddf804c3331) - @actions-user
- [Split workflows](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/14ab44459088a7c76d29f7a11e9fbb3858128256) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/262b82f9648cb705eefd37b9ccbab7edbed77b02) - @RogerSelwyn
Earlier
### Maintenance
- [Updated to remove deprecation warning on base_url use](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/afb9e98203c405cfdb41b98c3bb46aedc946279f) - @RogerSelwyn
- [Fix for all day_event](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/debf019e085d445fae1749f31c872592bd32ad7d) - @PTST
- [Now actually implements the offsets](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3fc4c9af68435156e77e03cf0aaf97bfbd2d20a8) - @PTST
- [Black formatting](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d61a26d607ff080b880d02854de4476cbeec9587) - @PTST
# Documentation
The full documentation is available here - [O365 Documentation](https://rogerselwyn.github.io/O365-HomeAssistant/)
Nice video from fixtse showing how to install the O365 integration to Home Assistant. Also providing some Lovelace cards for displaying content from O365 - [O365 Card for Home Assistant](https://github.com/fixtse/o365-card)
# Migration
Details on how to migration to the new MS365 integrations can be found here - [Migration](https://rogerselwyn.github.io/O365-HomeAssistant/migration.html)
================================================
FILE: custom_components/o365/__init__.py
================================================
"""Main initialisation code."""
import json
import logging
import voluptuous as vol
import yaml
from homeassistant.const import CONF_ENABLED
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from O365 import Account, FileSystemTokenBackend
from oauthlib.oauth2.rfc6749.errors import InvalidClientError
from .classes.permissions import Permissions
from .const import (
CONF_ACCOUNT,
CONF_ACCOUNT_CONF,
CONF_ACCOUNT_NAME,
CONF_ACCOUNTS,
CONF_AUTO_REPLY_SENSORS,
CONF_CHAT_SENSORS,
CONF_CLIENT_ID,
CONF_CLIENT_SECRET,
CONF_CONFIG_TYPE,
CONF_FAILED_PERMISSIONS,
CONF_GROUPS,
CONF_SHARED_MAILBOX,
CONF_STATUS_SENSORS,
CONF_TODO_SENSORS,
CONST_CONFIG_TYPE_LIST,
CONST_PRIMARY,
CONST_UTC_TIMEZONE,
DOMAIN,
TOKEN_FILE_CORRUPTED,
TOKEN_FILE_MISSING,
TOKEN_FILE_OUTDATED,
)
from .helpers.migration import MigrationServices
from .helpers.setup import do_setup
from .schema import MULTI_ACCOUNT_SCHEMA
CONFIG_SCHEMA = vol.Schema({DOMAIN: MULTI_ACCOUNT_SCHEMA}, extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass, config):
"""Set up the O365 platform."""
_LOGGER.debug("Startup")
conf = config.get(DOMAIN, {})
accounts = MULTI_ACCOUNT_SCHEMA(conf)[CONF_ACCOUNTS]
conf_type = CONST_CONFIG_TYPE_LIST
for account in accounts:
await _async_setup_account(hass, account, conf_type)
await _async_setup_migration_service(hass, conf)
_LOGGER.debug("Finish")
return True
async def _async_setup_account(hass, account_conf, conf_type):
credentials = (
account_conf.get(CONF_CLIENT_ID),
account_conf.get(CONF_CLIENT_SECRET),
)
account_name = account_conf.get(CONF_ACCOUNT_NAME, CONST_PRIMARY)
main_resource = account_conf.get(CONF_SHARED_MAILBOX)
_LOGGER.debug("Validate shared")
if not _validate_shared_schema(account_name, main_resource, account_conf):
return
_LOGGER.debug("Permissions setup")
perms = Permissions(hass, account_conf, conf_type)
permissions, failed_permissions = await perms.async_check_authorizations()
account, is_authenticated = await hass.async_add_executor_job(
_try_authentication, perms, credentials, main_resource
)
if is_authenticated and permissions is True:
_LOGGER.debug("do setup")
check_token = await _async_check_token(hass, account, account_name)
if check_token:
await do_setup(
hass,
account_conf,
account,
is_authenticated,
account_name,
conf_type,
perms,
)
else:
await _async_authorization_repair(
hass,
account_conf,
account,
account_name,
conf_type,
failed_permissions,
permissions,
)
def _try_authentication(perms, credentials, main_resource):
_LOGGER.debug("Setup token")
token_backend = FileSystemTokenBackend(
token_path=perms.token_path,
token_filename=perms.token_filename,
)
_LOGGER.debug("Setup account")
account = Account(
credentials,
token_backend=token_backend,
timezone=CONST_UTC_TIMEZONE,
main_resource=main_resource,
)
try:
return account, account.is_authenticated
except json.decoder.JSONDecodeError:
return account, False
async def _async_check_token(hass, account, account_name):
try:
await hass.async_add_executor_job(account.get_current_user_data)
return True
except InvalidClientError as err:
if "client secret" in err.description and "expired" in err.description:
_LOGGER.warning(
"Client Secret expired for account: %s. Create new Client Secret in Azure App.",
account_name,
)
else:
_LOGGER.warning(
"Token error for account: %s. Error - %s", account_name, err.description
)
return False
except RuntimeError as err:
if "Refresh token operation failed: invalid_grant" in err.args:
_LOGGER.warning(
"Token has expired for account: '%s'. "
+ "Please delete token, reboot and re-authenticate.",
account_name,
)
return False
elif "Refresh token operation failed: invalid_client" in err.args:
_LOGGER.warning(
"Invalid Client ID for account: '%s'. "
+ "Please delete token, reboot and re-authenticate.",
account_name,
)
return False
raise err
def _validate_shared_schema(account_name, main_account, config):
if not main_account:
return True
error = False
if config.get(CONF_STATUS_SENSORS, None):
_LOGGER.error("Status sensor not allowed for shared account: %s", account_name)
error = True
if config.get(CONF_CHAT_SENSORS, None):
_LOGGER.error("Chat sensor not allowed for shared account: %s", account_name)
error = True
if (
config.get(CONF_TODO_SENSORS, None)
and config.get(CONF_TODO_SENSORS)[CONF_ENABLED]
):
_LOGGER.error("Todo sensors not allowed for shared account: %s", account_name)
error = True
if config.get(CONF_GROUPS, None):
_LOGGER.error("Groups not allowed for shared account: %s", account_name)
error = True
if config.get(CONF_AUTO_REPLY_SENSORS, None):
_LOGGER.error(
"AutoReply sensor not allowed for shared account: %s", account_name
)
error = True
return not error
async def _async_authorization_repair(
hass,
account_conf,
account,
account_name,
conf_type,
failed_permissions,
token_missing,
):
base_message = f"requesting authorization for account: {account_name}"
if token_missing == TOKEN_FILE_MISSING:
message = "No token file found;"
elif token_missing == TOKEN_FILE_CORRUPTED:
message = "Token file corrupted;"
elif token_missing == TOKEN_FILE_OUTDATED:
message = "Token file is outdated, it has been deleted;"
else:
message = "Token doesn't have all required permissions;"
_LOGGER.warning("%s %s", message, base_message)
data = {
CONF_ACCOUNT_CONF: account_conf,
CONF_ACCOUNT: account,
CONF_ACCOUNT_NAME: account_name,
CONF_CONFIG_TYPE: conf_type,
CONF_FAILED_PERMISSIONS: failed_permissions,
}
# Register a repair issue
async_create_issue(
hass,
DOMAIN,
"authorization",
data=data,
is_fixable=True,
# learn_more_url=url,
severity=IssueSeverity.ERROR,
translation_key="authorization",
translation_placeholders={
CONF_ACCOUNT_NAME: account_name,
},
)
async def _async_setup_migration_service(hass, config):
migration_services = MigrationServices(hass, config)
hass.services.async_register(
DOMAIN, "migrate_config", migration_services.async_migrate_config
)
class _IncreaseIndent(yaml.Dumper):
def increase_indent(self, flow=False, indentless=False):
return super(_IncreaseIndent, self).increase_indent(flow, False)
================================================
FILE: custom_components/o365/calendar.py
================================================
"""Main calendar processing."""
import functools as ft
import logging
import re
from copy import deepcopy
from datetime import date, datetime, timedelta
from operator import attrgetter
from typing import Any
from homeassistant.components.calendar import (
EVENT_DESCRIPTION,
EVENT_END,
EVENT_RRULE,
EVENT_START,
EVENT_SUMMARY,
CalendarEntity,
CalendarEntityFeature,
CalendarEvent,
extract_offset,
is_offset_reached,
)
from homeassistant.const import CONF_NAME
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.util import dt as dt_util
from requests.exceptions import HTTPError, RetryError
from O365.utils.query import ( # pylint: disable=no-name-in-module, import-error
QueryBuilder,
)
from .const import (
ATTR_ALL_DAY,
ATTR_COLOR,
ATTR_DATA,
ATTR_EVENT_ID,
ATTR_HEX_COLOR,
ATTR_OFFSET,
CALENDAR_ENTITY_ID_FORMAT,
CONF_ACCOUNT,
CONF_ACCOUNT_NAME,
CONF_CAL_ID,
CONF_CAL_IDS,
CONF_CONFIG_TYPE,
CONF_DEVICE_ID,
CONF_ENABLE_UPDATE,
CONF_ENTITIES,
CONF_EXCLUDE,
CONF_HOURS_BACKWARD_TO_GET,
CONF_HOURS_FORWARD_TO_GET,
CONF_IS_AUTHENTICATED,
CONF_MAX_RESULTS,
CONF_PERMISSIONS,
CONF_SEARCH,
CONF_TRACK,
CONF_TRACK_NEW_CALENDAR,
CONST_CONFIG_TYPE_LIST,
CONST_GROUP,
DEFAULT_OFFSET,
DOMAIN,
EVENT_CREATE_CALENDAR_EVENT,
EVENT_HA_EVENT,
EVENT_MODIFY_CALENDAR_EVENT,
EVENT_MODIFY_CALENDAR_RECURRENCES,
EVENT_REMOVE_CALENDAR_EVENT,
EVENT_REMOVE_CALENDAR_RECURRENCES,
EVENT_RESPOND_CALENDAR_EVENT,
LEGACY_ACCOUNT_NAME,
PERM_CALENDARS_READWRITE,
YAML_CALENDARS_FILENAME,
EventResponse,
)
from .schema import (
CALENDAR_SERVICE_CREATE_SCHEMA,
CALENDAR_SERVICE_MODIFY_SCHEMA,
CALENDAR_SERVICE_REMOVE_SCHEMA,
CALENDAR_SERVICE_RESPOND_SCHEMA,
YAML_CALENDAR_DEVICE_SCHEMA,
)
from .utils.calendar_utils import (
add_call_data_to_event,
format_event_data,
get_end_date,
get_hass_date,
get_start_date,
)
from .utils.filemgmt import (
async_update_calendar_file,
build_config_file_path,
build_yaml_filename,
load_yaml_file,
)
from .utils.utils import clean_html
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, add_entities, discovery_info=None): # pylint: disable=unused-argument
"""Set up the O365 platform."""
if discovery_info is None:
return None
account_name = discovery_info[CONF_ACCOUNT_NAME]
conf = hass.data[DOMAIN][account_name]
account = conf[CONF_ACCOUNT]
is_authenticated = conf[CONF_IS_AUTHENTICATED]
if not is_authenticated:
return False
update_supported = bool(
conf[CONF_ENABLE_UPDATE]
and conf[CONF_PERMISSIONS].validate_authorization(PERM_CALENDARS_READWRITE)
)
cal_ids = await _async_setup_add_entities(
hass, account, add_entities, conf, update_supported
)
hass.data[DOMAIN][account_name][CONF_CAL_IDS] = cal_ids
await _async_setup_register_services(hass, account, update_supported)
_LOGGER.warning(
"The O365 Calendar sensors are now deprecated - please migrate to MS365 Calendar "
+ "- for more details on how to do this see "
+ "https://rogerselwyn.github.io/O365-HomeAssistant/migration.html"
)
return True
async def _async_setup_add_entities(
hass, account, add_entities, conf, update_supported
):
yaml_filename = build_yaml_filename(conf, YAML_CALENDARS_FILENAME)
yaml_filepath = build_config_file_path(hass, yaml_filename)
calendars = await hass.async_add_executor_job(
load_yaml_file, yaml_filepath, CONF_CAL_ID, YAML_CALENDAR_DEVICE_SCHEMA
)
cal_ids = {}
for cal_id, calendar in calendars.items():
for entity in calendar.get(CONF_ENTITIES):
if not entity[CONF_TRACK]:
continue
entity_id = _build_entity_id(hass, entity, conf)
device_id = entity["device_id"]
try:
cal = O365CalendarEntity(
account,
cal_id,
entity,
entity_id,
device_id,
conf,
update_supported,
)
await cal.data.async_calendar_data_init(hass)
except HTTPError:
_LOGGER.warning(
"No permission for calendar, please remove - Name: %s; Device: %s;",
entity[CONF_NAME],
entity[CONF_DEVICE_ID],
)
continue
cal_ids[entity_id] = cal_id
add_entities([cal], True)
return cal_ids
def _build_entity_id(hass, entity, conf):
account_name = conf[CONF_ACCOUNT_NAME]
entity_suffix = (
f"_{account_name}"
if (
conf[CONF_CONFIG_TYPE] == CONST_CONFIG_TYPE_LIST
and account_name != LEGACY_ACCOUNT_NAME
)
else ""
)
return generate_entity_id(
CALENDAR_ENTITY_ID_FORMAT,
f"{entity.get(CONF_DEVICE_ID)}{entity_suffix}",
hass=hass,
)
async def _async_setup_register_services(hass, account, update_supported):
platform = entity_platform.async_get_current_platform()
calendar_services = CalendarServices(hass, account)
await calendar_services.async_scan_for_calendars(None)
if update_supported:
platform.async_register_entity_service(
"create_calendar_event",
CALENDAR_SERVICE_CREATE_SCHEMA,
"async_create_calendar_event",
)
platform.async_register_entity_service(
"modify_calendar_event",
CALENDAR_SERVICE_MODIFY_SCHEMA,
"async_modify_calendar_event",
)
platform.async_register_entity_service(
"remove_calendar_event",
CALENDAR_SERVICE_REMOVE_SCHEMA,
"async_remove_calendar_event",
)
platform.async_register_entity_service(
"respond_calendar_event",
CALENDAR_SERVICE_RESPOND_SCHEMA,
"async_respond_calendar_event",
)
hass.services.async_register(
DOMAIN, "scan_for_calendars", calendar_services.async_scan_for_calendars
)
class O365CalendarEntity(CalendarEntity):
"""O365 Calendar Event Processing."""
_unrecorded_attributes = frozenset((ATTR_DATA, ATTR_COLOR, ATTR_HEX_COLOR))
def __init__(
self,
account,
calendar_id,
entity,
entity_id,
device_id,
config,
update_supported,
):
"""Initialise the O365 Calendar Event."""
self._config = config
self._account = account
self._start_offset = entity.get(CONF_HOURS_BACKWARD_TO_GET)
self._end_offset = entity.get(CONF_HOURS_FORWARD_TO_GET)
self._event = {}
self._name = f"{entity.get(CONF_NAME)}"
self.entity_id = entity_id
self._offset_reached = False
self._data_attribute = []
self.data = self._init_data(calendar_id, entity)
self._calendar_id = calendar_id
self._device_id = device_id
if update_supported:
self._attr_supported_features = (
CalendarEntityFeature.CREATE_EVENT
| CalendarEntityFeature.DELETE_EVENT
| CalendarEntityFeature.UPDATE_EVENT
)
self._error = None
def _init_data(self, calendar_id, entity):
max_results = entity.get(CONF_MAX_RESULTS)
search = entity.get(CONF_SEARCH)
exclude = entity.get(CONF_EXCLUDE)
return O365CalendarData(
self._account,
self.entity_id,
calendar_id,
search,
exclude,
max_results,
)
@property
def extra_state_attributes(self):
"""Extra state attributes."""
attributes = {
ATTR_DATA: self._data_attribute,
}
if hasattr(self.data.calendar, ATTR_COLOR):
attributes[ATTR_COLOR] = self.data.calendar.color
if hasattr(self.data.calendar, ATTR_HEX_COLOR) and self.data.calendar.hex_color:
attributes[ATTR_HEX_COLOR] = self.data.calendar.hex_color
if self._event:
attributes[ATTR_ALL_DAY] = (
self._event.all_day if self.data.event is not None else False
)
attributes[ATTR_OFFSET] = self._offset_reached
return attributes
@property
def event(self):
"""Event property."""
return self._event
@property
def name(self):
"""Name property."""
return self._name
@property
def unique_id(self):
"""Entity unique id."""
return (
f"{self._calendar_id}_{self._config[CONF_ACCOUNT_NAME]}_{self._device_id}"
)
async def async_get_events(self, hass, start_date, end_date):
"""Get events."""
return await self.data.async_get_events(hass, start_date, end_date)
async def async_update(self):
"""Do the update."""
# Get today's event for HA Core.
try:
await self.data.async_update(self.hass)
event = deepcopy(self.data.event)
except (HTTPError, RetryError, ConnectionError) as err:
self._log_error("Error getting calendar events for day", err)
return
if event:
event.summary, offset = extract_offset(event.summary, DEFAULT_OFFSET)
start = O365CalendarData.to_datetime(event.start)
self._offset_reached = is_offset_reached(start, offset)
# Get events for extra attributes.
try:
results = await self.data.async_o365_get_events(
self.hass,
dt_util.utcnow() + timedelta(hours=self._start_offset),
dt_util.utcnow() + timedelta(hours=self._end_offset),
)
except (HTTPError, RetryError, ConnectionError) as err:
self._log_error("Error getting calendar events for data", err)
return
self._error = False
if results is not None:
self._data_attribute = [format_event_data(x) for x in results]
self._event = event
async def async_create_event(self, **kwargs: Any) -> None:
"""Add a new event to calendar."""
start = kwargs[EVENT_START]
end = kwargs[EVENT_END]
is_all_day = not isinstance(start, datetime)
subject = kwargs[EVENT_SUMMARY]
body = kwargs.get(EVENT_DESCRIPTION)
rrule = kwargs.get(EVENT_RRULE)
await self.async_create_calendar_event(
subject,
start,
end,
body=body,
is_all_day=is_all_day,
rrule=rrule,
)
async def async_update_event(
self,
uid: str,
event: dict[str, Any],
recurrence_id: str | None = None,
recurrence_range: str | None = None,
) -> None:
"""Update an event on the calendar."""
start = event[EVENT_START]
end = event[EVENT_END]
is_all_day = not isinstance(start, datetime)
subject = event[EVENT_SUMMARY]
body = event.get(EVENT_DESCRIPTION)
rrule = event.get(EVENT_RRULE)
await self.async_modify_calendar_event(
event_id=uid,
recurrence_id=recurrence_id,
recurrence_range=recurrence_range,
subject=subject,
start=start,
end=end,
body=body,
is_all_day=is_all_day,
rrule=rrule,
)
async def async_delete_event(
self,
uid: str,
recurrence_id: str | None = None,
recurrence_range: str | None = None,
) -> None:
"""Delete an event on the calendar."""
await self.async_remove_calendar_event(uid, recurrence_id, recurrence_range)
async def async_create_calendar_event(self, subject, start, end, **kwargs):
"""Create the event."""
if not self._validate_permissions("create"):
return
calendar = self.data.calendar
event = calendar.new_event()
event = add_call_data_to_event(event, subject, start, end, **kwargs)
await self.hass.async_add_executor_job(event.save)
self._raise_event(EVENT_CREATE_CALENDAR_EVENT, event.object_id)
self.async_schedule_update_ha_state(True)
async def async_modify_calendar_event(
self,
event_id,
recurrence_id=None,
recurrence_range=None,
subject=None,
start=None,
end=None,
**kwargs,
):
"""Modify the event."""
if not self._validate_permissions("modify"):
return
if self.data.group_calendar:
_group_calendar_log(self.entity_id)
return
if recurrence_range:
await self._async_update_calendar_event(
recurrence_id,
EVENT_MODIFY_CALENDAR_RECURRENCES,
subject,
start,
end,
**kwargs,
)
else:
await self._async_update_calendar_event(
event_id, EVENT_MODIFY_CALENDAR_EVENT, subject, start, end, **kwargs
)
async def _async_update_calendar_event(
self, event_id, ha_event, subject, start, end, **kwargs
):
event = await self._async_get_event_from_calendar(event_id)
event = add_call_data_to_event(event, subject, start, end, **kwargs)
await self.hass.async_add_executor_job(event.save)
self._raise_event(ha_event, event_id)
self.async_schedule_update_ha_state(True)
async def async_remove_calendar_event(
self,
event_id,
recurrence_id: str | None = None,
recurrence_range: str | None = None,
):
"""Remove the event."""
if not self._validate_permissions("delete"):
return
if self.data.group_calendar:
_group_calendar_log(self.entity_id)
return
if recurrence_range:
await self._async_delete_calendar_event(
recurrence_id, EVENT_REMOVE_CALENDAR_RECURRENCES
)
else:
await self._async_delete_calendar_event(
event_id, EVENT_REMOVE_CALENDAR_EVENT
)
def _log_error(self, error, err):
if not self._error:
_LOGGER.warning("%s - %s", error, err)
self._error = True
else:
_LOGGER.debug("Repeat error - %s - %s", error, err)
async def _async_delete_calendar_event(self, event_id, ha_event):
event = await self._async_get_event_from_calendar(event_id)
await self.hass.async_add_executor_job(
event.delete,
)
self._raise_event(ha_event, event_id)
self.async_schedule_update_ha_state(True)
async def async_respond_calendar_event(
self, event_id, response, send_response=True, message=None
):
"""Respond to calendar event."""
if not self._validate_permissions("respond to"):
return
if self.data.group_calendar:
_group_calendar_log(self.entity_id)
return
await self._async_send_response(event_id, response, send_response, message)
self._raise_event(EVENT_RESPOND_CALENDAR_EVENT, event_id)
self.async_schedule_update_ha_state(True)
async def _async_send_response(self, event_id, response, send_response, message):
event = await self._async_get_event_from_calendar(event_id)
if response == EventResponse.Accept:
await self.hass.async_add_executor_job(
ft.partial(event.accept_event, message, send_response=send_response)
)
elif response == EventResponse.Tentative:
await self.hass.async_add_executor_job(
ft.partial(
event.accept_event,
message,
tentatively=True,
send_response=send_response,
)
)
elif response == EventResponse.Decline:
await self.hass.async_add_executor_job(
ft.partial(event.decline_event, message, send_response=send_response)
)
async def _async_get_event_from_calendar(self, event_id):
calendar = self.data.calendar
return await self.hass.async_add_executor_job(calendar.get_event, event_id)
def _validate_permissions(self, error_message):
if not self._config[CONF_PERMISSIONS].validate_authorization(
PERM_CALENDARS_READWRITE
):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="not_authorised_to_event",
translation_placeholders={
"calendar": PERM_CALENDARS_READWRITE,
"error_message": error_message,
},
)
return True
def _raise_event(self, event_type, event_id):
self.hass.bus.fire(
f"{DOMAIN}_{event_type}",
{ATTR_EVENT_ID: event_id, EVENT_HA_EVENT: True},
)
_LOGGER.debug("%s - %s", event_type, event_id)
class O365CalendarData:
"""O365 Calendar Data."""
def __init__(
self,
account,
entity_id,
calendar_id,
search=None,
exclude=None,
limit=999,
):
"""Initialise the O365 Calendar Data."""
self._account = account
self._limit = limit
self.group_calendar = calendar_id.startswith(CONST_GROUP)
self.calendar_id = calendar_id
self._schedule = None
self.calendar = None
self._search = search
self._exclude = exclude
self.event = None
self._entity_id = entity_id
self._error = False
self._builder = QueryBuilder(protocol=account.protocol)
async def async_calendar_data_init(self, hass):
"""Async init of calendar data."""
if self.group_calendar:
self._schedule = None
self.calendar = await hass.async_add_executor_job(
ft.partial(self._account.schedule, resource=self.calendar_id)
)
else:
self._schedule = await hass.async_add_executor_job(self._account.schedule)
self.calendar = None
async def _async_get_calendar(self, hass):
try:
schedule = await hass.async_add_executor_job(self._account.schedule)
query = self._builder.select("name", "id", "canEdit", "color", "hexColor")
self.calendar = await hass.async_add_executor_job(
ft.partial(
schedule.get_calendar, calendar_id=self.calendar_id, query=query
)
)
return True
except (HTTPError, RetryError, ConnectionError) as err:
_LOGGER.warning("Error getting calendar events - %s", err)
return False
async def async_o365_get_events(self, hass, start_date, end_date):
"""Get the events."""
if not self.calendar:
if not await self._async_get_calendar(hass):
return []
events = await self._async_calendar_schedule_get_events(
hass, self.calendar, start_date, end_date
)
if events is None:
return None
events = self._filter_events(events)
events = self._sort_events(events)
return events
def _filter_events(self, events):
lst_events = list(events)
if not events or not self._exclude:
return lst_events
rtn_events = []
for event in lst_events:
include = True
for exclude in self._exclude:
if re.search(exclude, event.subject):
include = False
if include:
rtn_events.append(event)
return rtn_events
def _sort_events(self, events):
for event in events:
event.start_sort = event.start
if event.is_all_day:
event.start_sort = dt_util.as_utc(
dt_util.start_of_local_day(event.start)
)
events.sort(key=attrgetter("start_sort"))
return events
async def _async_calendar_schedule_get_events(
self, hass, calendar_schedule, start_date, end_date
):
"""Get the events for the calendar."""
query = self._builder.select(
"subject",
"body",
"start",
"end",
"is_all_day",
"location",
"categories",
"sensitivity",
"show_as",
"attendees",
"series_master_id",
)
if self._search is not None:
query = query & self._builder.contains("subject", self._search)
# As at March 2023 not contains is not supported by Graph API
# if self._exclude is not None:
# query.chain("and").on_attribute("subject").negate().contains(self._exclude)
try:
return await hass.async_add_executor_job(
ft.partial(
calendar_schedule.get_events,
limit=self._limit,
query=query,
include_recurring=True,
start_recurring=self._builder.greater_equal("start", start_date),
end_recurring=self._builder.less_equal("end", end_date),
)
)
except (HTTPError, RetryError, ConnectionError) as err:
_LOGGER.warning("Error getting calendar events - %s", err)
return None
async def async_get_events(self, hass, start_date, end_date):
"""Get the via async."""
results = await self.async_o365_get_events(hass, start_date, end_date)
if not results:
return []
event_list = []
for vevent in results:
try:
event = CalendarEvent(
get_hass_date(vevent.start, vevent.is_all_day),
get_hass_date(get_end_date(vevent), vevent.is_all_day),
vevent.subject,
clean_html(vevent.body),
vevent.location["displayName"],
uid=vevent.object_id,
)
if vevent.series_master_id:
event.recurrence_id = vevent.series_master_id
event_list.append(event)
except HomeAssistantError as err:
_LOGGER.warning(
"Invalid event found - Error: %s, Event: %s", err, vevent
)
return event_list
async def async_update(self, hass):
"""Do the update."""
start_of_day_utc = dt_util.as_utc(dt_util.start_of_local_day())
results = await self.async_o365_get_events(
hass,
start_of_day_utc,
start_of_day_utc + timedelta(days=1),
)
if not results:
_LOGGER.debug(
"No current event found for %s",
self._entity_id,
)
self.event = None
return
vevent = self._get_root_event(results)
if vevent is None:
_LOGGER.debug(
"No matching event found in the %d results for %s",
len(results),
self._entity_id,
)
self.event = None
return
try:
self.event = CalendarEvent(
get_hass_date(vevent.start, vevent.is_all_day),
get_hass_date(get_end_date(vevent), vevent.is_all_day),
vevent.subject,
clean_html(vevent.body),
vevent.location["displayName"],
)
self._error = False
except HomeAssistantError as err:
if not self._error:
_LOGGER.warning(
"Invalid event found - Error: %s, Event: %s", err, vevent
)
self._error = True
def _get_root_event(self, results):
started_event = None
not_started_event = None
all_day_event = None
for event in results:
if event.is_all_day:
if not all_day_event and not self.is_finished(event):
all_day_event = event
continue
if self.is_started(event) and not self.is_finished(event):
if not started_event:
started_event = event
continue
if (
not self.is_finished(event)
and not event.is_all_day
and not not_started_event
):
not_started_event = event
vevent = None
if started_event:
vevent = started_event
elif all_day_event:
vevent = all_day_event
elif not_started_event:
vevent = not_started_event
return vevent
@staticmethod
def is_all_day(vevent):
"""Is it all day."""
return vevent.is_all_day
@staticmethod
def is_started(vevent):
"""Is it over."""
return dt_util.utcnow() >= O365CalendarData.to_datetime(get_start_date(vevent))
@staticmethod
def is_finished(vevent):
"""Is it over."""
return dt_util.utcnow() >= O365CalendarData.to_datetime(get_end_date(vevent))
@staticmethod
def to_datetime(obj):
"""To datetime."""
if isinstance(obj, datetime):
date_obj = (
obj.replace(tzinfo=dt_util.get_default_time_zone())
if obj.tzinfo is None
else obj
)
elif isinstance(obj, date):
date_obj = dt_util.start_of_local_day(
dt_util.dt.datetime.combine(obj, dt_util.dt.time.min)
)
elif "date" in obj:
date_obj = dt_util.start_of_local_day(
dt_util.dt.datetime.combine(
dt_util.parse_date(obj["date"]), dt_util.dt.time.min
)
)
else:
date_obj = dt_util.as_local(dt_util.parse_datetime(obj["dateTime"]))
return dt_util.as_utc(date_obj)
class CalendarServices:
"""Calendar Services."""
def __init__(self, hass, account):
"""Initialise the calendar services."""
self._hass = hass
self._account = account
async def async_scan_for_calendars(self, call): # pylint: disable=unused-argument
"""Scan for new calendars."""
for config in self._hass.data[DOMAIN]:
config = self._hass.data[DOMAIN][config]
if CONF_ACCOUNT in config:
schedule = await self._hass.async_add_executor_job(
config[CONF_ACCOUNT].schedule
)
builder = QueryBuilder(protocol=self._account.protocol)
query = builder.select("name", "id", "canEdit", "color", "hexColor")
calendars = await self._hass.async_add_executor_job(
ft.partial(schedule.list_calendars, query=query, limit=50)
)
track = config.get(CONF_TRACK_NEW_CALENDAR, True)
for calendar in calendars:
await async_update_calendar_file(
config,
calendar,
self._hass,
track,
)
def _group_calendar_log(entity_id):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="o365_group_calendar_error",
translation_placeholders={
"entity_id": entity_id,
},
)
================================================
FILE: custom_components/o365/classes/__init__.py
================================================
"""Initialise."""
================================================
FILE: custom_components/o365/classes/entity.py
================================================
"""Generic O465 Sensor Entity."""
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from ..const import ATTR_DATA, CONF_PERMISSIONS, DOMAIN
class O365Entity(CoordinatorEntity):
"""O365 generic Sensor class."""
_attr_should_poll = False
_unrecorded_attributes = frozenset((ATTR_DATA,))
def __init__(self, coordinator, config, name, entity_id, entity_type, unique_id):
"""Initialise the O365 Sensor."""
super().__init__(coordinator)
self._config = config
self._name = name
self._entity_id = entity_id
self.entity_type = entity_type
self._unique_id = unique_id
@property
def name(self):
"""Name property."""
return self._name
@property
def entity_key(self):
"""Entity Key property."""
return self._entity_id
@property
def unique_id(self):
"""Entity unique id."""
return self._unique_id
def _validate_permissions(self, required_permission, required_permission_error):
if not self._config[CONF_PERMISSIONS].validate_authorization(
required_permission
):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="not_authorised",
translation_placeholders={
"required_permission": required_permission_error,
},
)
return True
================================================
FILE: custom_components/o365/classes/mailsensor.py
================================================
"""O365 mail sensors."""
import datetime
from operator import itemgetter
from homeassistant.components.sensor import SensorEntity
from O365 import mailbox # pylint: disable=no-name-in-module
from O365.utils.query import ( # pylint: disable=no-name-in-module, import-error
QueryBuilder,
)
from ..const import (
ATTR_AUTOREPLIESSETTINGS,
ATTR_DATA,
ATTR_END,
ATTR_EXTERNAL_AUDIENCE,
ATTR_EXTERNALREPLY,
ATTR_INTERNALREPLY,
ATTR_START,
ATTR_STATE,
CONF_ACCOUNT,
CONF_BODY_CONTAINS,
CONF_DOWNLOAD_ATTACHMENTS,
CONF_HAS_ATTACHMENT,
CONF_HTML_BODY,
CONF_IMPORTANCE,
CONF_IS_UNREAD,
CONF_MAIL_FROM,
CONF_SHOW_BODY,
CONF_SUBJECT_CONTAINS,
CONF_SUBJECT_IS,
DATETIME_FORMAT,
PERM_MAILBOX_SETTINGS,
SENSOR_AUTO_REPLY,
SENSOR_EMAIL,
)
from ..utils.utils import clean_html, get_email_attributes
from .entity import O365Entity
class O365MailSensor(O365Entity, SensorEntity):
"""O365 generic Mail Sensor class."""
_attr_translation_key = "mail"
def __init__(self, coordinator, config, sensor_conf, name, entity_id, unique_id):
"""Initialise the O365 Sensor."""
super().__init__(coordinator, config, name, entity_id, SENSOR_EMAIL, unique_id)
self._download_attachments = sensor_conf.get(CONF_DOWNLOAD_ATTACHMENTS)
self._html_body = sensor_conf.get(CONF_HTML_BODY)
self._show_body = sensor_conf.get(CONF_SHOW_BODY)
self._state = None
self._extra_attributes = None
self._update_status()
@property
def native_value(self):
"""Sensor state."""
return self._state
@property
def extra_state_attributes(self):
"""Device state attributes."""
return self._extra_attributes
def _handle_coordinator_update(self) -> None:
self._update_status()
self.async_write_ha_state()
def _update_status(self) -> None:
data = self.coordinator.data[self.entity_key][ATTR_DATA]
attrs = self._get_attributes(data)
attrs.sort(key=itemgetter("received"), reverse=True)
self._state = len(attrs)
self._extra_attributes = {ATTR_DATA: attrs}
def _get_attributes(self, data):
return [
get_email_attributes(
x, self._download_attachments, self._html_body, self._show_body
)
for x in data
]
class O365AutoReplySensor(O365Entity, SensorEntity):
"""O365 Auto Reply sensor processing."""
_attr_translation_key = "auto_reply"
def __init__(self, coordinator, name, entity_id, config, unique_id):
"""Initialise the Auto reply Sensor."""
super().__init__(
coordinator, config, name, entity_id, SENSOR_AUTO_REPLY, unique_id
)
self._config = config
self._account = self._config[CONF_ACCOUNT]
self.mailbox = None
async def async_init(self, hass):
"""async initialise."""
self.mailbox = await hass.async_add_executor_job(self._account.mailbox)
@property
def native_value(self):
"""Sensor state."""
return self.coordinator.data[self.entity_key][ATTR_STATE]
@property
def extra_state_attributes(self):
"""Return entity specific state attributes."""
ars = self.coordinator.data[self.entity_key][ATTR_AUTOREPLIESSETTINGS]
return {
ATTR_INTERNALREPLY: clean_html(ars.internal_reply_message),
ATTR_EXTERNALREPLY: clean_html(ars.external_reply_message),
ATTR_EXTERNAL_AUDIENCE: ars.external_audience.value,
ATTR_START: ars.scheduled_startdatetime.strftime(DATETIME_FORMAT),
ATTR_END: ars.scheduled_enddatetime.strftime(DATETIME_FORMAT),
}
async def async_auto_reply_enable(
self,
external_reply,
internal_reply,
start=None,
end=None,
external_audience=mailbox.ExternalAudience.ALL,
):
"""Enable out of office autoreply."""
if not self._validate_autoreply_permissions():
return
await self.hass.async_add_executor_job(
self.mailbox.set_automatic_reply,
internal_reply,
external_reply,
start,
end,
external_audience,
)
async def async_auto_reply_disable(self):
"""Disable out of office autoreply."""
if not self._validate_autoreply_permissions():
return
await self.hass.async_add_executor_job(self.mailbox.set_disable_reply)
def _validate_autoreply_permissions(self):
return self._validate_permissions(
PERM_MAILBOX_SETTINGS,
"Not authorised to update auto reply - requires permission: "
+ f"{PERM_MAILBOX_SETTINGS}",
)
async def _async_build_base_query(sensor_conf, builder):
"""Build base query for mail."""
download_attachments = sensor_conf.get(CONF_DOWNLOAD_ATTACHMENTS)
show_body = sensor_conf.get(CONF_SHOW_BODY)
html_body = sensor_conf.get(CONF_HTML_BODY)
query = builder.select(
"sender",
"from",
"subject",
"receivedDateTime",
"toRecipients",
"ccRecipients",
"has_attachments",
"importance",
"is_read",
"flag",
)
if show_body or html_body:
query = query & builder.select(
"body",
)
if download_attachments:
query = query & builder.select(
"attachments",
)
return query
async def async_build_inbox_query(sensor_conf, builder: QueryBuilder):
"""Build query for email sensor."""
query = await _async_build_base_query(sensor_conf, builder)
is_unread = sensor_conf.get(CONF_IS_UNREAD)
if is_unread is not None:
query = _add_to_query(
query, builder, "equals", "IsRead", not is_unread, is_unread
)
return query
async def async_build_query_query(sensor_conf, builder: QueryBuilder):
"""Build query for query sensor."""
query = await _async_build_base_query(sensor_conf, builder)
query = query & builder.orderby(("receivedDateTime", False))
body_contains = sensor_conf.get(CONF_BODY_CONTAINS)
subject_contains = sensor_conf.get(CONF_SUBJECT_CONTAINS)
subject_is = sensor_conf.get(CONF_SUBJECT_IS)
has_attachment = sensor_conf.get(CONF_HAS_ATTACHMENT)
importance = sensor_conf.get(CONF_IMPORTANCE)
email_from = sensor_conf.get(CONF_MAIL_FROM)
is_unread = sensor_conf.get(CONF_IS_UNREAD)
if (
body_contains is not None
or subject_contains is not None
or subject_is is not None
or has_attachment is not None
or importance is not None
or email_from is not None
or is_unread is not None
):
query = query & builder.greater_equal(
"receivedDateTime", datetime.datetime(1900, 5, 1)
)
query = _add_to_query(query, builder, "contains", "body", body_contains)
query = _add_to_query(query, builder, "contains", "subject", subject_contains)
query = _add_to_query(query, builder, "equals", "subject", subject_is)
query = _add_to_query(query, builder, "equals", "hasAttachments", has_attachment)
query = _add_to_query(query, builder, "equals", "from", email_from)
query = _add_to_query(query, builder, "equals", "IsRead", not is_unread, is_unread)
query = _add_to_query(query, builder, "equals", "importance", importance)
return query
def _add_to_query(
query,
builder: QueryBuilder,
qtype,
attribute_name,
attribute_value,
check_value=True,
):
if attribute_value is None or check_value is None:
return query
if qtype == "ge":
query = query & builder.greater_equal(attribute_name, attribute_value)
if qtype == "contains":
query = query & builder.contains(attribute_name, attribute_value)
if qtype == "equals":
query = query & builder.equals(attribute_name, attribute_value)
return query
================================================
FILE: custom_components/o365/classes/permissions.py
================================================
"""Permissions processes."""
import json
import logging
import os
from copy import deepcopy
from homeassistant.const import CONF_EMAIL, CONF_ENABLED
from ..const import (
CONF_ACCOUNT_NAME,
CONF_AUTO_REPLY_SENSORS,
CONF_BASIC_CALENDAR,
CONF_CHAT_SENSORS,
CONF_EMAIL_SENSORS,
CONF_ENABLE_CALENDAR,
CONF_ENABLE_UPDATE,
CONF_GROUPS,
CONF_QUERY_SENSORS,
CONF_SHARED_MAILBOX,
CONF_STATUS_SENSORS,
CONF_TODO_SENSORS,
CONST_CONFIG_TYPE_LIST,
O365_STORAGE_TOKEN,
PERM_BASE_PERMISSIONS,
PERM_CALENDARS_READ,
PERM_CALENDARS_READBASIC,
PERM_CALENDARS_READWRITE,
PERM_CHAT_READ,
PERM_CHAT_READWRITE,
PERM_GROUP_READ_ALL,
PERM_GROUP_READWRITE_ALL,
PERM_MAIL_READ,
PERM_MAIL_SEND,
PERM_MAILBOX_SETTINGS,
PERM_PRESENCE_READ,
PERM_PRESENCE_READ_ALL,
PERM_PRESENCE_READWRITE,
PERM_SHARED,
PERM_TASKS_READ,
PERM_TASKS_READWRITE,
PERM_USER_READBASIC_ALL,
TOKEN_FILE_CORRUPTED,
TOKEN_FILE_MISSING,
TOKEN_FILE_OUTDATED,
TOKEN_FILE_PERMISSIONS,
TOKEN_FILENAME,
)
from ..utils.filemgmt import build_config_file_path
_LOGGER = logging.getLogger(__name__)
class Permissions:
"""Class in support of building permssion sets."""
def __init__(self, hass, config, conf_type):
"""Initialise the class."""
self._hass = hass
self._config = config
self._conf_type = conf_type
self._shared = PERM_SHARED if config.get(CONF_SHARED_MAILBOX) else ""
self._enable_update = self._config.get(CONF_ENABLE_UPDATE, False)
self._requested_permissions = []
self.token_filename = self._build_token_filename()
self.token_path = build_config_file_path(self._hass, O365_STORAGE_TOKEN)
self._permissions = []
@property
def requested_permissions(self):
"""Return the required scope."""
if not self._requested_permissions:
self._requested_permissions = deepcopy(PERM_BASE_PERMISSIONS)
self._build_calendar_permissions()
self._build_group_permissions()
self._build_email_permissions()
self._build_autoreply_permissions()
self._build_status_permissions()
self._build_chat_permissions()
self._build_todo_permissions()
return self._requested_permissions
@property
def permissions(self):
"""Return the permission set."""
return self._permissions
async def async_check_authorizations(self):
"""Report on permissions status."""
self._permissions = await self._hass.async_add_executor_job(
self._get_permissions
)
if self._permissions in [
TOKEN_FILE_MISSING,
TOKEN_FILE_CORRUPTED,
TOKEN_FILE_OUTDATED,
]:
return self._permissions, None
failed_permissions = []
for permission in self.requested_permissions:
if not self.validate_authorization(permission):
failed_permissions.append(permission)
if failed_permissions:
_LOGGER.warning(
"Minimum required permissions: '%s'. Not available in token '%s' for account '%s'.",
", ".join(failed_permissions),
self.token_filename,
self._config[CONF_ACCOUNT_NAME],
)
return TOKEN_FILE_PERMISSIONS, failed_permissions
return True, None
def validate_authorization(self, permission):
"""Validate higher permissions."""
if permission in self.permissions:
return True
if self._check_higher_permissions(permission):
return True
resource = permission.split(".")[0]
constraint = permission.split(".")[1] if len(permission) == 3 else None
# If Calendar or Mail Resource then permissions can have a constraint of .Shared
# which includes base as well. e.g. Calendar.Read is also enabled by Calendar.Read.Shared
if not constraint and resource in ["Calendar", "Mail"]:
sharedpermission = f"{deepcopy(permission)}.Shared"
return self._check_higher_permissions(sharedpermission)
# If Presence Resource then permissions can have a constraint of .All
# which includes base as well. e.g. Presencedar.Read is also enabled by Presence.Read.All
if not constraint and resource in ["Presence"]:
allpermission = f"{deepcopy(permission)}.All"
return self._check_higher_permissions(allpermission)
return False
def _check_higher_permissions(self, permission):
operation = permission.split(".")[1]
# If Operation is ReadBasic then Read or ReadWrite will also work
# If Operation is Read then ReadWrite will also work
newops = [operation]
if operation == "ReadBasic":
newops = newops + ["Read", "ReadWrite"]
elif operation == "Read":
newops = newops + ["ReadWrite"]
for newop in newops:
newperm = deepcopy(permission).replace(operation, newop)
if newperm in self.permissions:
return True
return False
def _build_token_filename(self):
"""Create the token file name."""
config_file = (
f"_{self._config.get(CONF_ACCOUNT_NAME)}"
if self._conf_type == CONST_CONFIG_TYPE_LIST
else ""
)
return TOKEN_FILENAME.format(config_file)
def _get_permissions(self):
"""Get the permissions from the token file."""
full_token_path = os.path.join(self.token_path, self.token_filename)
if not os.path.exists(full_token_path) or not os.path.isfile(full_token_path):
_LOGGER.warning("Could not locate token at %s", full_token_path)
return TOKEN_FILE_MISSING
try:
with open(full_token_path, "r", encoding="UTF-8") as file_handle:
raw = file_handle.read()
permissions = next(iter(json.loads(raw)["AccessToken"].values()))[
"target"
].split()
except json.decoder.JSONDecodeError as err:
_LOGGER.warning("Token corrupted at %s - %s", full_token_path, err)
return TOKEN_FILE_CORRUPTED
except KeyError:
_LOGGER.warning(
"Legacy token found at %s, it has been deleted", full_token_path
)
self.delete_token()
return TOKEN_FILE_OUTDATED
return permissions
def _build_calendar_permissions(self):
if not self._config.get(CONF_ENABLE_CALENDAR, True):
return
if self._config.get(CONF_BASIC_CALENDAR, False):
if self._enable_update:
_LOGGER.warning(
"'enable_update' should not be true when 'basic_calendar' is true ."
+ "for account: %s ReadBasic used. ",
self._config[CONF_ACCOUNT_NAME],
)
self._requested_permissions.append(PERM_CALENDARS_READBASIC + self._shared)
elif self._enable_update:
self._requested_permissions.extend(
(PERM_MAIL_SEND + self._shared, PERM_CALENDARS_READWRITE + self._shared)
)
else:
self._requested_permissions.append(PERM_CALENDARS_READ + self._shared)
def _build_group_permissions(self):
if self._config.get(CONF_GROUPS, False):
if self._enable_update:
self._requested_permissions.append(PERM_GROUP_READWRITE_ALL)
else:
self._requested_permissions.append(PERM_GROUP_READ_ALL)
def _build_email_permissions(self):
email_sensors = self._config.get(CONF_EMAIL_SENSORS, [])
query_sensors = self._config.get(CONF_QUERY_SENSORS, [])
if len(email_sensors) > 0 or len(query_sensors) > 0:
self._requested_permissions.append(PERM_MAIL_READ + self._shared)
def _build_autoreply_permissions(self):
auto_reply_sensors = self._config.get(CONF_AUTO_REPLY_SENSORS, [])
if len(auto_reply_sensors) > 0:
self._requested_permissions.append(PERM_MAILBOX_SETTINGS)
def _build_status_permissions(self):
status_sensors = self._config.get(CONF_STATUS_SENSORS, [])
if len(status_sensors) > 0:
if any(
status_sensor.get(CONF_ENABLE_UPDATE)
for status_sensor in status_sensors
):
self._requested_permissions.append(PERM_PRESENCE_READWRITE)
else:
self._requested_permissions.append(PERM_PRESENCE_READ)
if any(status_sensor.get(CONF_EMAIL) for status_sensor in status_sensors):
self._requested_permissions.append(PERM_PRESENCE_READ_ALL)
self._requested_permissions.append(PERM_USER_READBASIC_ALL)
def _build_chat_permissions(self):
chat_sensors = self._config.get(CONF_CHAT_SENSORS, [])
if len(chat_sensors) > 0:
if chat_sensors[0][CONF_ENABLE_UPDATE]:
self._requested_permissions.append(PERM_CHAT_READWRITE)
else:
self._requested_permissions.append(PERM_CHAT_READ)
def _build_todo_permissions(self):
todo_sensors = self._config.get(CONF_TODO_SENSORS, [])
if todo_sensors and todo_sensors.get(CONF_ENABLED, False):
if todo_sensors[CONF_ENABLE_UPDATE]:
self._requested_permissions.append(PERM_TASKS_READWRITE)
else:
self._requested_permissions.append(PERM_TASKS_READ)
def delete_token(self):
"""Delete the token."""
full_token_path = os.path.join(self.token_path, self.token_filename)
if os.path.exists(full_token_path):
os.remove(full_token_path)
================================================
FILE: custom_components/o365/classes/teamssensor.py
================================================
"""O365 teams sensors."""
import functools as ft
import logging
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import ATTR_NAME, CONF_EMAIL
from homeassistant.exceptions import ServiceValidationError
from O365.teams import ( # pylint: disable=import-error, no-name-in-module
PreferredActivity,
PreferredAvailability,
)
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_PREFERRED_STATUS,
EVENT_UPDATE_USER_STATUS,
PERM_CHAT_READWRITE,
PERM_PRESENCE_READWRITE,
SENSOR_TEAMS_CHAT,
SENSOR_TEAMS_STATUS,
)
from .entity import O365Entity
_LOGGER = logging.getLogger(__name__)
class O365TeamsSensor(O365Entity):
"""O365 Teams sensor processing."""
_attr_translation_key = "teams"
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 native_value(self):
"""Sensor state."""
return self.coordinator.data[self.entity_key][ATTR_STATE]
class O365TeamsStatusSensor(O365TeamsSensor, SensorEntity):
"""O365 Teams sensor processing."""
def __init__(self, coordinator, name, entity_id, config, unique_id, email):
"""Initialise the Teams Sensor."""
super().__init__(
coordinator,
name,
entity_id,
config,
SENSOR_TEAMS_STATUS,
unique_id,
)
self._email = email
async def async_update_user_status(
self, availability, activity, expiration_duration=None
):
"""Update the users teams status."""
if self._email:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="not_possible",
translation_placeholders={
CONF_EMAIL: self._email,
},
)
if not self._validate_status_permissions():
return False
status = await self.hass.async_add_executor_job(
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
async def async_update_user_preferred_status(
self, availability, expiration_duration=None
):
"""Update the users teams status."""
if self._email:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="not_possible",
translation_placeholders={
CONF_EMAIL: self._email,
},
)
if not self._validate_status_permissions():
return False
activity = (
availability
if availability != PreferredAvailability.OFFLINE
else PreferredActivity.OFFWORK
)
status = await self.hass.async_add_executor_job(
self.teams.set_my_user_preferred_presence,
availability,
activity,
expiration_duration,
)
self._raise_event(
EVENT_UPDATE_USER_PREFERRED_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_PRESENCE_READWRITE,
f"Not authorised to update status - requires permission: {PERM_PRESENCE_READWRITE}",
)
class O365TeamsChatSensor(O365TeamsSensor, SensorEntity):
"""O365 Teams Chat sensor processing."""
def __init__(self, coordinator, name, entity_id, config, unique_id):
"""Initialise the Teams Chat Sensor."""
super().__init__(
coordinator, name, entity_id, config, SENSOR_TEAMS_CHAT, unique_id
)
@property
def extra_state_attributes(self):
"""Return entity specific state attributes."""
attributes = {
ATTR_FROM_DISPLAY_NAME: self.coordinator.data[self.entity_key][
ATTR_FROM_DISPLAY_NAME
],
ATTR_CONTENT: self.coordinator.data[self.entity_key][ATTR_CONTENT],
ATTR_CHAT_ID: self.coordinator.data[self.entity_key][ATTR_CHAT_ID],
ATTR_IMPORTANCE: self.coordinator.data[self.entity_key][ATTR_IMPORTANCE],
}
if self.coordinator.data[self.entity_key][ATTR_SUBJECT]:
attributes[ATTR_SUBJECT] = self.coordinator.data[self.entity_key][
ATTR_SUBJECT
]
if self.coordinator.data[self.entity_key][ATTR_SUMMARY]:
attributes[ATTR_SUMMARY] = self.coordinator.data[self.entity_key][
ATTR_SUMMARY
]
if self.coordinator.data[self.entity_key][ATTR_DATA]:
attributes[ATTR_DATA] = self.coordinator.data[self.entity_key][ATTR_DATA]
return attributes
async def async_send_chat_message(self, chat_id, message, content_type):
"""Send a message to the specified chat."""
if not self._validate_chat_permissions():
return False
chats = await self.hass.async_add_executor_job(self.teams.get_my_chats)
for chat in chats:
if chat.object_id == chat_id:
message = await self.hass.async_add_executor_job(
ft.partial(
chat.send_message, content=message, content_type=content_type
)
)
self._raise_event(EVENT_SEND_CHAT_MESSAGE, chat_id)
return True
_LOGGER.warning("Chat %s not found for send message", chat_id)
return False
def _raise_event(self, event_type, chat_id):
self.hass.bus.fire(
f"{DOMAIN}_{event_type}",
{ATTR_CHAT_ID: chat_id, EVENT_HA_EVENT: True},
)
_LOGGER.debug("%s - %s", event_type, chat_id)
def _validate_chat_permissions(self):
return self._validate_permissions(
PERM_CHAT_READWRITE,
f"Not authorised to send message - requires permission: {PERM_CHAT_READWRITE}",
)
================================================
FILE: custom_components/o365/const.py
================================================
"""Constants."""
from enum import Enum
class EventResponse(Enum):
"""Event response."""
Accept = "accept" # pylint: disable=invalid-name
Tentative = "tentative" # pylint: disable=invalid-name
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"
ATTR_CHAT_TYPE = "chat_type"
ATTR_COMPLETED = "completed"
ATTR_CONTENT_TYPE = "content_type"
ATTR_CREATED = "created"
ATTR_COLOR = "color"
ATTR_CONTENT = "content"
ATTR_DATA = "data"
ATTR_DESCRIPTION = "description"
ATTR_DUE = "due"
ATTR_EMAIL = "email"
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"
ATTR_HEX_COLOR = "hex_color"
ATTR_IS_ALL_DAY = "is_all_day"
ATTR_IMPORTANCE = "importance"
ATTR_INTERNALREPLY = "internal_reply"
ATTR_LOCATION = "location"
ATTR_MEMBERS = "members"
ATTR_MESSAGE_IS_HTML = "message_is_html"
ATTR_OFFSET = "offset_reached"
ATTR_OVERDUE_TODOS = "overdue_todos"
ATTR_PHOTOS = "photos"
ATTR_REMINDER = "reminder"
ATTR_RESPONSE = "response"
ATTR_RRULE = "rrule"
ATTR_SENDER = "sender"
ATTR_SEND_RESPONSE = "send_response"
ATTR_SENSITIVITY = "sensitivity"
ATTR_SHOW_AS = "show_as"
ATTR_START = "start"
ATTR_STATE = "state"
ATTR_STATUS = "status"
ATTR_SUBJECT = "subject"
ATTR_SUMMARY = "summary"
ATTR_TODOS = "todos"
ATTR_TODO_ID = "todo_id"
ATTR_TOPIC = "topic"
ATTR_TYPE = "type"
ATTR_ZIP_ATTACHMENTS = "zip_attachments"
ATTR_ZIP_NAME = "zip_name"
AUTH_CALLBACK_NAME = "api:o365"
AUTH_CALLBACK_PATH_ALT = "/api/o365"
AUTH_CALLBACK_PATH_DEFAULT = (
"https://login.microsoftonline.com/common/oauth2/nativeclient"
)
CALENDAR_ENTITY_ID_FORMAT = "calendar.{}"
CONF_ACCOUNT = "account"
CONF_ACCOUNTS = "accounts"
CONF_ACCOUNT_CONF = "account_conf"
CONF_ACCOUNT_NAME = "account_name"
CONF_ALT_AUTH_METHOD = "alt_auth_method"
CONF_AUTH_URL = "auth_url"
CONF_AUTO_REPLY_SENSORS = "auto_reply_sensors"
CONF_BASIC_CALENDAR = "basic_calendar"
CONF_BODY_CONTAINS = "body_contains"
CONF_CAL_ID = "cal_id"
CONF_CAL_IDS = "cal_ids"
CONF_CHAT_SENSORS = "chat_sensors"
CONF_CLIENT_ID = "client_id"
CONF_CLIENT_SECRET = "client_secret" # nosec
CONF_CONFIG_TYPE = "config_type"
CONF_COORDINATOR_EMAIL = "coordinator_email"
CONF_COORDINATOR_SENSORS = "coordinator_sensors"
CONF_DEVICE_ID = "device_id"
CONF_DOWNLOAD_ATTACHMENTS = "download_attachments"
CONF_DUE_HOURS_BACKWARD_TO_GET = "due_start_offset"
CONF_DUE_HOURS_FORWARD_TO_GET = "due_end_offset"
CONF_EMAIL_ACCOUNT = "email_account"
CONF_EMAIL_SENSORS = "email_sensor"
CONF_ENABLE_CALENDAR = "enable_calendar"
CONF_ENABLE_UPDATE = "enable_update"
CONF_ENTITIES = "entities"
CONF_ENTITY_KEY = "entity_key"
CONF_ENTITY_TYPE = "entity_type"
CONF_EXCLUDE = "exclude"
CONF_FAILED_PERMISSIONS = "failed_permissions"
CONF_GROUPS = "groups"
CONF_HAS_ATTACHMENT = "has_attachment"
CONF_HOURS_BACKWARD_TO_GET = "start_offset"
CONF_HOURS_FORWARD_TO_GET = "end_offset"
CONF_HTML_BODY = "html_body"
CONF_SHOW_BODY = "show_body"
CONF_IMPORTANCE = "importance"
CONF_IS_AUTHENTICATED = "is_authenticated"
CONF_IS_UNREAD = "is_unread"
CONF_KEYS_EMAIL = "keys_email"
CONF_KEYS_SENSORS = "keys_sensors"
CONF_MAIL_FOLDER = "folder"
CONF_MAIL_FROM = "from"
CONF_MAX_ITEMS = "max_items"
CONF_MAX_RESULTS = "max_results"
CONF_O365_MAIL_FOLDER = "mail_folder"
CONF_PERMISSIONS = "permissions"
CONF_QUERY = "query"
CONF_QUERY_SENSORS = "query_sensors"
CONF_SEARCH = "search"
CONF_SENSOR_CONF = "sensor_conf"
CONF_SHARED_MAILBOX = "shared_mailbox"
CONF_SHOW_COMPLETED = "show_completed"
CONF_STATUS_SENSORS = "status_sensors"
CONF_SUBJECT_CONTAINS = "subject_contains"
CONF_SUBJECT_IS = "subject_is"
CONF_O365_TASK_FOLDER = "O365_task_folder"
CONF_TODO_SENSORS = "todo_sensors"
CONF_TRACK = "track"
CONF_TRACK_NEW_CALENDAR = "track_new_calendar"
CONF_TRACK_NEW = "track_new"
CONF_YAML_TASK_LIST_ID = "task_list_id"
CONF_YAML_TASK_LIST = "yaml_task_list"
CONF_URL = "url"
CONST_CONFIG_TYPE_LIST = "list"
CONST_GROUP = "group:"
CONST_PRIMARY = "$o365-primary$"
CONST_UTC_TIMEZONE = "UTC"
CONTENT_TYPES = ["text", "html"]
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
DEFAULT_OFFSET = "!!"
DOMAIN = "o365"
ENTITY_ID_FORMAT_SENSOR = "sensor.{}"
ENTITY_ID_FORMAT_TODO = "todo.{}"
EVENT_HA_EVENT = "ha_event"
EVENT_COMPLETED_TODO = "completed_todo"
EVENT_DELETE_TODO = "delete_todo"
EVENT_NEW_TODO = "new_todo"
EVENT_UNCOMPLETED_TODO = "uncompleted_todo"
EVENT_UPDATE_TODO = "update_todo"
EVENT_CREATE_CALENDAR_EVENT = "create_calendar_event"
EVENT_MODIFY_CALENDAR_EVENT = "modify_calendar_event"
EVENT_MODIFY_CALENDAR_RECURRENCES = "modify_calendar_recurrences"
EVENT_REMOVE_CALENDAR_EVENT = "remove_calendar_event"
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"
EVENT_UPDATE_USER_PREFERRED_STATUS = "update_user_preferred_status"
LEGACY_ACCOUNT_NAME = "converted"
O365_STORAGE = "o365_storage"
O365_STORAGE_TOKEN = ".O365-token-cache"
PERM_CALENDARS_READ = "Calendars.Read"
PERM_CALENDARS_READBASIC = "Calendars.ReadBasic"
PERM_CALENDARS_READWRITE = "Calendars.ReadWrite"
PERM_CHAT_READ = "Chat.Read"
PERM_CHAT_READWRITE = "Chat.ReadWrite"
PERM_GROUP_READ_ALL = "Group.Read.All"
PERM_GROUP_READWRITE_ALL = "Group.ReadWrite.All"
PERM_MAILBOX_SETTINGS = "MailboxSettings.ReadWrite"
PERM_MAIL_READ = "Mail.Read"
PERM_MAIL_SEND = "Mail.Send"
PERM_PRESENCE_READ = "Presence.Read"
PERM_PRESENCE_READ_ALL = "Presence.Read.All"
PERM_PRESENCE_READWRITE = "Presence.ReadWrite"
PERM_TASKS_READ = "Tasks.Read"
PERM_TASKS_READWRITE = "Tasks.ReadWrite"
PERM_USER_READ = "User.Read"
PERM_USER_READBASIC_ALL = "User.ReadBasic.All"
PERM_SHARED = ".Shared"
PERM_BASE_PERMISSIONS = [PERM_USER_READ]
SENSOR_AUTO_REPLY = "auto_reply"
SENSOR_EMAIL = "inbox"
SENSOR_TEAMS_STATUS = "teams_status"
SENSOR_TEAMS_CHAT = "teams_chat"
TODO_TODO = "todo"
TOKEN_FILENAME = "o365{0}.token" # nosec
TOKEN_FILE_CORRUPTED = "corrupted"
TOKEN_FILE_MISSING = "missing"
TOKEN_FILE_OUTDATED = "outdated"
TOKEN_FILE_PERMISSIONS = "permissions"
YAML_CALENDARS_FILENAME = "{0}_calendars{1}.yaml"
YAML_TASK_LISTS_FILENAME = "{0}_tasks{1}.yaml"
DAYS = {
"MO": "monday",
"TU": "tuesday",
"WE": "wednesday",
"TH": "thursday",
"FR": "friday",
"SA": "saturday",
"SU": "sunday",
}
INDEXES = {
"+1": "first",
"+2": "second",
"+3": "third",
"+4": "fourth",
"-1": "last",
}
================================================
FILE: custom_components/o365/helpers/__init__.py
================================================
"""Initialise."""
================================================
FILE: custom_components/o365/helpers/coordinator.py
================================================
"""Sensor processing."""
import functools as ft
import logging
from datetime import datetime, timedelta
from homeassistant.const import CONF_EMAIL, CONF_ENABLED, CONF_NAME, CONF_UNIQUE_ID
from homeassistant.helpers import entity_registry
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util import dt as dt_util
from requests.exceptions import HTTPError
from O365.utils.query import ( # pylint: disable=no-name-in-module, import-error # pylint: disable=no-name-in-module, import-error
QueryBuilder,
)
from ..classes.mailsensor import async_build_inbox_query, async_build_query_query
from ..const import (
ATTR_AUTOREPLIESSETTINGS,
ATTR_CHAT_ID,
ATTR_CHAT_TYPE,
ATTR_CONTENT,
ATTR_DATA,
ATTR_ERROR,
ATTR_FROM_DISPLAY_NAME,
ATTR_IMPORTANCE,
ATTR_MEMBERS,
ATTR_STATE,
ATTR_SUBJECT,
ATTR_SUMMARY,
ATTR_TODOS,
ATTR_TOPIC,
CONF_ACCOUNT,
CONF_ACCOUNT_NAME,
CONF_AUTO_REPLY_SENSORS,
CONF_CHAT_SENSORS,
CONF_DOWNLOAD_ATTACHMENTS,
CONF_EMAIL_ACCOUNT,
CONF_EMAIL_SENSORS,
CONF_ENABLE_UPDATE,
CONF_ENTITY_KEY,
CONF_ENTITY_TYPE,
CONF_MAIL_FOLDER,
CONF_MAX_ITEMS,
CONF_O365_MAIL_FOLDER,
CONF_O365_TASK_FOLDER,
CONF_QUERY,
CONF_QUERY_SENSORS,
CONF_SENSOR_CONF,
CONF_STATUS_SENSORS,
CONF_TODO_SENSORS,
CONF_TRACK,
CONF_YAML_TASK_LIST,
CONF_YAML_TASK_LIST_ID,
DOMAIN,
ENTITY_ID_FORMAT_SENSOR,
ENTITY_ID_FORMAT_TODO,
LEGACY_ACCOUNT_NAME,
SENSOR_AUTO_REPLY,
SENSOR_EMAIL,
SENSOR_TEAMS_CHAT,
SENSOR_TEAMS_STATUS,
TODO_TODO,
YAML_TASK_LISTS_FILENAME,
)
from ..schema import YAML_TASK_LIST_SCHEMA
from ..todo import O365TodoEntityServices, async_build_todo_query
from ..utils.filemgmt import build_config_file_path, build_yaml_filename, load_yaml_file
_LOGGER = logging.getLogger(__name__)
class O365SensorCordinator(DataUpdateCoordinator):
"""O365 sensor data update coordinator."""
def __init__(self, hass, config):
"""Initialize my coordinator."""
super().__init__(
hass,
_LOGGER,
# Name of the data. For logging purposes.
name="O365 Sensors",
# Polling interval. Will only be polled if there are subscribers.
update_interval=timedelta(seconds=30),
)
self._config = config
self._account = config[CONF_ACCOUNT]
self._account_name = config[CONF_ACCOUNT_NAME]
self._keys = []
self._data = {}
self._zero_date = datetime(
1, 1, 1, 0, 0, 0, tzinfo=dt_util.get_default_time_zone()
)
self._chat_members = {}
self._ent_reg = entity_registry.async_get(hass)
self._builder = QueryBuilder(protocol=self._account.protocol)
async def async_setup_entries(self):
"""Do the initial setup of the entities."""
status_keys = await self._async_status_sensors()
chat_keys = self._chat_sensors()
todo_keys = await self._async_todo_sensors()
auto_reply_entities = await self._async_auto_reply_sensors()
self._keys = chat_keys + status_keys + todo_keys + auto_reply_entities
return self._keys
async def _async_status_sensors(self):
status_sensors = self._config.get(CONF_STATUS_SENSORS, [])
keys = []
for sensor_conf in status_sensors:
name = sensor_conf.get(CONF_NAME)
new_key = {
CONF_ENTITY_KEY: _build_entity_id(
self.hass, ENTITY_ID_FORMAT_SENSOR, name
),
CONF_UNIQUE_ID: f"{name}_{self._account_name}",
CONF_NAME: name,
CONF_ENTITY_TYPE: SENSOR_TEAMS_STATUS,
CONF_EMAIL: sensor_conf.get(CONF_EMAIL),
}
if sensor_conf.get(CONF_EMAIL):
email_account = await self.hass.async_add_executor_job(
self._account.directory().get_user,
sensor_conf.get(CONF_EMAIL),
)
new_key[CONF_EMAIL_ACCOUNT] = email_account.object_id
keys.append(new_key)
return keys
def _chat_sensors(self):
chat_sensors = self._config.get(CONF_CHAT_SENSORS, [])
keys = []
for sensor_conf in chat_sensors:
name = sensor_conf.get(CONF_NAME)
new_key = {
CONF_ENTITY_KEY: _build_entity_id(
self.hass, ENTITY_ID_FORMAT_SENSOR, name
),
CONF_UNIQUE_ID: f"{name}_{self._account_name}",
CONF_NAME: name,
CONF_ENTITY_TYPE: SENSOR_TEAMS_CHAT,
CONF_ENABLE_UPDATE: sensor_conf.get(CONF_ENABLE_UPDATE),
}
keys.append(new_key)
return keys
async def _async_todo_sensors(self):
todo_sensors = self._config.get(CONF_TODO_SENSORS)
keys = []
if todo_sensors and todo_sensors.get(CONF_ENABLED):
sensor_services = O365TodoEntityServices(self.hass)
await sensor_services.async_scan_for_todo_lists(None)
yaml_filename = build_yaml_filename(self._config, YAML_TASK_LISTS_FILENAME)
yaml_filepath = build_config_file_path(self.hass, yaml_filename)
o365_task_dict = await self.hass.async_add_executor_job(
load_yaml_file,
yaml_filepath,
CONF_YAML_TASK_LIST_ID,
YAML_TASK_LIST_SCHEMA,
)
o365_task_lists = list(o365_task_dict.values())
keys = await self._async_todo_entities(o365_task_lists)
return keys
async def _async_todo_entities(self, o365_task_lists):
keys = []
o365_tasks = await self.hass.async_add_executor_job(self._account.tasks)
for o365_tasklist in o365_task_lists:
track = o365_tasklist.get(CONF_TRACK)
if not track:
continue
o365_task_list_id = o365_tasklist.get(CONF_YAML_TASK_LIST_ID)
if self._account_name != LEGACY_ACCOUNT_NAME:
name = f"{o365_tasklist.get(CONF_NAME)} {self._account_name}"
else:
name = o365_tasklist.get(CONF_NAME)
try:
o365_task = await self.hass.async_add_executor_job( # pylint: disable=no-member
ft.partial(
o365_tasks.get_folder,
folder_id=o365_task_list_id,
)
)
unique_id = f"{o365_task_list_id}_{self._account_name}"
new_key = {
CONF_ENTITY_KEY: _build_entity_id(
self.hass, ENTITY_ID_FORMAT_TODO, name
),
CONF_UNIQUE_ID: unique_id,
CONF_O365_TASK_FOLDER: o365_task,
CONF_NAME: name,
CONF_YAML_TASK_LIST: o365_tasklist,
CONF_ENTITY_TYPE: TODO_TODO,
}
keys.append(new_key)
# To be deleted in mid 2024 after majority have migrated
# to HA 2023.11 and O365 version 4.5
await _async_delete_redundant_sensors(self._ent_reg, unique_id)
except HTTPError:
_LOGGER.warning(
"O365 Task list not found for: %s - Please remove from O365_tasks_%s.yaml",
name,
self._account_name,
)
return keys
async def _async_auto_reply_sensors(self):
auto_reply_sensors = self._config.get(CONF_AUTO_REPLY_SENSORS, [])
keys = []
for sensor_conf in auto_reply_sensors:
name = sensor_conf.get(CONF_NAME)
new_key = {
CONF_ENTITY_KEY: _build_entity_id(
self.hass, ENTITY_ID_FORMAT_SENSOR, name
),
CONF_UNIQUE_ID: f"{name}_{self._account_name}",
CONF_NAME: name,
CONF_ENTITY_TYPE: SENSOR_AUTO_REPLY,
}
keys.append(new_key)
return keys
async def _async_update_data(self):
_LOGGER.debug(
"Doing %s sensor update(s) for: %s", len(self._keys), self._account_name
)
for key in self._keys:
entity_type = key[CONF_ENTITY_TYPE]
_LOGGER.debug("%s for: %s", entity_type, self._account_name)
if entity_type == TODO_TODO:
await self._async_todos_update(key)
elif entity_type == SENSOR_TEAMS_CHAT:
await self._async_teams_chat_update(key)
elif entity_type == SENSOR_TEAMS_STATUS:
await self._async_teams_status_update(key)
elif entity_type == SENSOR_AUTO_REPLY:
await self._async_auto_reply_update(key)
return self._data
async def _async_teams_status_update(self, key):
"""Update state."""
entity_key = key[CONF_ENTITY_KEY]
email_account = key.get(CONF_EMAIL_ACCOUNT)
if not email_account:
if data := await self.hass.async_add_executor_job(
self._account.teams().get_my_presence
):
self._data[entity_key] = {ATTR_STATE: data.activity}
return
if data := await self.hass.async_add_executor_job(
self._account.teams().get_user_presence, email_account
):
self._data[entity_key] = {ATTR_STATE: data.activity}
async def _async_teams_chat_update(self, key):
entity_key = key[CONF_ENTITY_KEY]
state = None
data = []
self._data[entity_key] = {}
extra_attributes = {}
chats = await self.hass.async_add_executor_job(
ft.partial(self._account.teams().get_my_chats, limit=20)
)
for chat in chats:
if chat.chat_type == "unknownFutureValue":
continue
if not state:
messages = await self.hass.async_add_executor_job(
ft.partial(chat.get_messages, limit=10)
)
state, extra_attributes = self._process_chat_messages(messages)
if not key[CONF_ENABLE_UPDATE]:
if state:
break
continue
memberlist = await self._async_get_memberlist(chat)
chatitems = {
ATTR_CHAT_ID: chat.object_id,
ATTR_CHAT_TYPE: chat.chat_type,
ATTR_MEMBERS: ",".join(memberlist),
}
if chat.chat_type == "group":
chatitems[ATTR_TOPIC] = chat.topic
data.append(chatitems)
self._data[entity_key] = (
{ATTR_STATE: state} | extra_attributes | {ATTR_DATA: data}
)
def _process_chat_messages(self, messages):
state = None
extra_attributes = {}
for message in messages:
if not state and message.content != "