master e1a84431db5a cached
57 files
336.1 KB
91.4k tokens
244 symbols
1 requests
Download .txt
Showing preview only (355K chars total). Download the full file or copy to clipboard to get everything.
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
<details>
  <summary>Changes 2023</summary>

## 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

</details>
<details>
  <summary>Changes 2022</summary>

## 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`

<details>
  <summary>Full list of changes</summary>
- [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
</details>

## v4.0.3 (2022/12/08)
### Enhancements
- [Add reminder to tasks, and shorten due to just date](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6d4adc8ac65021afa3361c9a749772fca5caeffe) - @RogerSelwyn

### Maintenance
- [Bump to v4.0.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4214142f776aa9d20938a6ff9bdfaa378e9a94be) - @RogerSelwyn
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d367c573d6ccfe8a1fa0cd1fd8291b0ad797da25) - @actions-user

## v4.0.2 (2022/12/07)
### Fixes
- [Fix issue with converted config creation](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/df4c83e4e3f5f4c745cc0b723531501851e4991f) - @RogerSelwyn
- [Fix issue with downloading attachments](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5b90d62ea90734de323f050ffe361450ef986464) - @RogerSelwyn 

### Maintenance
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7379ba32cd1a9f1fb885f151666560a9f71ba983) - @RogerSelwyn
- [Update index.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d98c8e017e9a48735a258acf39d36825e22d8ee5) - @RogerSelwyn
- [Bump to v4.0.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4671b763d721273159dca8c4058f2ba02a65af26) - @RogerSelwyn

## v4.0.1 (2022/12/03)
#### Breaking Changes
- `alt_auth_flow` has been removed as a valid configuration parameter. This has been deprecated for 6 months. See [here](https://rogerselwyn.github.io/O365-HomeAssistant/authentication.html) for details of how to configure `alt_auth_method` to meet your needs.
- `calendar_id` is no longer supported as a parameter in service calls. `entity_id` should be used instead. Overall the changes to service calls in this release improve validation and should make it clearer when calling the service as to what a problem might be if one occurs. It also significantly simplifies the code which will benefit future changes.
- The location of the o365 token and o365_calendar.yaml files have been moved under the `o365_storage` directory. This helps to group the various o365 files in one place. If you are backing up your configuration to a public GitHub, you will need to change your `.gitignore`.

### Deprecations
- The Secondary/Legacy method of configuration has been marked as deprecated and will be removed in a future release. See [here](https://rogerselwyn.github.io/O365-HomeAssistant/legacy_migration.html) for more details on how to perform the migration to the Primary method.

### Enhancements
- Meaningful icons have been added to all sensors. Thanks to @rdeveen for prompting the change.
- Tasks/Todo sensors can be enabled. See [Configuration](https://rogerselwyn.github.io/O365-HomeAssistant/installation_and_configuration.html) for details.

### Fixes
- [Fix folder parameter usage](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8b5c7b982aca10c9bcf09a0f0fd6f9523c66ea4e) - @RogerSelwyn 
- [Fix incorrect service name created after conversion](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/41ccbdeebbe4ed700aa582de3be05cbca25ce22c) - @RogerSelwyn

<details>
  <summary>Full list of changes</summary>

- [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
</details>



## v3.3.0 (2022/11/10)
### Enhancements
- [Add ability to read group calendars](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6ba872fea5478bc0727abf559585ca6950039e3d) - @RogerSelwyn

### Maintenance
- [Bump to v3.3.0 Alpha 1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4e9828cc53080da96f247b363da85c53af734147) - @RogerSelwyn
- [Sourcery code recommendations](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f3d5391c522932a746c4e2f4db23e5b5c7338a05) - @RogerSelwyn
- [Sourcery code improvements](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1be5c9ddec9572d79dd2f80740f360ff27e37863) - @RogerSelwyn
- [Bump to v3.3.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b25edce41358d3ca2b6dce0aba21d4e8937ee6de) - @RogerSelwyn

## v3.2.3 (2022/11/05)
### Enhancements
- [Add ability to send for delegated user](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b9c18bc4918415085f092089321cfae76d2bf501) - @RogerSelwyn

### Maintenance
- [Update installation_and_configuration.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/53457035eec5bf0b586d17ff7ea934875ab57f34) - @RogerSelwyn
- [Update errors.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/52b12fc9a2f6433d056535285cbf6987ad2c8fa0) - @PuffinRub
- [Bump to v.3.2.3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/465d2143843e8a5ee9da7ba202f879c9781cf96c) - @RogerSelwyn

## v3.2.2 (2022/09/26)
### Enhancements
- [Moved documentation to GitHub page](https://rogerselwyn.github.io/O365-HomeAssistant/) - @RogerSelwyn
- [Return line breaks where available](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8f445edfaf7edec48e887ca5f44d730e6525b133) - @RogerSelwyn

### Maintenance
- [Make account type not optional](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e8135254ec6cc0499fcf18c7a2dc9e52268173df) - @spookyuser
- [Bump o365 to 2.0.20](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/63569ea4ed7a4481678da6a7f70e50e56b8e4400) - @RogerSelwyn
- [Code cleanup](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1c9086554daf5177094271164974e1822d22c753) - @RogerSelwyn
- [Bump o365 module to 2.0.21](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/174b5e2501055188d7ff09414b63a9d71a311c70) - @RogerSelwyn
- [Bump to v3.2.2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/168d981c2a8dcf59ad536abc16702592508a2ff1) - @RogerSelwyn

## v3.2.1 (2022/05/26)
### Enhancements
- [Add filtering on body](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b593646ca2c073929cdc7025fd59391df149ea4c) - @RogerSelwyn
### Maintenance
- [Remove unnecessary BCC](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/57b2839a94f16085fa13094f37d7f1c9f0d2f1d1) - @RogerSelwyn
- [Update readme](https://github.com/RogerSelwyn/O365-HomeAssistant/pull/57) - @GitHubGoody
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3bd203e9bce7f0da3f70bdb9401eb9ce468a4b88) - @RogerSelwyn
- [Remove domains key from hacs.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2066cb3b60a40ec9aa181b5404c5b927ec4ab33d) - @RogerSelwyn
- [Bump o365 to 2.0.19](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/880950c2228a11730700a268fc648fa48a972385) - @RogerSelwyn
- [Bump to v3.2.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4541f66e4dfa81acce7be8f5d906ecaf4f291804) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9065037f974f1bb09e6368261fbe4223a7765da0) - @actions-user

## v3.2.0 (2022/05/19)
### Breaking change
- [Change default auth method](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/efb3cf2b00d2d32df53691e3c82eb87d077ec814) - @RogerSelwyn
### Enhancements
- [Add Chat Sensor](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0184872e15b357489edfc56e1d11520fd187ba50) - @RogerSelwyn
### Maintenance
- [Update authentication info](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7c3c1e0aabb8f709925aa99357e2bba5a3f07e25) - @RogerSelwyn
- [Add deprecation warning and change alt_auth config parameter](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7feef161c715b9799a39be18165c90a392b54c85) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4d69ccd0467470fab0e6ccc5cc42626ee3345faa) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/218443692e8762d25ab7a26723159ce632019cf6) - @RogerSelwyn
- [Create stale.yaml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3a93714c918658850840d0e16b3e989c9150e7dc) - @RogerSelwyn
- [Bump to v3.2.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/62091cb924abbcafd283ca7a5aad47d4e7e5c790) - @RogerSelwyn

## v3.1.1 (2022/05/06)
### Fixes
- [Fix error on device update](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d5ed3cc98e95cdebfae334cb44b7009a5463f09e) - @RogerSelwyn
### Maintenance
- [Bump to v3.1.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9caac0960f198f4c24c75fac5d94f6b96cc8ffda) - @RogerSelwyn

## v3.1.0 (2022/05/05)
### Enhancements
- [Move setup_platform to async](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/dc8974b83858b7f4e401e0e0e36b6c0c15c3bf99) - @RogerSelwyn
- [Move calls to o365 async](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f9dfedd42f70c03f49760ba6fba8adcce4fd2cdc) - @RogerSelwyn
### Fixes
- [Fix issue with photo embedding](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d0eff81b6d6fa39246ed37113a300b4a88288a73) - @RogerSelwyn
### Maintenance
- [Use CalendarEntity instead of CalendarEventDevice](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/712daf59e92436664f75e4a3a053f408243abb16) - @RogerSelwyn
- [Rename device to entity](https://github.com/RogerSelwyn/O365-HomeAssistant/cpmmit/fcab07dc520e9e09c876ea3c6e1ecc81b83ea67b) - @RogerSelwyn
- [Bump to v3.1.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0d1032c8855ed439c28e32f3c8267bbf5b0badf6) - @RogerSelwyn
- [Sourcery recommended code change](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3f086fe7184276698d0e9d685a99b81debba89fd) - @RogerSelwyn

## v3.0.1 (2022/05/02)
### Fixes
- [Fix photo embedding](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/2b26133711c3f87e614b13c66799f9a7ff164f0c) - @RogerSelwyn
 
### Maintenance
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b5fb1f35dad975d3a939b4ec71a8c2e436d3c279) - @RogerSelwyn
- [Updated README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/912272b3c276a71b2944f728d4d18325864a589b) - @GitHubGoody
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7566dc035955c087a7e23f9ca69a20698fc288d8) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/e95504f2bc74abbdb4ae62b0ac6e460e0ec6b01f) - @RogerSelwyn
- [Create FUNDING.yml](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4b7b9038b8aaacd76d27a403d3c79f09f08876af) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3962a234aa2800e412af63df496f48f264803846) - @RogerSelwyn
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8e78e2423f199f75abdacbc137c95629e301d032) - @actions-user

## v3.0.0 (2022/04/17)
### Enhancements
- Support for multiple accounts
- Reduced permissions requirements for multiple accounts style config
- Enable use of Entity_ID instead of Calendar_ID for service calls (mandatory for multi-account)
- Complete list of changes - [#26](https://github.com/RogerSelwyn/O365-HomeAssistant/pull/26)

## v2.4.1 (2022/03/30)
### Fixes
- [Fix validation of service data and improve attachment handling](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/adbe61de8e8bd0b6b4f91fc8f400d519e8072bdd) - @RogerSelwyn
- [Fix for breaking change in HA](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4d616e96e8f6c59a8a208e07641261dbac736fdb) - @RogerSelwyn
- [Correct handling for DST](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f7bcf29ec621b7fbc474f7d6663f8e633218ba8c) - @RogerSelwyn

### Maintenance
- [Sourcery code improvements](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3983c3ab9a84f52a4213922f625ed66309a1d568) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d98a34d248faa04c1615924b5324d4efe1bed243) - @RogerSelwyn
- [Bump to v2.4.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8e1f585def6ee1ab433e69438d47e69ed79eb511) - @RogerSelwyn

## v2.4.0 (2022/03/07)
**Note:** This release has a radical change to the permissions structure to reduce the scope of the permissions requested. To further reduce the permissions please set 'enables_update' to False in your configuration. This will disable the various update services and remove the request for write access to calendars and send access to mail.

### Enhancements
- [Initial change to permissions](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/0d3eeb064745b0d70a75893ea31b2cbf76200500) - @RogerSelwyn
- [Add 'enable_update' switch so update capability can be disabled.](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8ceccffec922caf95fdb82ee2d94813e4118b6e8) - @RogerSelwyn

### Fixes
- [Remove extraneous error](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/f0adb5067c6fde20c6d4b750f6c9018685695d2f) - @RogerSelwyn

### Maintenance
- [Move more of calendar and sensor to async](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/becf8454a60aab3cc50a3c38ff92715a064f3263) - @RogerSelwyn
- [Remove the already deprecated YAML Calendar configuration](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/6cb631078b628e6bf4dd0b67adb2232ef6430f39) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1bbc3e4627d66a4b62a147b0a7a65a702d41d5c2) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1832b3094a7064e1652df34a1042dbb752210e5b) - @RogerSelwyn
- [Code tidy up](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4ffaf6779057b0a694a00e89467c679a48128cfa) - @RogerSelwyn
- [Bump to v2.4.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/9d2cb2744daa06798a1a846497bef91f60851f5b) - @RogerSelwyn


## v2.3.1 (2022/02/12)
### Enhancements
- [Add ability to stop dowloading of attachments (to increase performance)](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/37ad1b7cb6b6aeb1de0b7a17216719311a9b5407) - @RogerSelwyn

### Maintenance
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/28c7da2c7d2e2de5bd57e8219592e2f4f6eb4bce) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/55360e0c2a559501304c5c8fe83b4a58a25def30) - @RogerSelwyn
- [Minor code tidy up](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a14564d46e8c6ce70e1f4f2f6c0acc0dafd8dd34) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/8d75bc665389feb4f9ab55ea664f374853974e0c) - @RogerSelwyn
- [Bump to v2.3.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/32eb8d0140bf5763d365ec36a45ee02f09a77b73) - @RogerSelwyn

## v2.3.0 (2022/02/11)
### Enhancements
- [Add Teams Presence Sensor](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/a92a0a66cc140bca33fcbc81593e52579d1efa21) - @RogerSelwyn

### Fixes
- [Fix storing of o365_calendars.yaml to store/retrieve from config directory](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/7ccc053beb6b0aed88d4005570ec7ff2e2241e35) - @RogerSelwyn
- [Fix storing of token in the config directory](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/42a2f3fa38d433b00d8271ce1eef8ed434ea2d3a) - @RogerSelwyn

### Maintenance
- [Update CHANGELOG.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/323b3396c61817f4f354c938a068c1d72ebc0bb1) - @RogerSelwyn
- [Bump O365 to 2.0.18.1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/57dc3ded8231cd0243f5c9757d74af4746681efb) - @RogerSelwyn
- [Code tidy up to remove redundant code](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1e95e1dc00dcfa6a3ddbb936c7748adb4418b240) - @RogerSelwyn
- [Pylint code improvements](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4e5f2f7a19cc193a448a7e8c3ffa40d8d35964d0) - @RogerSelwyn
- [Code simplification from sourcery](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/5734711729d017a08e19f86b120049314d3e99d0) - @RogerSelwyn
- [Bump to v2.3.0](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/3fd836647092e0d1f1c8de5e8508dda71e345326) - @RogerSelwyn
- [Auto update requirements.txt](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/55a592bfad9dba11b3ed0caaf456a8d8842d5015) - @actions-user

## v2.2.8 (2022/02/02)
### Maintenance
- [Bump o365 from 2.0.16 to 2.0.17](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/66f669314668130d78c81568f5edad520c50b456) - @dependabot[bot]
- [Update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d465d61c1bb3aef551f35b1acbf262668389871d) - @RogerSelwyn
- [Bump to v2.2.9](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b236ab93c2594f5af6f8cb1e88019af0b071dec3) - @RogerSelwyn

## v2.2.8 (2022/01/19)
### Enhancements
- [Add importance as query filter](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/cf1055bca377c2dc42fd5337693b88fc305a4b0e) - @RogerSelwyn

### Fixes
- [Fix issue with no events retrieved if none in next 24h](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/80e99833d7aada408911c7ac7d878530f62a83a4) - @RogerSelwyn - [#13](https://github.com/RogerSelwyn/O365-HomeAssistant/issues/13) 
- [Fix error with filter not including receivedDateTime](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/26fcb30be5be22c84f66b7e8297ae66776eeacbe) - @RogerSelwyn

### Maintenance
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/ca2b3190c6fd80c904a7129c530471f0709a768a) - @RogerSelwyn
- [Documenation clarifications](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/aab090a53b65e01cde91aebd01dfe7c406d5587a) - @uSlackr
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/4cfb23620b7466dd38278496c8bd1d705b0466a5) - @RogerSelwyn
- [Update README.md](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/d1d231192ea7f0a870be8730afbf38ef58c20dcc) - @RogerSelwyn
- [Bump to 2.2.8 Beta 1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c5cef0214a8db234bdfc0f0a193f3fa9a277f775) - @RogerSelwyn
- [Revert "Bump to 2.2.8 Beta 1"](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/19829afe5bdfcc441e775fb334115f4eb90a3439) - @RogerSelwyn
- [Bump to 2.2.8 Beta 1](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/beafec4c46923a1aa290aed5a02374bd41d567aa) - @RogerSelwyn
- [Bump to 2.2.8 Beta 2](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/726ac8c03dac1e49beb0cd7869b506c69f07cc5d) - @RogerSelwyn
- [Simplify Code](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/14a1f1945a2147d2e1633a785f0d761c323ac578) - @RogerSelwyn
- [Remove duplicate code](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/b917935c6f572f43b2045ff3cff30bcc3514a104) - @RogerSelwyn
- [Bump to 2.2.8 Beta 3](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/1c3a4f3e034f6a554f4fd032933db7deab8e6307) - @RogerSelwyn
- [Auto update manifest.json](https://github.com/RogerSelwyn/O365-HomeAssistant/commit/c8d28ff0da03651e7d2ce964a235b3467f6a70a1) - @actions-user

</details>
<details>
  <summary>Changes 2021</summary>

## 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


</details>
<details>
  <summary>Earlier</summary>

### 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
</details>



================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Patrick Toft Steffensen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
[![Validate with hassfest](https://github.com/RogerSelwyn/O365-HomeAssistant/actions/workflows/hassfest.yaml/badge.svg)](https://github.com/RogerSelwyn/O365-HomeAssistant/actions/workflows/hassfest.yaml) [![HACS Validate](https://github.com/RogerSelwyn/O365-HomeAssistant/actions/workflows/hacs.yaml/badge.svg)](https://github.com/RogerSelwyn/O365-HomeAssistant/actions/workflows/hacs.yaml) [![CodeFactor](https://www.codefactor.io/repository/github/rogerselwyn/o365-homeassistant/badge)](https://www.codefactor.io/repository/github/rogerselwyn/o365-homeassistant) [![Downloads for latest release](https://img.shields.io/github/downloads/RogerSelwyn/O365-HomeAssistant/latest/total.svg)](https://github.com/RogerSelwyn/O365-HomeAssistant/releases/latest) ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/RogerSelwyn/O365-HomeAssistant/total?label=downloads%40all)

[![GitHub release](https://img.shields.io/github/v/release/RogerSelwyn/O365-HomeAssistant)](https://github.com/RogerSelwyn/O365-HomeAssistant/releases/latest) [![maintained](https://img.shields.io/maintenance/no/2025.svg)](#) [![maintainer](https://img.shields.io/badge/maintainer-%20%40RogerSelwyn-blue.svg)](https://github.com/RogerSelwyn) [![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg)](https://github.com/hacs/integration) [![Community Forum](https://img.shields.io/badge/community-forum-brightgreen.svg)](https://community.home-assistant.io/t/office-365-calendar-access)

# Deprecation Notice:
The Calendar, Mail Notification, Teams and To Do entities and services are now deprecated in the O365 integration. Development on O365 has now stopped unless a change is required to support migration to the MS365 integrations.

Details on how to migrate to the new MS365 integrations can be found here - [Migration](https://rogerselwyn.github.io/O365-HomeAssistant/migration.html)

# Note: 

Support for this ended on 5th November with the release of 2025.11. I have already created integrations which will replace it - MS365-Calendar/Mail/Teams/ToDo, these can now be found on HACS. 

Many will have seen a warning in the logs - `Detected that custom integration 'o365' uses 'async_config_entry_first_refresh', which is only supported for coordinators with a config entry and will stop working in Home Assistant 2025.11` - this further forces the EOL due to a change in HA Core, which means that the integration will cease to work after the 2025.11 HA release without really significant re-work. Because this integration is setup via YAML, it does not have a `config_entry`, the replacements listed above do.

[O365 --> MS365 - A potential big change - your views needed](https://github.com/RogerSelwyn/O365-HomeAssistant/discussions/234)

# Office 365 Integration for Home Assistant

*This is a fork of the original integration by @PTST which has now been archived.*

This integration enables:
1. Getting and creating calendar events
1. Getting emails from your inbox using one of two available sensor types (e-mail and query)
1. Sending emails via the notify.o365_email service
1. Getting presence from Teams (not for personal accounts)
1. Getting the latest chat message from Teams (not for personal accounts)
1. Getting and creating To-Do tasks
1. Setting Auto Reply/Out of Office response

This project would not be possible without the wonderful [python-o365 project](https://github.com/O365/python-o365).

## [Buy Me A Beer 🍻](https://buymeacoffee.com/rogtp)
I work on this integration because I like things to work well for myself and others. Whilst I have now made significant changes to the integration, it would not be as it stands today without the major work to create it put in by @PTST. Please don't feel you are obligated to donate, but of course it is appreciated.

<a href="https://www.buymeacoffee.com/rogtp" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a> 
<a href="https://www.paypal.com/donate/?hosted_button_id=F7TGHNGH7A526">
  <img src="https://github.com/RogerSelwyn/actions/blob/e82dab9e5643bbb82e182215a748a3024e3e7eac/images/paypal-donate-button.png" alt="Donate with PayPal" height="40"/>
</a>

# 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 != "<systemEventMessage/>":
                state = message.created_date
                extra_attributes = {
                    ATTR_FROM_DISPLAY_NAME: message.from_display_name,
                    ATTR_CONTENT: message.content,
                    ATTR_CHAT_ID: message.chat_id,
                    ATTR_IMPORTANCE: message.importance,
                    ATTR_SUBJECT: message.subject,
                    ATTR_SUMMARY: message.summary,
                }
                break
        return state, extra_attributes

    async def _async_get_memberlist(self, chat):
        if chat.object_id in self._chat_members and chat.chat_type != "oneOnOne":
            return self._chat_members[chat.object_id]
        members = await self.hass.async_add_executor_job(chat.get_members)
        memberlist = []
        for member in members:
            if member.display_name:
                memberlist.append(member.display_name)
            elif member.email:
                memberlist.append(member.email)
            else:
                memberlist.append("Name Unknown")
        self._chat_members[chat.object_id] = memberlist
        return memberlist

    async def _async_todos_update(self, key):
        """Update state."""
        entity_key = key[CONF_ENTITY_KEY]
        if entity_key in self._data:
            error = self._data[entity_key][ATTR_ERROR]
        else:
            self._data[entity_key] = {ATTR_TODOS: {}, ATTR_STATE: 0}
            error = False
        data, error = await self._async_todos_update_query(key, error)
        if not error:
            self._data[entity_key][ATTR_DATA] = await self.hass.async_add_executor_job(
                list, data
            )

        self._data[entity_key][ATTR_ERROR] = error

    async def _async_todos_update_query(self, key, error):
        data = None
        o365_task = key[CONF_O365_TASK_FOLDER]
        full_query = await async_build_todo_query(self._builder, key)
        name = key[CONF_NAME]

        try:
            data = await self.hass.async_add_executor_job(  # pylint: disable=no-member
                ft.partial(o365_task.get_tasks, batch=100, query=full_query)
            )
            if error:
                _LOGGER.info("O365 Task list reconnected for: %s", name)
                error = False
        except HTTPError:
            if not error:
                _LOGGER.error(
                    "O365 Task list not found for: %s - Has it been deleted?",
                    name,
                )
                error = True

        return data, error

    async def _async_auto_reply_update(self, key):
        """Update state."""
        entity_key = key[CONF_ENTITY_KEY]
        if data := await self.hass.async_add_executor_job(
            self._account.mailbox().get_settings
        ):
            self._data[entity_key] = {
                ATTR_STATE: data.automaticrepliessettings.status.value,
                ATTR_AUTOREPLIESSETTINGS: data.automaticrepliessettings,
            }


class O365EmailCordinator(DataUpdateCoordinator):
    """O365 email data update coordinator."""

    def __init__(self, hass, config):
        """Initialize my coordinator."""
        super().__init__(
            hass,
            _LOGGER,
            # Name of the data. For logging purposes.
            name="O365 Email",
            # Polling interval. Will only be polled if there are subscribers.
            update_interval=timedelta(seconds=30),
        )
        self._hass = hass
        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."""
        email_keys = await self._async_email_sensors()
        query_keys = await self._async_query_sensors()
        self._keys = email_keys + query_keys
        return self._keys

    async def _async_email_sensors(self):
        email_sensors = self._config.get(CONF_EMAIL_SENSORS, [])
        keys = []
        for sensor_conf in email_sensors:
            name = sensor_conf[CONF_NAME]
            if mail_folder := await self._async_get_mail_folder(
                sensor_conf, CONF_EMAIL_SENSORS
            ):
                new_key = {
                    CONF_ENTITY_KEY: _build_entity_id(
                        self.hass, ENTITY_ID_FORMAT_SENSOR, name
                    ),
                    CONF_UNIQUE_ID: f"{mail_folder.folder_id}_{self._account_name}_{name}",
                    CONF_SENSOR_CONF: sensor_conf,
                    CONF_O365_MAIL_FOLDER: mail_folder,
                    CONF_NAME: name,
                    CONF_ENTITY_TYPE: SENSOR_EMAIL,
                    CONF_QUERY: await async_build_inbox_query(
                        sensor_conf, self._builder
                    ),
                }

                # Renames unique id to ensure uniqueness - To be deleted in early 2025
                entity = self._ent_reg.async_get(new_key[CONF_ENTITY_KEY])
                if (
                    entity
                    and entity.unique_id
                    == f"{mail_folder.folder_id}_{self._account_name}"
                ):
                    self._ent_reg.async_update_entity(
                        new_key[CONF_ENTITY_KEY], new_unique_id=new_key[CONF_UNIQUE_ID]
                    )

                keys.append(new_key)
        return keys

    async def _async_query_sensors(self):
        query_sensors = self._config.get(CONF_QUERY_SENSORS, [])
        keys = []
        for sensor_conf in query_sensors:
            if mail_folder := await self._async_get_mail_folder(
                sensor_conf, CONF_QUERY_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"{mail_folder.folder_id}_{self._account_name}_{name}",
                    CONF_SENSOR_CONF: sensor_conf,
                    CONF_O365_MAIL_FOLDER: mail_folder,
                    CONF_NAME: name,
                    CONF_ENTITY_TYPE: SENSOR_EMAIL,
                    CONF_QUERY: await async_build_query_query(
                        sensor_conf, self._builder
                    ),
                }

                # Renames unique id to ensure uniqueness - To be deleted in early 2025
                entity = self._ent_reg.async_get(new_key[CONF_ENTITY_KEY])
                if (
                    entity
                    and entity.unique_id
                    == f"{mail_folder.folder_id}_{self._account_name}"
                ):
                    self._ent_reg.async_update_entity(
                        new_key[CONF_ENTITY_KEY], new_unique_id=new_key[CONF_UNIQUE_ID]
                    )

                keys.append(new_key)
        return keys

    async def _async_get_mail_folder(self, sensor_conf, sensor_type):
        """Get the configured folder."""
        mailbox = await self.hass.async_add_executor_job(self._account.mailbox)
        _LOGGER.debug("Get mail folder: %s", sensor_conf.get(CONF_NAME))
        if mail_folder_conf := sensor_conf.get(CONF_MAIL_FOLDER):
            return await self._async_get_configured_mail_folder(
                mail_folder_conf, mailbox, sensor_type
            )

        return await self.hass.async_add_executor_job(mailbox.inbox_folder)

    async def _async_get_configured_mail_folder(
        self, mail_folder_conf, mailbox, sensor_type
    ):
        mail_folder = mailbox
        _LOGGER.debug("Get folder %s - start", mail_folder_conf)

        for folder in mail_folder_conf.split("/"):
            mail_folder = await self.hass.async_add_executor_job(
                ft.partial(
                    mail_folder.get_folder,
                    folder_name=folder,
                )
            )
            _LOGGER.debug("Get folder %s - process - %s", mail_folder_conf, mail_folder)
            if not mail_folder:
                _LOGGER.error(
                    "Folder - %s - not found from %s config entry - %s - entity not created",
                    folder,
                    sensor_type,
                    mail_folder_conf,
                )
                return None

        _LOGGER.debug("Get folder %s - finish ", mail_folder_conf)
        return mail_folder

    async def _async_update_data(self):
        _LOGGER.debug(
            "Doing %s email update(s) for: %s", len(self._keys), self._account_name
        )

        for key in self._keys:
            await self._async_email_update(key)

        return self._data

    async def _async_email_update(self, key):
        """Update code."""

        sensor_conf = key[CONF_SENSOR_CONF]
        download_attachments = sensor_conf.get(CONF_DOWNLOAD_ATTACHMENTS)
        max_items = sensor_conf.get(CONF_MAX_ITEMS, 5)
        mail_folder = key[CONF_O365_MAIL_FOLDER]
        entity_key = key[CONF_ENTITY_KEY]
        query = key[CONF_QUERY]

        data = await self.hass.async_add_executor_job(  # pylint: disable=no-member
            ft.partial(
                mail_folder.get_messages,
                limit=max_items,
                query=query,
                download_attachments=download_attachments,
            )
        )
        self._data[entity_key] = {
            ATTR_DATA: await self.hass.async_add_executor_job(list, data)
        }


def _build_entity_id(hass, entity_id_format, name):
    """Build and entity ID."""
    return async_generate_entity_id(
        entity_id_format,
        name,
        hass=hass,
    )


async def _async_delete_redundant_sensors(ent_reg, unique_id):
    if entity_id := ent_reg.async_get_entity_id("sensor", DOMAIN, unique_id):
        ent_reg.async_remove(entity_id)


================================================
FILE: custom_components/o365/helpers/migration.py
================================================
"""Migration services."""

import logging
from enum import StrEnum

from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_EMAIL, CONF_ENABLED, CONF_NAME
from homeassistant.core import HomeAssistant

from ..const import (
    CONF_ACCOUNT_NAME,
    CONF_ACCOUNTS,
    CONF_ALT_AUTH_METHOD,
    CONF_AUTO_REPLY_SENSORS,
    CONF_BASIC_CALENDAR,
    CONF_BODY_CONTAINS,
    CONF_CAL_ID,
    CONF_CHAT_SENSORS,
    CONF_CLIENT_ID,
    CONF_CLIENT_SECRET,
    CONF_DOWNLOAD_ATTACHMENTS,
    CONF_EMAIL_SENSORS,
    CONF_ENABLE_CALENDAR,
    CONF_ENABLE_UPDATE,
    CONF_GROUPS,
    CONF_HAS_ATTACHMENT,
    CONF_HTML_BODY,
    CONF_IMPORTANCE,
    CONF_IS_UNREAD,
    CONF_MAIL_FROM,
    CONF_MAX_ITEMS,
    CONF_QUERY_SENSORS,
    CONF_SHARED_MAILBOX,
    CONF_SHOW_BODY,
    CONF_STATUS_SENSORS,
    CONF_SUBJECT_CONTAINS,
    CONF_SUBJECT_IS,
    CONF_TODO_SENSORS,
    CONF_TRACK_NEW,
    CONF_TRACK_NEW_CALENDAR,
    CONF_YAML_TASK_LIST_ID,
    CONST_CONFIG_TYPE_LIST,
    CONST_PRIMARY,
    YAML_CALENDARS_FILENAME,
    YAML_TASK_LISTS_FILENAME,
)
from ..schema import YAML_CALENDAR_DEVICE_SCHEMA, YAML_TASK_LIST_SCHEMA
from ..utils.filemgmt import build_config_file_path, build_yaml_filename, load_yaml_file
from ..utils.utils import build_account_config

CONF_ALTERNATE_EMAIL = "alternate_email"
CONF_CHAT_ENABLE = "chat_enable"
CONF_ENABLE_AUTOREPLY = "enable_autoreply"
CONF_ENTITY_NAME = "entity_name"
CONF_FOLDER = "folder"
CONF_STATUS_ENABLE = "status_enable"
CONF_YAML_TODO_LIST_ID = "todo_list_id"
_LOGGER = logging.getLogger(__name__)


class Unread(StrEnum):
    """Mail Unread."""

    TRUE = "Unread Only"
    FALSE = "Read Only"
    NONE = "All"


class Attachment(StrEnum):
    """Mail Attachment."""

    TRUE = "Has attachment"
    FALSE = "Does not have attachment"
    NONE = "All"


class ImportanceLevel(StrEnum):
    """Mail Importance Level."""

    NORMAL = "Normal"
    LOW = "Low"
    HIGH = "High"
    NONE = "All"


class EnableOptions(StrEnum):
    """Teams sensors enablement."""

    DISABLED = "Disabled"
    READ = "Read"
    UPDATE = "Update"


class MigrationServices:
    """Migration Services."""

    def __init__(self, hass: HomeAssistant, config):
        """Initialise the migration services."""
        self._hass = hass
        self._config = config
        self._auto_reply_sensors = []

    async def async_migrate_config(self, call):  # pylint: disable=unused-argument
        """Service to migrate config entries."""
        for account in self._config[CONF_ACCOUNTS]:
            account_config = build_account_config(
                account, None, None, CONST_CONFIG_TYPE_LIST, None
            )
            account_config[CONF_ALT_AUTH_METHOD] = account[CONF_ALT_AUTH_METHOD]
            account_config[CONF_CLIENT_SECRET] = account[CONF_CLIE
Download .txt
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
Download .txt
SYMBOL INDEX (244 symbols across 18 files)

FILE: custom_components/o365/__init__.py
  function async_setup (line 45) | async def async_setup(hass, config):
  function _async_setup_account (line 61) | async def _async_setup_account(hass, account_conf, conf_type):
  function _try_authentication (line 105) | def _try_authentication(perms, credentials, main_resource):
  function _async_check_token (line 126) | async def _async_check_token(hass, account, account_name):
  function _validate_shared_schema (line 159) | def _validate_shared_schema(account_name, main_account, config):
  function _async_authorization_repair (line 188) | async def _async_authorization_repair(
  function _async_setup_migration_service (line 232) | async def _async_setup_migration_service(hass, config):
  class _IncreaseIndent (line 239) | class _IncreaseIndent(yaml.Dumper):
    method increase_indent (line 240) | def increase_indent(self, flow=False, indentless=False):

FILE: custom_components/o365/calendar.py
  function async_setup_platform (line 100) | async def async_setup_platform(hass, config, add_entities, discovery_inf...
  function _async_setup_add_entities (line 130) | async def _async_setup_add_entities(
  function _build_entity_id (line 171) | def _build_entity_id(hass, entity, conf):
  function _async_setup_register_services (line 189) | async def _async_setup_register_services(hass, account, update_supported):
  class O365CalendarEntity (line 221) | class O365CalendarEntity(CalendarEntity):
    method __init__ (line 226) | def __init__(
    method _init_data (line 257) | def _init_data(self, calendar_id, entity):
    method extra_state_attributes (line 271) | def extra_state_attributes(self):
    method event (line 288) | def event(self):
    method name (line 293) | def name(self):
    method unique_id (line 298) | def unique_id(self):
    method async_get_events (line 304) | async def async_get_events(self, hass, start_date, end_date):
    method async_update (line 308) | async def async_update(self):
    method async_create_event (line 339) | async def async_create_event(self, **kwargs: Any) -> None:
    method async_update_event (line 356) | async def async_update_event(
    method async_delete_event (line 382) | async def async_delete_event(
    method async_create_calendar_event (line 391) | async def async_create_calendar_event(self, subject, start, end, **kwa...
    method async_modify_calendar_event (line 405) | async def async_modify_calendar_event(
    method _async_update_calendar_event (line 438) | async def _async_update_calendar_event(
    method async_remove_calendar_event (line 447) | async def async_remove_calendar_event(
    method _log_error (line 470) | def _log_error(self, error, err):
    method _async_delete_calendar_event (line 477) | async def _async_delete_calendar_event(self, event_id, ha_event):
    method async_respond_calendar_event (line 485) | async def async_respond_calendar_event(
    method _async_send_response (line 500) | async def _async_send_response(self, event_id, response, send_response...
    method _async_get_event_from_calendar (line 522) | async def _async_get_event_from_calendar(self, event_id):
    method _validate_permissions (line 526) | def _validate_permissions(self, error_message):
    method _raise_event (line 541) | def _raise_event(self, event_type, event_id):
  class O365CalendarData (line 549) | class O365CalendarData:
    method __init__ (line 552) | def __init__(
    method async_calendar_data_init (line 575) | async def async_calendar_data_init(self, hass):
    method _async_get_calendar (line 586) | async def _async_get_calendar(self, hass):
    method async_o365_get_events (line 600) | async def async_o365_get_events(self, hass, start_date, end_date):
    method _filter_events (line 617) | def _filter_events(self, events):
    method _sort_events (line 633) | def _sort_events(self, events):
    method _async_calendar_schedule_get_events (line 645) | async def _async_calendar_schedule_get_events(
    method async_get_events (line 683) | async def async_get_events(self, hass, start_date, end_date):
    method async_update (line 710) | async def async_update(self, hass):
    method _get_root_event (line 753) | def _get_root_event(self, results):
    method is_all_day (line 784) | def is_all_day(vevent):
    method is_started (line 789) | def is_started(vevent):
    method is_finished (line 794) | def is_finished(vevent):
    method to_datetime (line 799) | def to_datetime(obj):
  class CalendarServices (line 822) | class CalendarServices:
    method __init__ (line 825) | def __init__(self, hass, account):
    method async_scan_for_calendars (line 830) | async def async_scan_for_calendars(self, call):  # pylint: disable=unu...
  function _group_calendar_log (line 853) | def _group_calendar_log(entity_id):

FILE: custom_components/o365/classes/entity.py
  class O365Entity (line 9) | class O365Entity(CoordinatorEntity):
    method __init__ (line 15) | def __init__(self, coordinator, config, name, entity_id, entity_type, ...
    method name (line 25) | def name(self):
    method entity_key (line 30) | def entity_key(self):
    method unique_id (line 35) | def unique_id(self):
    method _validate_permissions (line 39) | def _validate_permissions(self, required_permission, required_permissi...

FILE: custom_components/o365/classes/mailsensor.py
  class O365MailSensor (line 42) | class O365MailSensor(O365Entity, SensorEntity):
    method __init__ (line 47) | def __init__(self, coordinator, config, sensor_conf, name, entity_id, ...
    method native_value (line 58) | def native_value(self):
    method extra_state_attributes (line 63) | def extra_state_attributes(self):
    method _handle_coordinator_update (line 67) | def _handle_coordinator_update(self) -> None:
    method _update_status (line 71) | def _update_status(self) -> None:
    method _get_attributes (line 78) | def _get_attributes(self, data):
  class O365AutoReplySensor (line 87) | class O365AutoReplySensor(O365Entity, SensorEntity):
    method __init__ (line 92) | def __init__(self, coordinator, name, entity_id, config, unique_id):
    method async_init (line 101) | async def async_init(self, hass):
    method native_value (line 106) | def native_value(self):
    method extra_state_attributes (line 111) | def extra_state_attributes(self):
    method async_auto_reply_enable (line 122) | async def async_auto_reply_enable(
    method async_auto_reply_disable (line 143) | async def async_auto_reply_disable(self):
    method _validate_autoreply_permissions (line 150) | def _validate_autoreply_permissions(self):
  function _async_build_base_query (line 158) | async def _async_build_base_query(sensor_conf, builder):
  function async_build_inbox_query (line 186) | async def async_build_inbox_query(sensor_conf, builder: QueryBuilder):
  function async_build_query_query (line 200) | async def async_build_query_query(sensor_conf, builder: QueryBuilder):
  function _add_to_query (line 235) | def _add_to_query(

FILE: custom_components/o365/classes/permissions.py
  class Permissions (line 54) | class Permissions:
    method __init__ (line 57) | def __init__(self, hass, config, conf_type):
    method requested_permissions (line 71) | def requested_permissions(self):
    method permissions (line 85) | def permissions(self):
    method async_check_authorizations (line 89) | async def async_check_authorizations(self):
    method validate_authorization (line 117) | def validate_authorization(self, permission):
    method _check_higher_permissions (line 141) | def _check_higher_permissions(self, permission):
    method _build_token_filename (line 158) | def _build_token_filename(self):
    method _get_permissions (line 167) | def _get_permissions(self):
    method _build_calendar_permissions (line 190) | def _build_calendar_permissions(self):
    method _build_group_permissions (line 209) | def _build_group_permissions(self):
    method _build_email_permissions (line 216) | def _build_email_permissions(self):
    method _build_autoreply_permissions (line 222) | def _build_autoreply_permissions(self):
    method _build_status_permissions (line 227) | def _build_status_permissions(self):
    method _build_chat_permissions (line 241) | def _build_chat_permissions(self):
    method _build_todo_permissions (line 249) | def _build_todo_permissions(self):
    method delete_token (line 257) | def delete_token(self):

FILE: custom_components/o365/classes/teamssensor.py
  class O365TeamsSensor (line 43) | class O365TeamsSensor(O365Entity):
    method __init__ (line 48) | def __init__(self, cordinator, name, entity_id, config, entity_type, u...
    method native_value (line 55) | def native_value(self):
  class O365TeamsStatusSensor (line 60) | class O365TeamsStatusSensor(O365TeamsSensor, SensorEntity):
    method __init__ (line 63) | def __init__(self, coordinator, name, entity_id, config, unique_id, em...
    method async_update_user_status (line 75) | async def async_update_user_status(
    method async_update_user_preferred_status (line 104) | async def async_update_user_preferred_status(
    method _raise_event (line 137) | def _raise_event(self, event_type, status):
    method _validate_status_permissions (line 144) | def _validate_status_permissions(self):
  class O365TeamsChatSensor (line 151) | class O365TeamsChatSensor(O365TeamsSensor, SensorEntity):
    method __init__ (line 154) | def __init__(self, coordinator, name, entity_id, config, unique_id):
    method extra_state_attributes (line 161) | def extra_state_attributes(self):
    method async_send_chat_message (line 183) | async def async_send_chat_message(self, chat_id, message, content_type):
    method _raise_event (line 201) | def _raise_event(self, event_type, chat_id):
    method _validate_chat_permissions (line 208) | def _validate_chat_permissions(self):

FILE: custom_components/o365/const.py
  class EventResponse (line 6) | class EventResponse(Enum):

FILE: custom_components/o365/helpers/coordinator.py
  class O365SensorCordinator (line 74) | class O365SensorCordinator(DataUpdateCoordinator):
    method __init__ (line 77) | def __init__(self, hass, config):
    method async_setup_entries (line 99) | async def async_setup_entries(self):
    method _async_status_sensors (line 108) | async def _async_status_sensors(self):
    method _chat_sensors (line 132) | def _chat_sensors(self):
    method _async_todo_sensors (line 150) | async def _async_todo_sensors(self):
    method _async_todo_entities (line 170) | async def _async_todo_entities(self, o365_task_lists):
    method _async_auto_reply_sensors (line 215) | async def _async_auto_reply_sensors(self):
    method _async_update_data (line 232) | async def _async_update_data(self):
    method _async_teams_status_update (line 251) | async def _async_teams_status_update(self, key):
    method _async_teams_chat_update (line 266) | async def _async_teams_chat_update(self, key):
    method _process_chat_messages (line 304) | def _process_chat_messages(self, messages):
    method _async_get_memberlist (line 321) | async def _async_get_memberlist(self, chat):
    method _async_todos_update (line 336) | async def _async_todos_update(self, key):
    method _async_todos_update_query (line 352) | async def _async_todos_update_query(self, key, error):
    method _async_auto_reply_update (line 375) | async def _async_auto_reply_update(self, key):
  class O365EmailCordinator (line 387) | class O365EmailCordinator(DataUpdateCoordinator):
    method __init__ (line 390) | def __init__(self, hass, config):
    method async_setup_entries (line 413) | async def async_setup_entries(self):
    method _async_email_sensors (line 420) | async def _async_email_sensors(self):
    method _async_query_sensors (line 456) | async def _async_query_sensors(self):
    method _async_get_mail_folder (line 492) | async def _async_get_mail_folder(self, sensor_conf, sensor_type):
    method _async_get_configured_mail_folder (line 503) | async def _async_get_configured_mail_folder(
    method _async_update_data (line 529) | async def _async_update_data(self):
    method _async_email_update (line 539) | async def _async_email_update(self, key):
  function _build_entity_id (line 562) | def _build_entity_id(hass, entity_id_format, name):
  function _async_delete_redundant_sensors (line 571) | async def _async_delete_redundant_sensors(ent_reg, unique_id):

FILE: custom_components/o365/helpers/migration.py
  class Unread (line 61) | class Unread(StrEnum):
  class Attachment (line 69) | class Attachment(StrEnum):
  class ImportanceLevel (line 77) | class ImportanceLevel(StrEnum):
  class EnableOptions (line 86) | class EnableOptions(StrEnum):
  class MigrationServices (line 94) | class MigrationServices:
    method __init__ (line 97) | def __init__(self, hass: HomeAssistant, config):
    method async_migrate_config (line 103) | async def async_migrate_config(self, call):  # pylint: disable=unused-...
    method _async_migrate_account (line 116) | async def _async_migrate_account(self, config):
    method _async_migrate_calendar (line 123) | async def _async_migrate_calendar(self, config, base_config_entry):
    method _async_migrate_mail (line 149) | async def _async_migrate_mail(self, config, base_config_entry):
    method _async_migrate_teams (line 174) | async def _async_migrate_teams(self, config, base_config_entry):
    method _async_migrate_todos (line 221) | async def _async_migrate_todos(self, config, base_config_entry):
    method _async_create_alternate_email_status (line 246) | async def _async_create_alternate_email_status(
    method _async_mail_sensors (line 264) | async def _async_mail_sensors(
    method _async_create_entry (line 317) | async def _async_create_entry(
    method _integration_installed (line 331) | def _integration_installed(self, migrate_domain):
    method _setup_base (line 339) | def _setup_base(self, config):
    method _add_attribute (line 347) | def _add_attribute(self, config, entry, attribute_name):

FILE: custom_components/o365/helpers/setup.py
  function do_setup (line 30) | async def do_setup(
  function _async_sensor_setup (line 58) | async def _async_sensor_setup(hass, account_config):
  function _async_email_setup (line 68) | async def _async_email_setup(hass, account_config):
  function _load_platforms (line 78) | def _load_platforms(hass, account_name, config, account_config):

FILE: custom_components/o365/notify.py
  function async_get_service (line 36) | async def async_get_service(hass, config, discovery_info=None):  # pylin...
  class O365EmailService (line 57) | class O365EmailService(BaseNotificationService):
    method __init__ (line 60) | def __init__(self, account, hass, config):
    method targets (line 74) | def targets(self):
    method send_message (line 78) | def send_message(self, message="", **kwargs):
    method async_send_message (line 82) | async def async_send_message(self, message="", **kwargs):
    method _build_message (line 120) | def _build_message(self, data, message, new_message_attachments):
    method _build_photo_content (line 136) | def _build_photo_content(self, photos, new_message_attachments):
    method _build_attachments (line 154) | def _build_attachments(self, data, new_message_attachments):
    method _cleanup (line 173) | def _cleanup(self):
    method _get_ha_filepath (line 177) | def _get_ha_filepath(self, filepath):
  function zip_files (line 190) | def zip_files(filespaths, zip_name):

FILE: custom_components/o365/repairs.py
  class AuthorizationRepairFlow (line 38) | class AuthorizationRepairFlow(RepairsFlow):
    method __init__ (line 41) | def __init__(
    method async_step_init (line 61) | async def async_step_init(
    method async_step_request_default (line 78) | async def async_step_request_default(
    method async_step_request_alt (line 103) | async def async_step_request_alt(
    method _async_validate_response (line 131) | async def _async_validate_response(self, user_input):
  function async_create_fix_flow (line 183) | async def async_create_fix_flow(
  class O365AuthCallbackView (line 193) | class O365AuthCallbackView(HomeAssistantView):
    method __init__ (line 200) | def __init__(self):
    method get (line 205) | async def get(self, request):
  function get_callback_url (line 215) | def get_callback_url(hass, alt_config):

FILE: custom_components/o365/schema.py
  function _has_consistent_timezone (line 118) | def _has_consistent_timezone(*keys: Any) -> Callable[[dict[str, Any]], d...
  function _as_local_timezone (line 136) | def _as_local_timezone(*keys: Any) -> Callable[[dict[str, Any]], dict[st...

FILE: custom_components/o365/sensor.py
  function async_setup_platform (line 44) | async def async_setup_platform(hass, config, async_add_entities, discove...
  function _async_sensor_entities (line 66) | async def _async_sensor_entities(conf, hass):
  function _email_entities (line 119) | def _email_entities(conf):
  function _async_setup_register_services (line 139) | async def _async_setup_register_services(config):
  function _async_setup_status_services (line 146) | async def _async_setup_status_services(config, perms):
  function _async_setup_chat_services (line 170) | async def _async_setup_chat_services(config, perms):
  function _async_setup_mailbox_services (line 187) | async def _async_setup_mailbox_services(config, perms):

FILE: custom_components/o365/todo.py
  function async_setup_platform (line 68) | async def async_setup_platform(hass, config, async_add_entities, discove...
  function _async_setup_register_services (line 106) | async def _async_setup_register_services(hass, config):
  function _async_setup_task_services (line 111) | async def _async_setup_task_services(hass, config, perms):
  class O365TodoList (line 149) | class O365TodoList(O365Entity, TodoListEntity):  # pylint: disable=abstr...
    method __init__ (line 152) | def __init__(
    method state (line 187) | def state(self):
    method todo_items (line 192) | def todo_items(self):
    method extra_state_attributes (line 197) | def extra_state_attributes(self):
    method _handle_coordinator_update (line 201) | def _handle_coordinator_update(self) -> None:
    method _update_status (line 205) | def _update_status(self, hass):
    method _update_extra_state_attributes (line 252) | def _update_extra_state_attributes(self, todos):
    method async_create_todo_item (line 293) | async def async_create_todo_item(self, item: TodoItem) -> None:
    method async_new_todo (line 299) | async def async_new_todo(self, subject, description=None, due=None, re...
    method async_update_todo_item (line 311) | async def async_update_todo_item(self, item: TodoItem) -> None:
    method async_update_todo (line 340) | async def async_update_todo(
    method async_delete_todo_items (line 365) | async def async_delete_todo_items(self, uids: list[str]) -> None:
    method async_delete_todo (line 370) | async def async_delete_todo(self, todo_id):
    method async_complete_todo (line 383) | async def async_complete_todo(self, todo_id, completed, o365_task=None):
    method _async_complete_task (line 400) | async def _async_complete_task(self, o365_task, todo_id):
    method _async_uncomplete_task (line 411) | async def _async_uncomplete_task(self, o365_task, todo_id):
    method _async_save_task (line 421) | async def _async_save_task(
    method _raise_event (line 453) | def _raise_event(self, event_type, todo_id):
    method _validate_task_permissions (line 460) | def _validate_task_permissions(self):
  function _raise_event_external (line 467) | def _raise_event_external(hass, event_type, todo_id, time_type, task_dat...
  function async_build_todo_query (line 475) | async def async_build_todo_query(builder: QueryBuilder, key):
  class O365TodoEntityServices (line 495) | class O365TodoEntityServices:
    method __init__ (line 498) | def __init__(self, hass):
    method async_scan_for_todo_lists (line 502) | async def async_scan_for_todo_lists(self, call):  # pylint: disable=un...

FILE: custom_components/o365/utils/calendar_utils.py
  function format_event_data (line 25) | def format_event_data(event):
  function get_hass_date (line 45) | def get_hass_date(obj, is_all_day):
  function get_end_date (line 50) | def get_end_date(obj):
  function get_start_date (line 61) | def get_start_date(obj):
  function add_call_data_to_event (line 66) | def add_call_data_to_event(event, subject, start, end, **kwargs):
  function _add_attribute (line 86) | def _add_attribute(attribute, event_attribute):
  function _add_attendees (line 90) | def _add_attendees(attendees, event):
  function _add_all_day (line 101) | def _add_all_day(is_all_day, event):
  function _rrule_processing (line 113) | def _rrule_processing(event, rrule):
  function _process_byday (line 150) | def _process_byday(byday):

FILE: custom_components/o365/utils/filemgmt.py
  function load_yaml_file (line 29) | def load_yaml_file(path, item_id, item_schema):
  function _write_yaml_file (line 50) | def _write_yaml_file(yaml_filepath, yaml_list):
  function _get_calendar_info (line 57) | def _get_calendar_info(calendar, track_new_devices):
  function async_update_calendar_file (line 73) | async def async_update_calendar_file(config, calendar, hass, track_new_d...
  function _get_task_list_info (line 86) | def _get_task_list_info(yaml_task_list, track_new_devices):
  function async_update_task_list_file (line 97) | async def async_update_task_list_file(config, yaml_task_list, hass, trac...
  function build_config_file_path (line 110) | def build_config_file_path(hass, filepath):
  function build_yaml_filename (line 117) | def build_yaml_filename(conf, filename, conf_type=None):

FILE: custom_components/o365/utils/utils.py
  function clean_html (line 29) | def clean_html(html):
  function _safe_html (line 47) | def _safe_html(html):
  function get_email_attributes (line 60) | def get_email_attributes(mail, download_attachments, html_body, show_body):
  function build_account_config (line 87) | def build_account_config(config, account, is_authenticated, conf_type, p...
Condensed preview — 57 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (359K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 112,
    "preview": "custom: [\"https://www.buymeacoffee.com/rogtp\", \"https://www.paypal.com/donate/?hosted_button_id=F7TGHNGH7A526\"]\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 2774,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".github/workflows/hacs.yaml",
    "chars": 309,
    "preview": "name: HACS Validate\n\non:\n  push:\n  pull_request:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  hacs:\n    name: HACS Actio"
  },
  {
    "path": ".github/workflows/hassfest.yaml",
    "chars": 268,
    "preview": "name: Validate with hassfest\n\non:\n  push:\n  pull_request:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  hassfest:\n    nam"
  },
  {
    "path": ".github/workflows/lint.yaml",
    "chars": 543,
    "preview": "name: \"Lint\"\n\non:\n  push:\n  pull_request:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  lint:\n    name: Lint\n    runs-on:"
  },
  {
    "path": ".github/workflows/o365release.yaml",
    "chars": 695,
    "preview": "name: O365 Release\n\non:\n  release:\n    types: [published]\n\njobs:\n  release_zip_file:\n    name: Prepare release asset\n   "
  },
  {
    "path": ".github/workflows/stale.yaml",
    "chars": 706,
    "preview": "name: Close inactive issues\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  close-issues:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".gitignore",
    "chars": 1848,
    "preview": "\n# Created by https://www.gitignore.io/api/python,visualstudiocode\n# Edit at https://www.gitignore.io/?templates=python,"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 90805,
    "preview": "# Changelog\n\n## v5.3.5 (2025/10/17)\n### 🐛 Fixes\n- [Include oauthlib dependency](https://github.com/RogerSelwyn/O365-Home"
  },
  {
    "path": "LICENSE",
    "chars": 1080,
    "preview": "MIT License\n\nCopyright (c) 2020 Patrick Toft Steffensen\n\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "README.md",
    "chars": 4761,
    "preview": "[![Validate with hassfest](https://github.com/RogerSelwyn/O365-HomeAssistant/actions/workflows/hassfest.yaml/badge.svg)]"
  },
  {
    "path": "custom_components/o365/__init__.py",
    "chars": 7413,
    "preview": "\"\"\"Main initialisation code.\"\"\"\n\nimport json\nimport logging\n\nimport voluptuous as vol\nimport yaml\nfrom homeassistant.con"
  },
  {
    "path": "custom_components/o365/calendar.py",
    "chars": 28107,
    "preview": "\"\"\"Main calendar processing.\"\"\"\n\nimport functools as ft\nimport logging\nimport re\nfrom copy import deepcopy\nfrom datetime"
  },
  {
    "path": "custom_components/o365/classes/__init__.py",
    "chars": 18,
    "preview": "\"\"\"Initialise.\"\"\"\n"
  },
  {
    "path": "custom_components/o365/classes/entity.py",
    "chars": 1518,
    "preview": "\"\"\"Generic O465 Sensor Entity.\"\"\"\n\nfrom homeassistant.exceptions import ServiceValidationError\nfrom homeassistant.helper"
  },
  {
    "path": "custom_components/o365/classes/mailsensor.py",
    "chars": 8042,
    "preview": "\"\"\"O365 mail sensors.\"\"\"\n\nimport datetime\nfrom operator import itemgetter\n\nfrom homeassistant.components.sensor import S"
  },
  {
    "path": "custom_components/o365/classes/permissions.py",
    "chars": 9885,
    "preview": "\"\"\"Permissions processes.\"\"\"\n\nimport json\nimport logging\nimport os\nfrom copy import deepcopy\n\nfrom homeassistant.const i"
  },
  {
    "path": "custom_components/o365/classes/teamssensor.py",
    "chars": 7082,
    "preview": "\"\"\"O365 teams sensors.\"\"\"\n\nimport functools as ft\nimport logging\n\nfrom homeassistant.components.sensor import SensorEnti"
  },
  {
    "path": "custom_components/o365/const.py",
    "chars": 6846,
    "preview": "\"\"\"Constants.\"\"\"\n\nfrom enum import Enum\n\n\nclass EventResponse(Enum):\n    \"\"\"Event response.\"\"\"\n\n    Accept = \"accept\"  #"
  },
  {
    "path": "custom_components/o365/helpers/__init__.py",
    "chars": 18,
    "preview": "\"\"\"Initialise.\"\"\"\n"
  },
  {
    "path": "custom_components/o365/helpers/coordinator.py",
    "chars": 21308,
    "preview": "\"\"\"Sensor processing.\"\"\"\n\nimport functools as ft\nimport logging\nfrom datetime import datetime, timedelta\n\nfrom homeassis"
  },
  {
    "path": "custom_components/o365/helpers/migration.py",
    "chars": 12719,
    "preview": "\"\"\"Migration services.\"\"\"\n\nimport logging\nfrom enum import StrEnum\n\nfrom homeassistant.config_entries import SOURCE_IMPO"
  },
  {
    "path": "custom_components/o365/helpers/setup.py",
    "chars": 3912,
    "preview": "\"\"\"Do configuration setup.\"\"\"\n\nimport asyncio\nimport logging\n\nfrom homeassistant.const import CONF_ENABLED\nfrom homeassi"
  },
  {
    "path": "custom_components/o365/icons.json",
    "chars": 1268,
    "preview": "{\n    \"services\": {\n        \"scan_for_calendars\": \"mdi:calendar-sync\",\n        \"scan_for_todo_lists\": \"mdi:clipboard-lis"
  },
  {
    "path": "custom_components/o365/manifest.json",
    "chars": 500,
    "preview": "{\n  \"domain\": \"o365\",\n  \"name\": \"Office 365\",\n  \"codeowners\": [\n    \"@RogerSelwyn\"\n  ],\n  \"dependencies\": [\n    \"configu"
  },
  {
    "path": "custom_components/o365/notify.py",
    "chars": 6645,
    "preview": "\"\"\"Notification processing.\"\"\"\n\nimport logging\nimport os\nimport zipfile\nfrom pathlib import Path\n\nfrom homeassistant.com"
  },
  {
    "path": "custom_components/o365/repairs.py",
    "chars": 7089,
    "preview": "\"\"\"Repair flows.\"\"\"\n\nfrom __future__ import annotations\n\nimport functools as ft\nimport logging\n\nimport voluptuous as vol"
  },
  {
    "path": "custom_components/o365/schema.py",
    "chars": 12774,
    "preview": "\"\"\"Schema for O365 Integration.\"\"\"\n\nimport datetime\nfrom collections.abc import Callable\nfrom itertools import groupby\nf"
  },
  {
    "path": "custom_components/o365/sensor.py",
    "chars": 6786,
    "preview": "\"\"\"Sensor processing.\"\"\"\n\nimport logging\n\nfrom homeassistant.const import CONF_EMAIL, CONF_NAME, CONF_UNIQUE_ID\nfrom hom"
  },
  {
    "path": "custom_components/o365/services.yaml",
    "chars": 14776,
    "preview": "scan_for_calendars:\n  name: Scan for new calendars\n  description: \"Scan for newly available calendars\"\n\nscan_for_todo_li"
  },
  {
    "path": "custom_components/o365/strings.json",
    "chars": 1895,
    "preview": "{\n  \"issues\": {\n     \"deprecated_legacy_configuration\": {\n          \"title\": \"Deprecated Secondary/Legacy configuration "
  },
  {
    "path": "custom_components/o365/todo.py",
    "chars": 17974,
    "preview": "\"\"\"Todo processing.\"\"\"\n\nimport logging\nfrom datetime import datetime, timedelta\n\nfrom homeassistant.components.todo impo"
  },
  {
    "path": "custom_components/o365/translations/en.json",
    "chars": 2995,
    "preview": "{\n    \"issues\": {\n        \"deprecated_legacy_configuration\": {\n            \"title\": \"Deprecated Secondary/Legacy configu"
  },
  {
    "path": "custom_components/o365/translations/sk.json",
    "chars": 3062,
    "preview": "{\n    \"issues\": {\n        \"deprecated_legacy_configuration\": {\n            \"title\": \"Zastaraná sekundárna/stará metóda k"
  },
  {
    "path": "custom_components/o365/utils/__init__.py",
    "chars": 18,
    "preview": "\"\"\"Initialise.\"\"\"\n"
  },
  {
    "path": "custom_components/o365/utils/calendar_utils.py",
    "chars": 4776,
    "preview": "\"\"\"Calendar utilities processes.\"\"\"\n\nimport logging\nfrom datetime import datetime, timedelta\n\nfrom O365.calendar import "
  },
  {
    "path": "custom_components/o365/utils/filemgmt.py",
    "chars": 4012,
    "preview": "\"\"\"File management processes.\"\"\"\n\nimport logging\nimport os\n\nimport yaml\nfrom homeassistant.const import CONF_NAME\nfrom v"
  },
  {
    "path": "custom_components/o365/utils/utils.py",
    "chars": 3757,
    "preview": "\"\"\"Utilities processes.\"\"\"\n\nimport logging\n\nfrom bs4 import BeautifulSoup\n\nfrom ..const import (\n    CONF_ACCOUNT,\n    C"
  },
  {
    "path": "docs/_config.yml",
    "chars": 798,
    "preview": "title: O365 Home Assistant\nremote_theme: just-the-docs/just-the-docs\n\nsearch_enabled: true\nsearch:\n  heading_level: 3\n\n#"
  },
  {
    "path": "docs/authentication.md",
    "chars": 2482,
    "preview": "---\ntitle: Authentication\nnav_order: 5\n---\n\n# Authentication\n\nThe Primary method of authentication is the simplest to co"
  },
  {
    "path": "docs/calendar_configuration.md",
    "chars": 2833,
    "preview": "---\ntitle: Calendar Configuration\nnav_order: 6\n---\n\n# Calendar configuration\nThe integration uses an external `o365_cale"
  },
  {
    "path": "docs/calendar_panel.md",
    "chars": 451,
    "preview": "---\ntitle: Calendar Panel\nnav_order: 17\n---\n\nCreation and deletion of events is possible via the Calendar Panel introduc"
  },
  {
    "path": "docs/errors.md",
    "chars": 3513,
    "preview": "---\ntitle: Errors\nnav_order: 99\n---\n\n# Errors\n* **The reply URL specified in the request does not match the reply URLs c"
  },
  {
    "path": "docs/events.md",
    "chars": 3503,
    "preview": "---\ntitle: Events\nnav_order: 16\n---\n\n# Events\n\nThe attribute `ha_event` shows whether the event is triggered by an HA in"
  },
  {
    "path": "docs/index.md",
    "chars": 1179,
    "preview": "---\ntitle: Home\nnav_order: 1\n---\n\n# Office 365 Integration for Home Assistant\n\n## Deprecation Notice:\nThe Calendar, Mail"
  },
  {
    "path": "docs/installation_and_configuration.md",
    "chars": 9048,
    "preview": "---\ntitle: Installation and Configuration\nnav_order: 4\n---\n\n# Installation and Configuration\n## Installation\n1. Ensure y"
  },
  {
    "path": "docs/migration.md",
    "chars": 2760,
    "preview": "---\ntitle: Migration\nnav_order: 19\n---\n\n# Migration\n\n**Please upgrade to the latest version of this O365 integration, pr"
  },
  {
    "path": "docs/permissions.md",
    "chars": 4971,
    "preview": "---\ntitle: Permissions\nnav_order: 3\n---\n\n# Permissions\n\nUnder \"API Permissions\" click Add a permission, then Microsoft G"
  },
  {
    "path": "docs/prerequisites.md",
    "chars": 2276,
    "preview": "---\ntitle: Prerequisites\nnav_order: 2\n---\n\n# Prerequisites\n\n## Note - Personal accounts\nSince the middle of 2024, Micros"
  },
  {
    "path": "docs/sensor.md",
    "chars": 1606,
    "preview": "---\ntitle: Sensors\nnav_order: 8\n---\n\n# Sensors\n## Calendar Sensor\nThe status of the calendar sensor indicates (on/off) w"
  },
  {
    "path": "docs/services.md",
    "chars": 7019,
    "preview": "---\ntitle: Services\nnav_order: 15\n---\n\n# Services\n\n##  Notify Services\n\n### notify.o365_email_xxxxxxxx\n\n#### Service dat"
  },
  {
    "path": "docs/todo.md",
    "chars": 802,
    "preview": "---\ntitle: To-Do Lists\nnav_order: 9\n---\n\n# To-Do Lists\n\nOne To-Do List entity is created for each to-do list on the user"
  },
  {
    "path": "docs/todos_configuration.md",
    "chars": 1073,
    "preview": "---\ntitle: To-Do Configuration\nnav_order: 7\n---\n\n# ToDo configuration\nThe integration uses an external `o365_tasks_<acco"
  },
  {
    "path": "docs/token.md",
    "chars": 502,
    "preview": "---\ntitle: Token\nnav_order: 18\n---\n\n# Token\nAt some point you are very likely need to delete your token so that you re-a"
  },
  {
    "path": "hacs.json",
    "chars": 173,
    "preview": "{\n    \"name\": \"Office 365\",\n    \"zip_release\": true,\n    \"filename\": \"o365.zip\",\n    \"homeassistant\": \"2024.6.0\",\n    \"c"
  },
  {
    "path": "requirements.txt",
    "chars": 44,
    "preview": "O365>=2.1.4\nBeautifulSoup4>=4.10.0\noauthlib\n"
  },
  {
    "path": "requirements_release.txt",
    "chars": 28,
    "preview": "PyGithub>=1.51\nruff==0.14.2\n"
  }
]

About this extraction

This page contains the full source code of the RogerSelwyn/O365-HomeAssistant GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 57 files (336.1 KB), approximately 91.4k tokens, and a symbol index with 244 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!