├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── funding.yaml ├── labels.yml ├── release-drafter.yml ├── renovate.json └── workflows │ ├── documentation.yaml │ ├── hacs.yaml │ ├── hassfest.yaml │ ├── labels.yaml │ ├── pr-labels.yaml │ ├── pylint.yaml │ ├── release-drafter.yaml │ ├── release.yaml │ └── ruff.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode ├── launch.json └── tasks.json ├── LICENSE.md ├── README.md ├── custom_components └── spook │ ├── __init__.py │ ├── binary_sensor.py │ ├── button.py │ ├── config_flow.py │ ├── const.py │ ├── ectoplasms │ ├── __init__.py │ ├── automation │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ ├── unknown_area_references.py │ │ │ ├── unknown_device_references.py │ │ │ ├── unknown_entity_references.py │ │ │ ├── unknown_floor_references.py │ │ │ ├── unknown_label_references.py │ │ │ └── unknown_service_references.py │ ├── blueprint │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ └── importer.py │ ├── cloud │ │ ├── __init__.py │ │ ├── entity.py │ │ └── switch.py │ ├── group │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ └── unknown_members.py │ ├── homeassistant │ │ ├── __init__.py │ │ ├── button.py │ │ ├── entity.py │ │ ├── sensor.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── add_alias_to_area.py │ │ │ ├── add_alias_to_floor.py │ │ │ ├── add_area_to_floor.py │ │ │ ├── add_device_to_area.py │ │ │ ├── add_entity_to_area.py │ │ │ ├── add_label_to_area.py │ │ │ ├── add_label_to_device.py │ │ │ ├── add_label_to_entity.py │ │ │ ├── create_area.py │ │ │ ├── create_floor.py │ │ │ ├── create_label.py │ │ │ ├── delete_all_orphaned_entities.py │ │ │ ├── delete_area.py │ │ │ ├── delete_floor.py │ │ │ ├── delete_label.py │ │ │ ├── disable_config_entry.py │ │ │ ├── disable_device.py │ │ │ ├── disable_entity.py │ │ │ ├── disable_polling.py │ │ │ ├── enable_config_entry.py │ │ │ ├── enable_device.py │ │ │ ├── enable_entity.py │ │ │ ├── enable_polling.py │ │ │ ├── hide_entity.py │ │ │ ├── ignore_all_discovered.py │ │ │ ├── list_orphaned_database_entities.py │ │ │ ├── remove_alias_from_area.py │ │ │ ├── remove_alias_from_floor.py │ │ │ ├── remove_area_from_floor.py │ │ │ ├── remove_device_from_area.py │ │ │ ├── remove_entity_from_area.py │ │ │ ├── remove_label_from_area.py │ │ │ ├── remove_label_from_device.py │ │ │ ├── remove_label_from_entity.py │ │ │ ├── rename_entity.py │ │ │ ├── restart.py │ │ │ ├── set_area_aliases.py │ │ │ ├── set_floor_aliases.py │ │ │ ├── unhide_entity.py │ │ │ └── update_entity_id.py │ ├── input_number │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── decrement.py │ │ │ ├── increment.py │ │ │ ├── max.py │ │ │ └── min.py │ ├── input_select │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── random.py │ │ │ ├── shuffle.py │ │ │ └── sort.py │ ├── integration │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ └── unknown_source.py │ ├── lovelace │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ └── unknown_entity_references.py │ ├── number │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── decrement.py │ │ │ ├── increment.py │ │ │ ├── max.py │ │ │ └── min.py │ ├── person │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── add_device_tracker.py │ │ │ └── remove_device_tracker.py │ ├── proximity │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ ├── unknown_ignored_zones.py │ │ │ ├── unknown_tracked_entities.py │ │ │ └── unknown_zone.py │ ├── recorder │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ └── import_statistics.py │ ├── repairs │ │ ├── __init__.py │ │ ├── button.py │ │ ├── entity.py │ │ ├── event.py │ │ ├── sensor.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── create.py │ │ │ ├── ignore_all.py │ │ │ ├── remove.py │ │ │ └── unignore_all.py │ ├── scene │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ └── unknown_entity_references.py │ ├── script │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ ├── unknown_area_references.py │ │ │ ├── unknown_device_references.py │ │ │ ├── unknown_entity_references.py │ │ │ ├── unknown_floor_references.py │ │ │ └── unknown_label_references.py │ ├── select │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ └── random.py │ ├── spook │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── boo.py │ │ │ └── random_fail.py │ ├── switch_as_x │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ └── unknown_source.py │ ├── timer │ │ ├── __init__.py │ │ └── services │ │ │ ├── __init__.py │ │ │ └── set_duration.py │ ├── trend │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ └── unknown_source.py │ ├── utility_meter │ │ ├── __init__.py │ │ └── repairs │ │ │ ├── __init__.py │ │ │ └── unknown_source.py │ └── zone │ │ ├── __init__.py │ │ └── services │ │ ├── __init__.py │ │ ├── create.py │ │ ├── delete.py │ │ └── update.py │ ├── entity.py │ ├── event.py │ ├── integrations │ ├── __init__.py │ └── spook_inverse │ │ ├── __init__.py │ │ ├── binary_sensor.py │ │ ├── config_flow.py │ │ ├── const.py │ │ ├── entity.py │ │ ├── manifest.json │ │ ├── switch.py │ │ └── translations │ │ ├── af.json │ │ ├── ar.json │ │ ├── bg.json │ │ ├── ca.json │ │ ├── cs.json │ │ ├── da.json │ │ ├── de.json │ │ ├── el.json │ │ ├── en.json │ │ ├── es.json │ │ ├── et.json │ │ ├── fi.json │ │ ├── fr.json │ │ ├── gl.json │ │ ├── he.json │ │ ├── hr.json │ │ ├── hu.json │ │ ├── id.json │ │ ├── it.json │ │ ├── ja.json │ │ ├── ka.json │ │ ├── ko.json │ │ ├── lb.json │ │ ├── lv.json │ │ ├── mt.json │ │ ├── nb_NO.json │ │ ├── nl.json │ │ ├── pl.json │ │ ├── pt.json │ │ ├── pt_BR.json │ │ ├── ro.json │ │ ├── ru.json │ │ ├── sk.json │ │ ├── sl.json │ │ ├── sv.json │ │ ├── th.json │ │ ├── tr.json │ │ ├── uk.json │ │ ├── ur.json │ │ ├── zh_Hans.json │ │ └── zh_Hant.json │ ├── manifest.json │ ├── number.py │ ├── repairs.py │ ├── select.py │ ├── sensor.py │ ├── services.py │ ├── services.yaml │ ├── switch.py │ ├── time.py │ ├── translations │ ├── LICENSE.md │ ├── af.json │ ├── ar.json │ ├── bg.json │ ├── ca.json │ ├── cs.json │ ├── da.json │ ├── de.json │ ├── el.json │ ├── en.json │ ├── es.json │ ├── et.json │ ├── fi.json │ ├── fr.json │ ├── gl.json │ ├── he.json │ ├── hr.json │ ├── hu.json │ ├── id.json │ ├── it.json │ ├── ja.json │ ├── ka.json │ ├── ko.json │ ├── lb.json │ ├── lv.json │ ├── mt.json │ ├── nb_NO.json │ ├── nl.json │ ├── pl.json │ ├── pt-BR.json │ ├── pt.json │ ├── ro.json │ ├── ru.json │ ├── sk.json │ ├── sl.json │ ├── sv.json │ ├── th.json │ ├── tr.json │ ├── uk.json │ ├── ur.json │ ├── zh_Hans.json │ └── zh_Hant.json │ └── util.py ├── documentation ├── .nvmrc ├── _toc.yml ├── about.md ├── actions.md ├── areas.md ├── background_and_history.md ├── blueprints.md ├── code_of_conduct.md ├── core_extensions.md ├── development.md ├── devices.md ├── devices_entities.md ├── disclaimers.md ├── enhanced_integrations.md ├── entities.md ├── faq.md ├── features.md ├── floors.md ├── glossary.md ├── helpers.md ├── helpers │ └── inverse.md ├── images │ ├── areas │ │ ├── add_alias.png │ │ ├── add_device.png │ │ ├── add_entity.png │ │ ├── create.png │ │ ├── delete.png │ │ ├── example.png │ │ ├── remove_alias.png │ │ ├── remove_device.png │ │ ├── remove_entity.png │ │ └── set_aliases.png │ ├── devices │ │ ├── disable_device.png │ │ └── enable_device.png │ ├── devices_entities │ │ └── example.png │ ├── entities │ │ ├── delete_all_orphaned_entities.png │ │ ├── disable_entity.png │ │ ├── enable_entity.png │ │ ├── hide_entity.png │ │ ├── list_orphaned_database_entities.png │ │ ├── rename_entity.png │ │ ├── unhide_entity.png │ │ └── update_entity_id.png │ ├── floors │ │ ├── add_alias.png │ │ ├── add_area.png │ │ ├── create.png │ │ ├── delete.png │ │ ├── example.png │ │ ├── remove_alias.png │ │ ├── remove_area.png │ │ └── set_aliases.png │ ├── helpers │ │ └── inverse │ │ │ ├── configure.png │ │ │ ├── done.png │ │ │ ├── helper_dialog.png │ │ │ └── select_entity_type.png │ ├── installation │ │ ├── accept_license.png │ │ ├── add_integration.png │ │ ├── experimental_restart_home_assistant.png │ │ ├── find_spook.png │ │ ├── hacs_experimental_download.png │ │ ├── hacs_experimental_download_fab.png │ │ ├── hacs_experimental_find_spook.png │ │ └── restart_home_assistant.png │ ├── integration │ │ ├── disable_config_entry.png │ │ ├── disable_polling.png │ │ ├── enable_config_entry.png │ │ ├── enable_polling.png │ │ └── ignore_all_discovered.png │ ├── integrations │ │ ├── automation │ │ │ ├── example.png │ │ │ ├── unknown_areas.png │ │ │ └── unknown_device.png │ │ ├── blueprint │ │ │ └── example.png │ │ ├── cloud │ │ │ └── example.png │ │ ├── group │ │ │ └── example.png │ │ ├── input_number │ │ │ ├── decrease.png │ │ │ ├── example.png │ │ │ ├── increase.png │ │ │ ├── maximum.png │ │ │ └── minimum.png │ │ ├── input_select │ │ │ ├── example.png │ │ │ ├── random.png │ │ │ ├── shuffle.png │ │ │ └── sort.png │ │ ├── lovelace │ │ │ └── unknown_entity.png │ │ ├── number │ │ │ ├── decrease.png │ │ │ ├── example.png │ │ │ ├── increase.png │ │ │ ├── maximum.png │ │ │ └── minimum.png │ │ ├── person │ │ │ ├── add_device_tracker.png │ │ │ ├── example.png │ │ │ └── remove_device_tracker.png │ │ ├── recorder │ │ │ ├── example.png │ │ │ └── import.png │ │ ├── repairs │ │ │ ├── create.png │ │ │ ├── create_result.png │ │ │ ├── devices_entities.png │ │ │ ├── example.png │ │ │ ├── ignore_all.png │ │ │ ├── remove.png │ │ │ └── unignore_all.png │ │ ├── scene │ │ │ └── unknown_entity.png │ │ ├── script │ │ │ ├── example.png │ │ │ ├── unknown_area.png │ │ │ └── unknown_device.png │ │ ├── select │ │ │ └── example.png │ │ └── zone │ │ │ ├── create.png │ │ │ ├── delete.png │ │ │ ├── example.png │ │ │ └── update.png │ ├── labels │ │ ├── add_to_area.png │ │ ├── add_to_device.png │ │ ├── add_to_entity.png │ │ ├── create.png │ │ ├── delete.png │ │ ├── example.png │ │ ├── remove_from_area.png │ │ ├── remove_from_device.png │ │ └── remove_from_entity.png │ ├── logo.png │ ├── logo_dark.png │ ├── misc │ │ └── restart.png │ ├── social.png │ ├── spook │ │ ├── boo.png │ │ └── random_fail.png │ ├── support.png │ └── usage │ │ ├── device_example.png │ │ ├── repairs_example.png │ │ └── services_example.png ├── index.md ├── installation.md ├── integrations.md ├── integrations │ ├── _template.md │ ├── automation.md │ ├── blueprint.md │ ├── cloud.md │ ├── group.md │ ├── input_number.md │ ├── input_select.md │ ├── integration.md │ ├── lovelace.md │ ├── number.md │ ├── person.md │ ├── proximity.md │ ├── recorder.md │ ├── repairs.md │ ├── scene.md │ ├── script.md │ ├── select.md │ ├── switch_as_x.md │ ├── timer.md │ ├── trend.md │ ├── utility_meter.md │ └── zone.md ├── integrations_devices.md ├── labels.md ├── license.md ├── media.md ├── misc.md ├── myst.yml ├── other_features.md ├── previous_features.md ├── public │ └── favicon.ico ├── security.md ├── support.md └── usage.md ├── hacs.json ├── logos ├── icon_1024x1024.png ├── icon_128x128.png ├── icon_2048x2048.png ├── icon_256x256.png ├── icon_32x32.png ├── icon_512x512.png ├── icon_64x64.png ├── icon_transparent_1024x1024.png ├── icon_transparent_128x128.png ├── icon_transparent_2048x2048.png ├── icon_transparent_256x256.png ├── icon_transparent_32x32.png ├── icon_transparent_512x512.png ├── icon_transparent_64x64.png ├── logo_wordmark_1024x1024.png ├── logo_wordmark_1024x256.png ├── logo_wordmark_128x128.png ├── logo_wordmark_2048x2048.png ├── logo_wordmark_2048x512.png ├── logo_wordmark_256_64.png ├── logo_wordmark_256x256.png ├── logo_wordmark_512x128.png ├── logo_wordmark_512x512.png ├── logo_wordmark_catchphrase_1024x1024.png ├── logo_wordmark_catchphrase_1024x256.png ├── logo_wordmark_catchphrase_2048x2048.png ├── logo_wordmark_catchphrase_2048x512.png ├── logo_wordmark_catchphrase_256x256.png ├── logo_wordmark_catchphrase_256x64.png ├── logo_wordmark_catchphrase_512x128.png ├── logo_wordmark_catchphrase_512x512.png ├── logo_wordmark_catchphrase_margin_1024x1024.png ├── logo_wordmark_catchphrase_margin_2048x2048.png ├── logo_wordmark_catchphrase_margin_512x512.png ├── logo_wordmark_catchphrase_margin_alt_1024x1024.png ├── logo_wordmark_catchphrase_margin_alt_2048x2048.png ├── logo_wordmark_catchphrase_margin_alt_256x256.png ├── logo_wordmark_catchphrase_margin_alt_512x512.png ├── logo_wordmark_catchphrase_margin_transparent_1024x1024.png ├── logo_wordmark_catchphrase_margin_transparent_2048x2048.png ├── logo_wordmark_catchphrase_margin_transparent_512x512.png ├── logo_wordmark_catchphrase_margin_transparent_alt_1024x1024.png ├── logo_wordmark_catchphrase_margin_transparent_alt_2048x2048.png ├── logo_wordmark_catchphrase_margin_transparent_alt_256x256.png ├── logo_wordmark_catchphrase_margin_transparent_alt_512x512.png ├── logo_wordmark_catchphrase_transparent_1024x1024.png ├── logo_wordmark_catchphrase_transparent_1024x256.png ├── logo_wordmark_catchphrase_transparent_128x64.png ├── logo_wordmark_catchphrase_transparent_2048x2048.png ├── logo_wordmark_catchphrase_transparent_2048x512.png ├── logo_wordmark_catchphrase_transparent_256x256.png ├── logo_wordmark_catchphrase_transparent_512x128.png ├── logo_wordmark_catchphrase_transparent_512x512.png ├── logo_wordmark_transparent_1024x1024.png ├── logo_wordmark_transparent_1024x256.png ├── logo_wordmark_transparent_128x128.png ├── logo_wordmark_transparent_2048x2048.png ├── logo_wordmark_transparent_2048x512.png ├── logo_wordmark_transparent_256x256.png ├── logo_wordmark_transparent_256x64.png ├── logo_wordmark_transparent_512x128.png └── logo_wordmark_transparent_512x512.png ├── pyproject.toml ├── sonar-project.properties └── uv.lock /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/python:3.13 2 | 3 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 4 | 5 | # Install dependencies needed by Home Assistant or its dependencies 6 | RUN \ 7 | apt-get update \ 8 | && DEBIAN_FRONTEND=noninteractive \ 9 | apt-get install -y --no-install-recommends \ 10 | build-essential \ 11 | clang \ 12 | cmake \ 13 | ffmpeg \ 14 | gcc \ 15 | git \ 16 | libavcodec-dev \ 17 | libavdevice-dev \ 18 | libavfilter-dev \ 19 | libavformat-dev \ 20 | libavutil-dev \ 21 | libffi-dev \ 22 | liblzma-dev \ 23 | libpcap-dev \ 24 | libssl-dev \ 25 | libswresample-dev \ 26 | libswscale-dev \ 27 | libturbojpeg0 \ 28 | libudev-dev \ 29 | libxml2 \ 30 | libxmlsec1-dev \ 31 | libyaml-dev \ 32 | lzma-dev \ 33 | uuid-dev \ 34 | webp \ 35 | xz-utils \ 36 | zlib1g-dev 37 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_size = 2 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | You can find all information on development and contributing on our website: 4 | 5 | https://spook.boo/development 6 | 7 | ../Frenck 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I've got an idea for something to add to Spook 4 | url: https://github.com/frenck/spook/discussions/categories/ideas 5 | about: Please use our dicussion form to share your ideas. 6 | - name: I have a question or need support 7 | url: https://github.com/frenck/spook/discussions 8 | about: Please use our discussion forums for getting help in that case. 9 | - name: I'm unsure where to go 10 | url: https://spook.boo/support 11 | about: Check out documentation for more details and guidance in that case. 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Motivation and Context 8 | 9 | 10 | 11 | 12 | ## How has this been tested? 13 | 14 | 15 | 16 | 17 | 18 | ## Screenshots (if appropriate): 19 | 20 | 21 | 22 | ## Types of changes 23 | 24 | 25 | 26 | - [ ] Bug fix (non-breaking change which fixes an issue) 27 | - [ ] New feature (non-breaking change which adds functionality) 28 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 29 | - [ ] Other 30 | 31 | ## Checklist 32 | 33 | 34 | 35 | 36 | - [ ] My code follows the code style of this project. 37 | - [ ] My change requires a change to the documentation. 38 | - [ ] I have updated the documentation accordingly. 39 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | The security policy of Spook is fully documented here: 4 | 5 | https://spook.boo/security 6 | -------------------------------------------------------------------------------- /.github/funding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | github: frenck 3 | patreon: frenck 4 | custom: https://frenck.dev/donate/ 5 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name-template: "v$RESOLVED_VERSION" 3 | tag-template: "v$RESOLVED_VERSION" 4 | change-template: "- $TITLE @$AUTHOR (#$NUMBER)" 5 | sort-direction: ascending 6 | 7 | categories: 8 | - title: "🚨 Breaking changes" 9 | labels: 10 | - "breaking-change" 11 | - title: "✨ New features" 12 | labels: 13 | - "new-feature" 14 | - title: "🐛 Bug fixes" 15 | labels: 16 | - "bugfix" 17 | - title: "🚀 Enhancements" 18 | labels: 19 | - "enhancement" 20 | - "refactor" 21 | - "performance" 22 | - title: "🧰 Maintenance" 23 | labels: 24 | - "maintenance" 25 | - "ci" 26 | - title: "📚 Documentation" 27 | labels: 28 | - "documentation" 29 | - title: "⬆️ Dependency updates" 30 | labels: 31 | - "dependencies" 32 | 33 | version-resolver: 34 | major: 35 | labels: 36 | - "major" 37 | - "breaking-change" 38 | minor: 39 | labels: 40 | - "minor" 41 | - "new-feature" 42 | patch: 43 | labels: 44 | - "bugfix" 45 | - "chore" 46 | - "ci" 47 | - "dependencies" 48 | - "documentation" 49 | - "enhancement" 50 | - "performance" 51 | - "refactor" 52 | default: patch 53 | 54 | template: | 55 | # 👻👻👻👻👻👻👻👻👻👻 56 | 57 | $CHANGES 58 | 59 | # 👻👻👻👻👻👻👻👻👻👻 60 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "rebaseWhen": "behind-base-branch", 4 | "dependencyDashboard": true, 5 | "labels": ["dependencies", "no-stale"], 6 | "lockFileMaintenance": { 7 | "enabled": true, 8 | "automerge": true 9 | }, 10 | "commitMessagePrefix": "⬆️", 11 | "packageRules": [ 12 | { 13 | "matchManagers": ["npm", "nvm"], 14 | "addLabels": ["javascript"], 15 | "rangeStrategy": "pin" 16 | }, 17 | { 18 | "matchManagers": ["npm", "nvm"], 19 | "matchUpdateTypes": ["minor", "patch"], 20 | "automerge": true 21 | }, 22 | { 23 | "matchManagers": ["github-actions"], 24 | "addLabels": ["github_actions"], 25 | "rangeStrategy": "pin" 26 | }, 27 | { 28 | "matchManagers": ["github-actions"], 29 | "matchUpdateTypes": ["minor", "patch"], 30 | "automerge": true 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | 4 | on: 5 | push: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: ⤵️ Check out code from GitHub 14 | uses: actions/checkout@v4.2.2 15 | 16 | - name: 🏗 Set up Node.js 17 | uses: actions/setup-node@v4.4.0 18 | with: 19 | node-version-file: "documentation/.nvmrc" 20 | 21 | - name: 🏗 Install MyST 22 | run: npm install -g mystmd 23 | 24 | - name: 🚀 Build documentation 25 | working-directory: ./documentation 26 | run: myst build --html 27 | 28 | - name: ©️ Public folder 29 | run: cp ./documentation/public/* ./documentation/_build/html/ 30 | 31 | - name: 🆙 Upload artifact 32 | uses: actions/upload-pages-artifact@v3 33 | with: 34 | path: ./documentation/_build/html 35 | 36 | deploy: 37 | name: Deploy 38 | runs-on: ubuntu-latest 39 | needs: build 40 | if: github.ref == 'refs/heads/main' 41 | permissions: 42 | pages: write 43 | id-token: write 44 | environment: 45 | name: github-pages 46 | url: ${{ steps.deployment.outputs.page_url }} 47 | steps: 48 | - name: 🚀 Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v4.0.5 51 | -------------------------------------------------------------------------------- /.github/workflows/hacs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: HACS Validation 3 | 4 | on: 5 | pull_request: 6 | push: 7 | schedule: 8 | - cron: "0 0 * * *" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | hacs: 13 | name: HACS Validation 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: ⤵️ Check out code from GitHub 17 | uses: actions/checkout@v4.2.2 18 | 19 | - name: 🚀 Run HACS validation 20 | uses: hacs/action@main 21 | with: 22 | category: integration 23 | -------------------------------------------------------------------------------- /.github/workflows/hassfest.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Hassfest 3 | 4 | on: 5 | pull_request: 6 | push: 7 | schedule: 8 | - cron: "0 0 * * *" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | hassfest: 13 | name: Hassfest 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: ⤵️ Check out code from GitHub 17 | uses: actions/checkout@v4.2.2 18 | 19 | - name: 🚀 Run hassfest validation 20 | uses: home-assistant/actions/hassfest@master 21 | -------------------------------------------------------------------------------- /.github/workflows/labels.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Sync labels 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | branches: 8 | - main 9 | paths: 10 | - .github/labels.yml 11 | workflow_dispatch: 12 | 13 | jobs: 14 | labels: 15 | name: ♻️ Sync labels 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: ⤵️ Check out code from GitHub 19 | uses: actions/checkout@v4.2.2 20 | 21 | - name: 🚀 Run Label Syncer 22 | uses: micnncim/action-label-syncer@v1.3.0 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/pr-labels.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: PR Labels 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | pull_request_target: 7 | types: 8 | - opened 9 | - labeled 10 | - unlabeled 11 | - synchronize 12 | workflow_call: 13 | 14 | jobs: 15 | pr_labels: 16 | name: Verify 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: 🏷 Verify PR has a valid label 20 | uses: jesusvasquez333/verify-pr-label-action@v1.4.0 21 | with: 22 | pull-request-number: "${{ github.event.pull_request.number }}" 23 | github-token: "${{ secrets.GITHUB_TOKEN }}" 24 | valid-labels: >- 25 | breaking-change, bugfix, documentation, enhancement, 26 | refactor, performance, new-feature, maintenance, ci, dependencies 27 | disable-reviews: true 28 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pylint 3 | 4 | on: 5 | pull_request: 6 | push: 7 | workflow_dispatch: 8 | 9 | env: 10 | DEFAULT_PYTHON: "3.13" 11 | 12 | jobs: 13 | pylint: 14 | name: Pylint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: ⤵️ Check out code from GitHub 18 | uses: actions/checkout@v4.2.2 19 | 20 | - name: 🏗 Set up uv 21 | uses: astral-sh/setup-uv@v6.1.0 22 | with: 23 | enable-cache: true 24 | python-version: ${{ env.DEFAULT_PYTHON }} 25 | 26 | - name: 🏗 Install dependencies 27 | run: uv sync --frozen --dev 28 | 29 | - name: 🚀 Run pylint 30 | run: uv run pre-commit run pylint --all-files 31 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release Drafter 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | 11 | jobs: 12 | update_release_draft: 13 | name: ✏️ Draft release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: 🚀 Run Release Drafter 17 | uses: release-drafter/release-drafter@v6.1.0 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: 5 | release: 6 | types: 7 | - published 8 | 9 | jobs: 10 | release: 11 | name: Release the Spook! 👻 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | id-token: write 16 | steps: 17 | - name: ⤵️ Check out code from GitHub 18 | uses: actions/checkout@v4.2.2 19 | 20 | - name: 🔢 Adjust version number 21 | shell: bash 22 | run: | 23 | version="${{ github.event.release.tag_name }}" 24 | version="${version,,}" 25 | version="${version#v}" 26 | yq e -P -o=json \ 27 | -i ".version = \"${version}\"" \ 28 | "${{ github.workspace }}/custom_components/spook/manifest.json" 29 | yq e -P -o=json \ 30 | -i ".version = \"${version}\"" \ 31 | "${{ github.workspace }}/custom_components/spook/integrations/spook_inverse/manifest.json" 32 | 33 | - name: 📦 Created zipped release package 34 | shell: bash 35 | run: | 36 | cd "${{ github.workspace }}/custom_components/spook" 37 | zip spook.zip -r ./ 38 | 39 | - name: 🔏 Sign release package 40 | uses: sigstore/gh-action-sigstore-python@v3.0.0 41 | with: 42 | inputs: ${{ github.workspace }}/custom_components/spook/spook.zip 43 | 44 | - name: ⬆️ Upload zip to release 45 | uses: softprops/action-gh-release@v2.2.2 46 | with: 47 | files: ${{ github.workspace }}/custom_components/spook/spook.zip 48 | 49 | # Upload this one in the future, currently problematic for HACS 50 | # https://github.com/frenck/spook/issues/286 51 | # ${{ github.workspace }}/custom_components/spook/spook.zip.sigstore 52 | -------------------------------------------------------------------------------- /.github/workflows/ruff.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ruff 3 | 4 | on: 5 | pull_request: 6 | push: 7 | workflow_dispatch: 8 | 9 | env: 10 | DEFAULT_PYTHON: "3.13" 11 | 12 | jobs: 13 | ruff: 14 | name: Ruff 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: ⤵️ Check out code from GitHub 18 | uses: actions/checkout@v4.2.2 19 | 20 | - name: 🏗 Set up uv 21 | uses: astral-sh/setup-uv@v6.1.0 22 | with: 23 | enable-cache: true 24 | python-version: ${{ env.DEFAULT_PYTHON }} 25 | 26 | - name: 🏗 Install dependencies 27 | run: uv sync --frozen --dev 28 | 29 | - name: 🚀 Run ruff linter 30 | run: uv run ruff check --output-format=github . 31 | 32 | - name: 🚀 Run ruff formatter 33 | run: uv run ruff format --check . 34 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Home Assistant with Spook", 6 | "type": "debugpy", 7 | "request": "launch", 8 | "python": "${workspaceFolder}/.venv/bin/python", 9 | "module": "homeassistant", 10 | "cwd": "${workspaceFolder}", 11 | "justMyCode": false, 12 | "args": ["--debug", "-c", "config"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Preview documentation", 6 | "type": "shell", 7 | "command": "cd documentation && myst start", 8 | "problemMatcher": [], 9 | "presentation": { 10 | "reveal": "always", 11 | "panel": "new" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023-2025 Franck Nijhof 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /custom_components/spook/binary_sensor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.const import Platform 8 | 9 | from .util import async_forward_platform_entry_setups_to_ectoplasm 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | 16 | 17 | async def async_setup_entry( 18 | hass: HomeAssistant, 19 | entry: ConfigEntry, 20 | async_add_entities: AddEntitiesCallback, 21 | ) -> None: 22 | """Set up Spook binary sensors.""" 23 | await async_forward_platform_entry_setups_to_ectoplasm( 24 | hass, 25 | entry, 26 | async_add_entities, 27 | Platform.BINARY_SENSOR, 28 | ) 29 | -------------------------------------------------------------------------------- /custom_components/spook/button.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.const import Platform 8 | 9 | from .util import async_forward_platform_entry_setups_to_ectoplasm 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | 16 | 17 | async def async_setup_entry( 18 | hass: HomeAssistant, 19 | entry: ConfigEntry, 20 | async_add_entities: AddEntitiesCallback, 21 | ) -> None: 22 | """Set up Spook buttons.""" 23 | await async_forward_platform_entry_setups_to_ectoplasm( 24 | hass, 25 | entry, 26 | async_add_entities, 27 | Platform.BUTTON, 28 | ) 29 | -------------------------------------------------------------------------------- /custom_components/spook/const.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | import logging 4 | from typing import Final 5 | 6 | from homeassistant.const import Platform 7 | 8 | DOMAIN: Final = "spook" 9 | LOGGER = logging.getLogger(__package__) 10 | 11 | PLATFORMS: Final = [ 12 | Platform.BINARY_SENSOR, 13 | Platform.BUTTON, 14 | Platform.EVENT, 15 | Platform.NUMBER, 16 | Platform.SELECT, 17 | Platform.SENSOR, 18 | Platform.SWITCH, 19 | Platform.TIME, 20 | ] 21 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/automation/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/automation/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/blueprint/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/blueprint/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/cloud/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/cloud/entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.cloud.const import DOMAIN as CLOUD_DOMAIN 8 | from homeassistant.helpers.device_registry import DeviceInfo 9 | 10 | from ...const import DOMAIN 11 | from ...entity import SpookEntity, SpookEntityDescription 12 | 13 | if TYPE_CHECKING: 14 | from hass_nabucasa import Cloud 15 | 16 | from homeassistant.components.cloud.client import CloudClient 17 | 18 | 19 | class HomeAssistantCloudSpookEntity(SpookEntity): 20 | """Defines an base Spook entity for Home Assistant Cloud related entities.""" 21 | 22 | def __init__( 23 | self, cloud: Cloud[CloudClient], description: SpookEntityDescription 24 | ) -> None: 25 | """Initialize the entity.""" 26 | super().__init__(description=description) 27 | self._cloud = cloud 28 | self._attr_device_info = DeviceInfo( 29 | identifiers={(DOMAIN, CLOUD_DOMAIN)}, 30 | manufacturer="Nabu Casa Inc.", 31 | name="Home Assistant Cloud", 32 | configuration_url="https://account.nabucasa.com/", 33 | ) 34 | self._attr_unique_id = f"{CLOUD_DOMAIN}_{description.key}" 35 | 36 | @property 37 | def available(self) -> bool: 38 | """Return if cloud services are available.""" 39 | return ( 40 | super().available and self._cloud.is_logged_in and self._cloud.is_connected 41 | ) 42 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/group/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/group/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from homeassistant.components import homeassistant 6 | from homeassistant.const import __version__ 7 | from homeassistant.helpers.device_registry import DeviceInfo 8 | 9 | from ...const import DOMAIN 10 | from ...entity import SpookEntity, SpookEntityDescription 11 | 12 | 13 | class HomeAssistantSpookEntity(SpookEntity): 14 | """Defines an base Spook entity for Home Assistant related entities.""" 15 | 16 | def __init__(self, description: SpookEntityDescription) -> None: 17 | """Initialize the entity.""" 18 | super().__init__(description=description) 19 | self._attr_device_info = DeviceInfo( 20 | identifiers={(DOMAIN, homeassistant.DOMAIN)}, 21 | manufacturer="Home Assistant", 22 | name="Home Assistant", 23 | sw_version=__version__, 24 | ) 25 | self._attr_unique_id = f"{homeassistant.DOMAIN}_{description.key}" 26 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/add_alias_to_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import area_registry as ar, config_validation as cv 12 | 13 | from ....services import AbstractSpookAdminService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookAdminService): 20 | """Home Assistant service to add an alias to an area.""" 21 | 22 | domain = DOMAIN 23 | service = "add_alias_to_area" 24 | schema = { 25 | vol.Required("area_id"): cv.string, 26 | vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]), 27 | } 28 | 29 | async def async_handle_service(self, call: ServiceCall) -> None: 30 | """Handle the service call.""" 31 | area_registry = ar.async_get(self.hass) 32 | if not (area := area_registry.async_get_area(call.data["area_id"])): 33 | msg = f"Area {call.data['area_id']} not found" 34 | raise HomeAssistantError(msg) 35 | 36 | aliases = area.aliases.copy() 37 | area_registry.async_update( 38 | call.data["area_id"], 39 | aliases=aliases.union(call.data["alias"]), 40 | ) 41 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/add_alias_to_floor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import ( 12 | config_validation as cv, 13 | floor_registry as fr, 14 | ) 15 | 16 | from ....services import AbstractSpookAdminService 17 | 18 | if TYPE_CHECKING: 19 | from homeassistant.core import ServiceCall 20 | 21 | 22 | class SpookService(AbstractSpookAdminService): 23 | """Home Assistant service to add an alias to a floor.""" 24 | 25 | domain = DOMAIN 26 | service = "add_alias_to_floor" 27 | schema = { 28 | vol.Required("floor_id"): cv.string, 29 | vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]), 30 | } 31 | 32 | async def async_handle_service(self, call: ServiceCall) -> None: 33 | """Handle the service call.""" 34 | floor_registry = fr.async_get(self.hass) 35 | if not (floor := floor_registry.async_get_floor(call.data["floor_id"])): 36 | msg = f"Floor {call.data['floor_id']} not found" 37 | raise HomeAssistantError(msg) 38 | 39 | aliases = floor.aliases.copy() 40 | floor_registry.async_update( 41 | call.data["floor_id"], 42 | aliases=aliases.union(call.data["alias"]), 43 | ) 44 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/add_area_to_floor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import ( 12 | area_registry as ar, 13 | config_validation as cv, 14 | floor_registry as fr, 15 | ) 16 | 17 | from ....services import AbstractSpookAdminService 18 | 19 | if TYPE_CHECKING: 20 | from homeassistant.core import ServiceCall 21 | 22 | 23 | class SpookService(AbstractSpookAdminService): 24 | """Home Assistant service to add an area to a floor.""" 25 | 26 | domain = DOMAIN 27 | service = "add_area_to_floor" 28 | schema = { 29 | vol.Required("floor_id"): cv.string, 30 | vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]), 31 | } 32 | 33 | async def async_handle_service(self, call: ServiceCall) -> None: 34 | """Handle the service call.""" 35 | floor_registry = fr.async_get(self.hass) 36 | if not floor_registry.async_get_floor(call.data["floor_id"]): 37 | msg = f"Floor {call.data['floor_id']} not found" 38 | raise HomeAssistantError(msg) 39 | 40 | area_registry = ar.async_get(self.hass) 41 | for area_id in call.data["area_id"]: 42 | area_registry.async_update( 43 | area_id=area_id, 44 | floor_id=call.data["floor_id"], 45 | ) 46 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/add_device_to_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import ( 12 | area_registry as ar, 13 | config_validation as cv, 14 | device_registry as dr, 15 | ) 16 | 17 | from ....services import AbstractSpookAdminService 18 | 19 | if TYPE_CHECKING: 20 | from homeassistant.core import ServiceCall 21 | 22 | 23 | class SpookService(AbstractSpookAdminService): 24 | """Home Assistant service to add a device to an area.""" 25 | 26 | domain = DOMAIN 27 | service = "add_device_to_area" 28 | schema = { 29 | vol.Required("area_id"): cv.string, 30 | vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]), 31 | } 32 | 33 | async def async_handle_service(self, call: ServiceCall) -> None: 34 | """Handle the service call.""" 35 | area_registry = ar.async_get(self.hass) 36 | if not area_registry.async_get_area(call.data["area_id"]): 37 | msg = f"Area {call.data['area_id']} not found" 38 | raise HomeAssistantError(msg) 39 | 40 | device_registry = dr.async_get(self.hass) 41 | for device_id in call.data["device_id"]: 42 | device_registry.async_update_device( 43 | device_id, 44 | area_id=call.data["area_id"], 45 | ) 46 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/add_entity_to_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import ( 12 | area_registry as ar, 13 | config_validation as cv, 14 | entity_registry as er, 15 | ) 16 | 17 | from ....services import AbstractSpookAdminService 18 | 19 | if TYPE_CHECKING: 20 | from homeassistant.core import ServiceCall 21 | 22 | 23 | class SpookService(AbstractSpookAdminService): 24 | """Home Assistant service to add a entity to an area.""" 25 | 26 | domain = DOMAIN 27 | service = "add_entity_to_area" 28 | schema = { 29 | vol.Required("area_id"): cv.string, 30 | vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]), 31 | } 32 | 33 | async def async_handle_service(self, call: ServiceCall) -> None: 34 | """Handle the service call.""" 35 | area_registry = ar.async_get(self.hass) 36 | if not area_registry.async_get_area(call.data["area_id"]): 37 | msg = f"Area {call.data['area_id']} not found" 38 | raise HomeAssistantError(msg) 39 | 40 | entity_registry = er.async_get(self.hass) 41 | for entity_id in call.data["entity_id"]: 42 | entity_registry.async_update_entity( 43 | entity_id, 44 | area_id=call.data["area_id"], 45 | ) 46 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/add_label_to_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import ( 12 | area_registry as ar, 13 | config_validation as cv, 14 | label_registry as lr, 15 | ) 16 | 17 | from ....services import AbstractSpookAdminService 18 | 19 | if TYPE_CHECKING: 20 | from homeassistant.core import ServiceCall 21 | 22 | 23 | class SpookService(AbstractSpookAdminService): 24 | """Home Assistant service to add a label to an area.""" 25 | 26 | domain = DOMAIN 27 | service = "add_label_to_area" 28 | schema = { 29 | vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]), 30 | vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]), 31 | } 32 | 33 | async def async_handle_service(self, call: ServiceCall) -> None: 34 | """Handle the service call.""" 35 | label_registry = lr.async_get(self.hass) 36 | for label_id in call.data["label_id"]: 37 | if not label_registry.async_get_label(label_id): 38 | msg = f"Label {label_id} not found" 39 | raise HomeAssistantError(msg) 40 | 41 | area_registry = ar.async_get(self.hass) 42 | for area_id in call.data["area_id"]: 43 | if area_entry := area_registry.async_get_area(area_id): 44 | labels = area_entry.labels.copy() 45 | labels.update(call.data["label_id"]) 46 | area_registry.async_update(area_id, labels=labels) 47 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/add_label_to_device.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import ( 12 | config_validation as cv, 13 | device_registry as dr, 14 | label_registry as lr, 15 | ) 16 | 17 | from ....services import AbstractSpookAdminService 18 | 19 | if TYPE_CHECKING: 20 | from homeassistant.core import ServiceCall 21 | 22 | 23 | class SpookService(AbstractSpookAdminService): 24 | """Home Assistant service to add a label to a device.""" 25 | 26 | domain = DOMAIN 27 | service = "add_label_to_device" 28 | schema = { 29 | vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]), 30 | vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]), 31 | } 32 | 33 | async def async_handle_service(self, call: ServiceCall) -> None: 34 | """Handle the service call.""" 35 | label_registry = lr.async_get(self.hass) 36 | for label_id in call.data["label_id"]: 37 | if not label_registry.async_get_label(label_id): 38 | msg = f"Label {label_id} not found" 39 | raise HomeAssistantError(msg) 40 | 41 | device_registry = dr.async_get(self.hass) 42 | for device_id in call.data["device_id"]: 43 | if device_entry := device_registry.async_get(device_id): 44 | labels = device_entry.labels.copy() 45 | labels.update(call.data["label_id"]) 46 | device_registry.async_update_device(device_id, labels=labels) 47 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/add_label_to_entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import ( 12 | config_validation as cv, 13 | entity_registry as er, 14 | label_registry as lr, 15 | ) 16 | 17 | from ....services import AbstractSpookAdminService 18 | 19 | if TYPE_CHECKING: 20 | from homeassistant.core import ServiceCall 21 | 22 | 23 | class SpookService(AbstractSpookAdminService): 24 | """Home Assistant service to add a label to an entity.""" 25 | 26 | domain = DOMAIN 27 | service = "add_label_to_entity" 28 | schema = { 29 | vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]), 30 | vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]), 31 | } 32 | 33 | async def async_handle_service(self, call: ServiceCall) -> None: 34 | """Handle the service call.""" 35 | label_registry = lr.async_get(self.hass) 36 | for label_id in call.data["label_id"]: 37 | if not label_registry.async_get_label(label_id): 38 | msg = f"Label {label_id} not found" 39 | raise HomeAssistantError(msg) 40 | 41 | entity_registry = er.async_get(self.hass) 42 | for entity_id in call.data["entity_id"]: 43 | if entity_entry := entity_registry.async_get(entity_id): 44 | labels = entity_entry.labels.copy() 45 | labels.update(call.data["label_id"]) 46 | entity_registry.async_update_entity(entity_id, labels=labels) 47 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/create_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import area_registry as ar, config_validation as cv 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant area service to create areas on the fly.""" 20 | 21 | domain = DOMAIN 22 | service = "create_area" 23 | schema = { 24 | vol.Required("name"): cv.string, 25 | vol.Optional("aliases"): [cv.string], 26 | vol.Optional("icon"): cv.icon, 27 | } 28 | 29 | async def async_handle_service(self, call: ServiceCall) -> None: 30 | """Handle the service call.""" 31 | area_registry = ar.async_get(self.hass) 32 | area_registry.async_create( 33 | name=call.data["name"], 34 | aliases=call.data.get("aliases"), 35 | icon=call.data.get("icon"), 36 | ) 37 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/create_floor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, floor_registry as fr 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant floor service to create floors on the fly.""" 20 | 21 | domain = DOMAIN 22 | service = "create_floor" 23 | schema = { 24 | vol.Required("name"): cv.string, 25 | vol.Optional("aliases"): [cv.string], 26 | vol.Optional("icon"): cv.icon, 27 | vol.Optional("level"): vol.Coerce(int), 28 | } 29 | 30 | async def async_handle_service(self, call: ServiceCall) -> None: 31 | """Handle the service call.""" 32 | floor_registry = fr.async_get(self.hass) 33 | floor_registry.async_create( 34 | name=call.data["name"], 35 | aliases=call.data.get("aliases"), 36 | icon=call.data.get("icon"), 37 | level=call.data.get("level"), 38 | ) 39 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/delete_all_orphaned_entities.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.homeassistant import DOMAIN 8 | from homeassistant.const import ATTR_RESTORED 9 | from homeassistant.helpers import entity_registry as er 10 | 11 | from ....services import AbstractSpookAdminService 12 | 13 | if TYPE_CHECKING: 14 | from homeassistant.core import ServiceCall 15 | 16 | 17 | class SpookService(AbstractSpookAdminService): 18 | """Home Assistant Core integration service to delete all orphaned entities.""" 19 | 20 | domain = DOMAIN 21 | service = "delete_all_orphaned_entities" 22 | 23 | async def async_handle_service(self, call: ServiceCall) -> None: 24 | """Handle the service call.""" 25 | entity_registry = er.async_get(self.hass) 26 | for state in self.hass.states.async_all(): 27 | if not state.attributes.get(ATTR_RESTORED): 28 | continue 29 | entity_registry.async_remove(state.entity_id) 30 | self.hass.states.async_remove(state.entity_id, call.context) 31 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/delete_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import area_registry as ar, config_validation as cv 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant area service to delete areas on the fly.""" 20 | 21 | domain = DOMAIN 22 | service = "delete_area" 23 | schema = {vol.Required("area_id"): cv.string} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | area_registry = ar.async_get(self.hass) 28 | area_registry.async_delete(call.data["area_id"]) 29 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/delete_floor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, floor_registry as fr 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant floor service to delete floors on the fly.""" 20 | 21 | domain = DOMAIN 22 | service = "delete_floor" 23 | schema = {vol.Required("floor_id"): cv.string} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | floor_registry = fr.async_get(self.hass) 28 | floor_registry.async_delete(call.data["floor_id"]) 29 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/delete_label.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, label_registry as lr 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant service to delete labels on the fly.""" 20 | 21 | domain = DOMAIN 22 | service = "delete_label" 23 | schema = {vol.Required("label_id"): cv.string} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | label_registry = lr.async_get(self.hass) 28 | label_registry.async_delete(call.data["label_id"]) 29 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/disable_config_entry.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.config_entries import ConfigEntryDisabler 11 | from homeassistant.helpers import config_validation as cv 12 | 13 | from ....services import AbstractSpookAdminService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookAdminService): 20 | """Home Assistant Core integration service to disable a config entry.""" 21 | 22 | domain = DOMAIN 23 | service = "disable_config_entry" 24 | schema = {vol.Required("config_entry_id"): vol.All(cv.ensure_list, [cv.string])} 25 | 26 | async def async_handle_service(self, call: ServiceCall) -> None: 27 | """Handle the service call.""" 28 | for config_entry_id in call.data["config_entry_id"]: 29 | await self.hass.config_entries.async_set_disabled_by( 30 | config_entry_id, 31 | disabled_by=ConfigEntryDisabler.USER, 32 | ) 33 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/disable_device.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, device_registry as dr 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to disable a device.""" 20 | 21 | domain = DOMAIN 22 | service = "disable_device" 23 | schema = {vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string])} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | device_registry = dr.async_get(self.hass) 28 | for device_id in call.data["device_id"]: 29 | device_registry.async_update_device( 30 | device_id=device_id, 31 | disabled_by=dr.DeviceEntryDisabler.USER, 32 | ) 33 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/disable_entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, entity_registry as er 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to disable an entity.""" 20 | 21 | domain = DOMAIN 22 | service = "disable_entity" 23 | schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | entity_registry = er.async_get(self.hass) 28 | for entity_id in call.data["entity_id"]: 29 | entity_registry.async_update_entity( 30 | entity_id=entity_id, 31 | disabled_by=er.RegistryEntryDisabler.USER, 32 | ) 33 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/disable_polling.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import config_validation as cv 12 | 13 | from ....services import AbstractSpookAdminService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookAdminService): 20 | """Home Assistant Core integration service to disable polling.""" 21 | 22 | domain = DOMAIN 23 | service = "disable_polling" 24 | schema = {vol.Required("config_entry_id"): cv.string} 25 | 26 | async def async_handle_service(self, call: ServiceCall) -> None: 27 | """Handle the service call.""" 28 | if not ( 29 | entry := self.hass.config_entries.async_get_entry( 30 | call.data["config_entry_id"], 31 | ) 32 | ): 33 | msg = f"Config entry not found: {call.data['config_entry_id']}" 34 | raise HomeAssistantError(msg) 35 | 36 | self.hass.config_entries.async_update_entry( 37 | entry, 38 | pref_disable_polling=True, 39 | ) 40 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/enable_config_entry.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to enable a config entry.""" 20 | 21 | domain = DOMAIN 22 | service = "enable_config_entry" 23 | schema = {vol.Required("config_entry_id"): vol.All(cv.ensure_list, [cv.string])} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | for config_entry_id in call.data["config_entry_id"]: 28 | await self.hass.config_entries.async_set_disabled_by( 29 | config_entry_id, 30 | disabled_by=None, 31 | ) 32 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/enable_device.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, device_registry as dr 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to enable a device.""" 20 | 21 | domain = DOMAIN 22 | service = "enable_device" 23 | schema = {vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string])} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | device_registry = dr.async_get(self.hass) 28 | for device_id in call.data["device_id"]: 29 | device_registry.async_update_device( 30 | device_id=device_id, 31 | disabled_by=None, 32 | ) 33 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/enable_entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, entity_registry as er 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to enable an entity.""" 20 | 21 | domain = DOMAIN 22 | service = "enable_entity" 23 | schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | entity_registry = er.async_get(self.hass) 28 | for entity_id in call.data["entity_id"]: 29 | entity_registry.async_update_entity( 30 | entity_id=entity_id, 31 | disabled_by=None, 32 | ) 33 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/enable_polling.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import config_validation as cv 12 | 13 | from ....services import AbstractSpookAdminService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookAdminService): 20 | """Home Assistant Core integration service to enable polling.""" 21 | 22 | domain = DOMAIN 23 | service = "enable_polling" 24 | schema = {vol.Required("config_entry_id"): cv.string} 25 | 26 | async def async_handle_service(self, call: ServiceCall) -> None: 27 | """Handle the service call.""" 28 | if not ( 29 | entry := self.hass.config_entries.async_get_entry( 30 | call.data["config_entry_id"], 31 | ) 32 | ): 33 | msg = f"Config entry not found: {call.data['config_entry_id']}" 34 | raise HomeAssistantError(msg) 35 | 36 | self.hass.config_entries.async_update_entry( 37 | entry, 38 | pref_disable_polling=False, 39 | ) 40 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/hide_entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, entity_registry as er 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to hide an entity.""" 20 | 21 | domain = DOMAIN 22 | service = "hide_entity" 23 | admin = True 24 | schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])} 25 | 26 | async def async_handle_service(self, call: ServiceCall) -> None: 27 | """Handle the service call.""" 28 | entity_registry = er.async_get(self.hass) 29 | for entity_id in call.data["entity_id"]: 30 | entity_registry.async_update_entity( 31 | entity_id=entity_id, 32 | hidden_by=er.RegistryEntryHider.USER, 33 | ) 34 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/list_orphaned_database_entities.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from sqlalchemy import create_engine, text 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.components.recorder import ( 11 | get_instance, 12 | ) 13 | from homeassistant.core import ServiceResponse, SupportsResponse 14 | 15 | from ....services import AbstractSpookService 16 | 17 | if TYPE_CHECKING: 18 | from homeassistant.core import ServiceCall 19 | 20 | 21 | class SpookService(AbstractSpookService): 22 | """Home Assistant Core integration service to list all orphaned database entities.""" 23 | 24 | domain = DOMAIN 25 | service = "list_orphaned_database_entities" 26 | supports_response = SupportsResponse.ONLY 27 | 28 | async def async_handle_service(self, call: ServiceCall) -> ServiceResponse: 29 | """Handle the service call.""" 30 | query = text( 31 | """ 32 | SELECT DISTINCT(entity_id) FROM states_meta 33 | """ 34 | ) 35 | db_url = get_instance(self.hass).db_url 36 | engine = create_engine(db_url) 37 | with engine.connect() as conn: 38 | response = conn.execute(query) 39 | db_list = [e[0] for e in response] 40 | states_list = self.hass.states.async_entity_ids() 41 | compared_list = set(db_list).difference(states_list) 42 | if call.return_response: 43 | return { 44 | "count": len(compared_list), 45 | "entities": list(compared_list), 46 | } 47 | return None 48 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/remove_alias_from_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import area_registry as ar, config_validation as cv 12 | 13 | from ....services import AbstractSpookAdminService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookAdminService): 20 | """Home Assistant service to remove an alias to an area.""" 21 | 22 | domain = DOMAIN 23 | service = "remove_alias_from_area" 24 | schema = { 25 | vol.Required("area_id"): cv.string, 26 | vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]), 27 | } 28 | 29 | async def async_handle_service(self, call: ServiceCall) -> None: 30 | """Handle the service call.""" 31 | area_registry = ar.async_get(self.hass) 32 | if not (area := area_registry.async_get_area(call.data["area_id"])): 33 | msg = f"Area {call.data['area_id']} not found" 34 | raise HomeAssistantError(msg) 35 | 36 | aliases = area.aliases.copy() 37 | area_registry.async_update( 38 | call.data["area_id"], 39 | aliases=aliases.difference(call.data["alias"]), 40 | ) 41 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/remove_alias_from_floor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import config_validation as cv, floor_registry as fr 12 | 13 | from ....services import AbstractSpookAdminService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookAdminService): 20 | """Home Assistant service to remove an alias from a floor.""" 21 | 22 | domain = DOMAIN 23 | service = "remove_alias_from_floor" 24 | schema = { 25 | vol.Required("floor_id"): cv.string, 26 | vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]), 27 | } 28 | 29 | async def async_handle_service(self, call: ServiceCall) -> None: 30 | """Handle the service call.""" 31 | floor_registry = fr.async_get(self.hass) 32 | if not (floor := floor_registry.async_get_floor(call.data["floor_id"])): 33 | msg = f"Floor {call.data['floor_id']} not found" 34 | raise HomeAssistantError(msg) 35 | 36 | aliases = floor.aliases.copy() 37 | floor_registry.async_update( 38 | call.data["floor_id"], 39 | aliases=aliases.difference(call.data["alias"]), 40 | ) 41 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/remove_area_from_floor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import area_registry as ar, config_validation as cv 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant service to remove an area from a floor.""" 20 | 21 | domain = DOMAIN 22 | service = "remove_area_from_floor" 23 | schema = { 24 | vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]), 25 | } 26 | 27 | async def async_handle_service(self, call: ServiceCall) -> None: 28 | """Handle the service call.""" 29 | area_registry = ar.async_get(self.hass) 30 | for area_id in call.data["area_id"]: 31 | area_registry.async_update( 32 | area_id, 33 | floor_id=None, 34 | ) 35 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/remove_device_from_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, device_registry as dr 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant service to remove a device from an area.""" 20 | 21 | domain = DOMAIN 22 | service = "remove_device_from_area" 23 | schema = { 24 | vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]), 25 | } 26 | 27 | async def async_handle_service(self, call: ServiceCall) -> None: 28 | """Handle the service call.""" 29 | device_registry = dr.async_get(self.hass) 30 | for device_id in call.data["device_id"]: 31 | device_registry.async_update_device( 32 | device_id, 33 | area_id=None, 34 | ) 35 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/remove_entity_from_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, entity_registry as er 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant service to remove an entity from an area.""" 20 | 21 | domain = DOMAIN 22 | service = "remove_entity_from_area" 23 | schema = { 24 | vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]), 25 | } 26 | 27 | async def async_handle_service(self, call: ServiceCall) -> None: 28 | """Handle the service call.""" 29 | entity_registry = er.async_get(self.hass) 30 | for entity_id in call.data["entity_id"]: 31 | entity_registry.async_update_entity( 32 | entity_id, 33 | area_id=None, 34 | ) 35 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/remove_label_from_area.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import area_registry as ar, config_validation as cv 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant service to remove a label from an area.""" 20 | 21 | domain = DOMAIN 22 | service = "remove_label_from_area" 23 | schema = { 24 | vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]), 25 | vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]), 26 | } 27 | 28 | async def async_handle_service(self, call: ServiceCall) -> None: 29 | """Handle the service call.""" 30 | area_registry = ar.async_get(self.hass) 31 | for area_id in call.data["area_id"]: 32 | if area_entry := area_registry.async_get_area(area_id): 33 | labels = area_entry.labels.copy() 34 | labels.difference_update(call.data["label_id"]) 35 | area_registry.async_update(area_id, labels=labels) 36 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/remove_label_from_device.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, device_registry as dr 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant service to remove a label from a device.""" 20 | 21 | domain = DOMAIN 22 | service = "remove_label_from_device" 23 | schema = { 24 | vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]), 25 | vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]), 26 | } 27 | 28 | async def async_handle_service(self, call: ServiceCall) -> None: 29 | """Handle the service call.""" 30 | device_registry = dr.async_get(self.hass) 31 | for device_id in call.data["device_id"]: 32 | if device_entry := device_registry.async_get(device_id): 33 | labels = device_entry.labels.copy() 34 | labels.difference_update(call.data["label_id"]) 35 | device_registry.async_update_device(device_id, labels=labels) 36 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/remove_label_from_entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, entity_registry as er 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant service to remove a label from an entity.""" 20 | 21 | domain = DOMAIN 22 | service = "remove_label_from_entity" 23 | schema = { 24 | vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]), 25 | vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]), 26 | } 27 | 28 | async def async_handle_service(self, call: ServiceCall) -> None: 29 | """Handle the service call.""" 30 | entity_registry = er.async_get(self.hass) 31 | for entity_id in call.data["entity_id"]: 32 | if entity_entry := entity_registry.async_get(entity_id): 33 | labels = entity_entry.labels.copy() 34 | labels.difference_update(call.data["label_id"]) 35 | entity_registry.async_update_entity(entity_id, labels=labels) 36 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/rename_entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, entity_registry as er 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to rename an entity.""" 20 | 21 | domain = DOMAIN 22 | service = "rename_entity" 23 | schema = { 24 | vol.Required("name"): cv.string, 25 | vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]), 26 | } 27 | 28 | async def async_handle_service(self, call: ServiceCall) -> None: 29 | """Handle the service call.""" 30 | entity_registry = er.async_get(self.hass) 31 | for entity_id in call.data["entity_id"]: 32 | entity_registry.async_update_entity( 33 | entity_id=entity_id, 34 | name=call.data["name"], 35 | ) 36 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/set_area_aliases.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import area_registry as ar, config_validation as cv 12 | 13 | from ....services import AbstractSpookAdminService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookAdminService): 20 | """Home Assistant service to set the aliases of an area.""" 21 | 22 | domain = DOMAIN 23 | service = "set_area_aliases" 24 | schema = { 25 | vol.Required("area_id"): cv.string, 26 | vol.Required("aliases"): vol.All(cv.ensure_list, [cv.string]), 27 | } 28 | 29 | async def async_handle_service(self, call: ServiceCall) -> None: 30 | """Handle the service call.""" 31 | area_registry = ar.async_get(self.hass) 32 | if not area_registry.async_get_area(call.data["area_id"]): 33 | msg = f"Area {call.data['area_id']} not found" 34 | raise HomeAssistantError(msg) 35 | 36 | area_registry.async_update( 37 | call.data["area_id"], 38 | aliases=set(call.data["aliases"]), 39 | ) 40 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/set_floor_aliases.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.exceptions import HomeAssistantError 11 | from homeassistant.helpers import config_validation as cv, floor_registry as fr 12 | 13 | from ....services import AbstractSpookAdminService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookAdminService): 20 | """Home Assistant service to set the aliases of a floor.""" 21 | 22 | domain = DOMAIN 23 | service = "set_floor_aliases" 24 | schema = { 25 | vol.Required("floor_id"): cv.string, 26 | vol.Required("aliases"): vol.All(cv.ensure_list, [cv.string]), 27 | } 28 | 29 | async def async_handle_service(self, call: ServiceCall) -> None: 30 | """Handle the service call.""" 31 | floor_registry = fr.async_get(self.hass) 32 | if not floor_registry.async_get_floor(call.data["floor_id"]): 33 | msg = f"Floor {call.data['floor_id']} not found" 34 | raise HomeAssistantError(msg) 35 | 36 | floor_registry.async_update( 37 | call.data["floor_id"], 38 | aliases=set(call.data["aliases"]), 39 | ) 40 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/unhide_entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, entity_registry as er 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to unhide an entity.""" 20 | 21 | domain = DOMAIN 22 | service = "unhide_entity" 23 | schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])} 24 | 25 | async def async_handle_service(self, call: ServiceCall) -> None: 26 | """Handle the service call.""" 27 | entity_registry = er.async_get(self.hass) 28 | for entity_id in call.data["entity_id"]: 29 | entity_registry.async_update_entity( 30 | entity_id=entity_id, 31 | hidden_by=None, 32 | ) 33 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/homeassistant/services/update_entity_id.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.homeassistant import DOMAIN 10 | from homeassistant.helpers import config_validation as cv, entity_registry as er 11 | 12 | from ....services import AbstractSpookAdminService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookAdminService): 19 | """Home Assistant Core integration service to update an entity's ID.""" 20 | 21 | domain = DOMAIN 22 | service = "update_entity_id" 23 | schema = { 24 | vol.Required("entity_id"): cv.entity_id, 25 | vol.Required("new_entity_id"): cv.entity_id, 26 | } 27 | 28 | async def async_handle_service(self, call: ServiceCall) -> None: 29 | """Handle the service call.""" 30 | entity_registry = er.async_get(self.hass) 31 | entity_registry.async_update_entity( 32 | entity_id=call.data["entity_id"], 33 | new_entity_id=call.data["new_entity_id"], 34 | ) 35 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_number/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_number/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_number/services/decrement.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | import math 6 | from typing import TYPE_CHECKING 7 | 8 | import voluptuous as vol 9 | 10 | from homeassistant.components.input_number import DOMAIN, InputNumber 11 | 12 | from ....services import AbstractSpookEntityComponentService, ReplaceExistingService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService( 19 | AbstractSpookEntityComponentService[InputNumber], ReplaceExistingService 20 | ): 21 | """Input number entity service, decrease value by a single step. 22 | 23 | It override the built-in increment service to allow for a custom amount. 24 | """ 25 | 26 | domain = DOMAIN 27 | service = "decrement" 28 | schema = {vol.Optional("amount"): vol.Coerce(float)} 29 | 30 | async def async_handle_service( 31 | self, 32 | entity: InputNumber, 33 | call: ServiceCall, 34 | ) -> None: 35 | """Handle the service call.""" 36 | # pylint: disable=protected-access 37 | amount = call.data.get("amount", entity._step) # noqa: SLF001 38 | if not math.isclose(amount % entity._step, 0, abs_tol=1e-9): # noqa: SLF001 39 | msg = ( 40 | f"Amount {amount} not valid for {entity.entity_id}, " 41 | f"it needs to be a multiple of {entity._step}", # noqa: SLF001 42 | ) 43 | raise ValueError(msg) 44 | 45 | await entity.async_set_value( 46 | max( 47 | entity._current_value - amount, # noqa: SLF001 48 | entity._minimum, # noqa: SLF001 49 | ), 50 | ) 51 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_number/services/increment.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | import math 6 | from typing import TYPE_CHECKING 7 | 8 | import voluptuous as vol 9 | 10 | from homeassistant.components.input_number import DOMAIN, InputNumber 11 | 12 | from ....services import AbstractSpookEntityComponentService, ReplaceExistingService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService( 19 | AbstractSpookEntityComponentService[InputNumber], ReplaceExistingService 20 | ): 21 | """Input number entity service, increase value by a single step. 22 | 23 | It override the built-in increment service to allow for a custom amount. 24 | """ 25 | 26 | domain = DOMAIN 27 | service = "increment" 28 | schema = {vol.Optional("amount"): vol.Coerce(float)} 29 | 30 | async def async_handle_service( 31 | self, 32 | entity: InputNumber, 33 | call: ServiceCall, 34 | ) -> None: 35 | """Handle the service call.""" 36 | # pylint: disable=protected-access 37 | amount = call.data.get("amount", entity._step) # noqa: SLF001 38 | if not math.isclose(amount % entity._step, 0, abs_tol=1e-9): # noqa: SLF001 39 | msg = ( 40 | f"Amount {amount} not valid for {entity.entity_id}, " 41 | f"it needs to be a multiple of {entity._step}", # noqa: SLF001 42 | ) 43 | raise ValueError(msg) 44 | 45 | await entity.async_set_value( 46 | min( 47 | entity._current_value + amount, # noqa: SLF001 48 | entity._maximum, # noqa: SLF001 49 | ), 50 | ) 51 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_number/services/max.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.input_number import DOMAIN, InputNumber 8 | 9 | from ....services import AbstractSpookEntityComponentService 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.core import ServiceCall 13 | 14 | 15 | class SpookService(AbstractSpookEntityComponentService[InputNumber]): 16 | """Input number entity service, set the max value.""" 17 | 18 | domain = DOMAIN 19 | service = "max" 20 | 21 | async def async_handle_service( 22 | self, 23 | entity: InputNumber, 24 | call: ServiceCall, # noqa: ARG002 25 | ) -> None: 26 | """Handle the service call.""" 27 | # pylint: disable-next=protected-access 28 | await entity.async_set_value(entity._maximum) # noqa: SLF001 29 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_number/services/min.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.input_number import DOMAIN, InputNumber 8 | 9 | from ....services import AbstractSpookEntityComponentService 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.core import ServiceCall 13 | 14 | 15 | class SpookService(AbstractSpookEntityComponentService[InputNumber]): 16 | """Input number entity service, set the min value.""" 17 | 18 | domain = DOMAIN 19 | service = "min" 20 | 21 | async def async_handle_service( 22 | self, 23 | entity: InputNumber, 24 | call: ServiceCall, # noqa: ARG002 25 | ) -> None: 26 | """Handle the service call.""" 27 | # pylint: disable-next=protected-access 28 | await entity.async_set_value(entity._minimum) # noqa: SLF001 29 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_select/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_select/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_select/services/random.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from homeassistant.components.input_select import DOMAIN 6 | 7 | from ...select.services.random import SpookService as SelectRandomSpookService 8 | 9 | 10 | class SpookService(SelectRandomSpookService): 11 | """Input select entity service, select a random option. 12 | 13 | Clone of the select_random service, but for input_select entities. 14 | """ 15 | 16 | domain = DOMAIN 17 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_select/services/shuffle.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | import random 6 | from typing import TYPE_CHECKING 7 | 8 | from homeassistant.components.input_select import DOMAIN, InputSelect 9 | 10 | from ....services import AbstractSpookEntityComponentService 11 | 12 | if TYPE_CHECKING: 13 | from homeassistant.core import ServiceCall 14 | 15 | 16 | class SpookService(AbstractSpookEntityComponentService[InputSelect]): 17 | """Input select entity service, shuffling the positions. 18 | 19 | These changes are not permanent, and will be lost when input select entities 20 | are loaded/changed, or when Home Assistant is restarted. 21 | """ 22 | 23 | domain = DOMAIN 24 | service = "shuffle" 25 | 26 | async def async_handle_service( 27 | self, 28 | entity: InputSelect, 29 | call: ServiceCall, # noqa: ARG002 30 | ) -> None: 31 | """Handle the service call.""" 32 | # pylint: disable-next=protected-access 33 | random.shuffle(entity._attr_options) # noqa: SLF001 34 | entity.async_write_ha_state() 35 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/input_select/services/sort.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.input_select import DOMAIN, InputSelect 8 | 9 | from ....services import AbstractSpookEntityComponentService 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.core import ServiceCall 13 | 14 | 15 | class SpookService(AbstractSpookEntityComponentService[InputSelect]): 16 | """Input select entity service, sorting the positions. 17 | 18 | These changes are not permanent, and will be lost when input select entities 19 | are loaded/changed, or when Home Assistant is restarted. 20 | """ 21 | 22 | domain = DOMAIN 23 | service = "sort" 24 | 25 | async def async_handle_service( 26 | self, 27 | entity: InputSelect, 28 | call: ServiceCall, # noqa: ARG002 29 | ) -> None: 30 | """Handle the service call.""" 31 | # pylint: disable-next=protected-access 32 | entity._attr_options.sort() # noqa: SLF001 33 | entity.async_write_ha_state() 34 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/integration/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/integration/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/lovelace/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/lovelace/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/number/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/number/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/number/services/decrement.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | import math 6 | from typing import TYPE_CHECKING 7 | 8 | import voluptuous as vol 9 | 10 | from homeassistant.components.number import DOMAIN, NumberEntity 11 | 12 | from ....services import AbstractSpookEntityComponentService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookEntityComponentService[NumberEntity]): 19 | """Number entity service, decrease value by a single step.""" 20 | 21 | domain = DOMAIN 22 | service = "decrement" 23 | schema = {vol.Optional("amount"): vol.Coerce(float)} 24 | 25 | async def async_handle_service( 26 | self, 27 | entity: NumberEntity, 28 | call: ServiceCall, 29 | ) -> None: 30 | """Handle the service call.""" 31 | amount = call.data.get("amount", entity.step or 1) 32 | if not math.isclose(amount % entity.step, 0, abs_tol=1e-9): 33 | msg = ( 34 | f"Amount {amount} not valid for {entity.entity_id}, " 35 | f"it needs to be a multiple of {entity.step}", 36 | ) 37 | raise ValueError(msg) 38 | 39 | value = entity.value - amount 40 | 41 | if entity.min_value is not None: 42 | value = max(value, entity.min_value) 43 | 44 | await entity.async_set_native_value(value) 45 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/number/services/increment.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | import math 6 | from typing import TYPE_CHECKING 7 | 8 | import voluptuous as vol 9 | 10 | from homeassistant.components.number import DOMAIN, NumberEntity 11 | 12 | from ....services import AbstractSpookEntityComponentService 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.core import ServiceCall 16 | 17 | 18 | class SpookService(AbstractSpookEntityComponentService[NumberEntity]): 19 | """Number entity service, increase value by a single step.""" 20 | 21 | domain = DOMAIN 22 | service = "increment" 23 | schema = {vol.Optional("amount"): vol.Coerce(float)} 24 | 25 | async def async_handle_service( 26 | self, 27 | entity: NumberEntity, 28 | call: ServiceCall, 29 | ) -> None: 30 | """Handle the service call.""" 31 | amount = call.data.get("amount", entity.step or 1) 32 | if not math.isclose(amount % entity.step, 0, abs_tol=1e-9): 33 | msg = ( 34 | f"Amount {amount} not valid for {entity.entity_id}, " 35 | f"it needs to be a multiple of {entity.step}", 36 | ) 37 | raise ValueError(msg) 38 | 39 | value = entity.value + amount 40 | 41 | if entity.max_value is not None: 42 | value = min(value, entity.max_value) 43 | 44 | await entity.async_set_native_value(value) 45 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/number/services/max.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.number import DOMAIN, NumberEntity 8 | from homeassistant.exceptions import HomeAssistantError 9 | 10 | from ....services import AbstractSpookEntityComponentService 11 | 12 | if TYPE_CHECKING: 13 | from homeassistant.core import ServiceCall 14 | 15 | 16 | class SpookService(AbstractSpookEntityComponentService[NumberEntity]): 17 | """Number entity service, set the max value.""" 18 | 19 | domain = DOMAIN 20 | service = "max" 21 | 22 | async def async_handle_service( 23 | self, 24 | entity: NumberEntity, 25 | call: ServiceCall, # noqa: ARG002 26 | ) -> None: 27 | """Handle the service call.""" 28 | if entity.max_value is None: 29 | msg = f"Entity {entity.entity_id} has no max value" 30 | raise HomeAssistantError(msg) 31 | await entity.async_set_native_value(entity.native_max_value) 32 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/number/services/min.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.number import DOMAIN, NumberEntity 8 | from homeassistant.exceptions import HomeAssistantError 9 | 10 | from ....services import AbstractSpookEntityComponentService 11 | 12 | if TYPE_CHECKING: 13 | from homeassistant.core import ServiceCall 14 | 15 | 16 | class SpookService(AbstractSpookEntityComponentService[NumberEntity]): 17 | """Number entity service, set the min value.""" 18 | 19 | domain = DOMAIN 20 | service = "min" 21 | 22 | async def async_handle_service( 23 | self, 24 | entity: NumberEntity, 25 | call: ServiceCall, # noqa: ARG002 26 | ) -> None: 27 | """Handle the service call.""" 28 | if entity.min_value is None: 29 | msg = f"Entity {entity.entity_id} has no min value" 30 | raise HomeAssistantError(msg) 31 | await entity.async_set_native_value(entity.native_min_value) 32 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/person/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/person/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/proximity/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/proximity/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/recorder/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/recorder/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/repairs/entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from homeassistant.components import repairs 6 | from homeassistant.const import __version__ 7 | from homeassistant.helpers.device_registry import DeviceInfo 8 | 9 | from ...const import DOMAIN 10 | from ...entity import SpookEntity, SpookEntityDescription 11 | 12 | 13 | class RepairsSpookEntity(SpookEntity): 14 | """Defines an base Spook entity for Repairs related entities.""" 15 | 16 | def __init__(self, description: SpookEntityDescription) -> None: 17 | """Initialize the entity.""" 18 | super().__init__(description=description) 19 | self._attr_device_info = DeviceInfo( 20 | identifiers={(DOMAIN, repairs.DOMAIN)}, 21 | manufacturer="Home Assistant", 22 | name="Repairs", 23 | sw_version=__version__, 24 | ) 25 | self._attr_unique_id = f"{repairs.DOMAIN}_{description.key}" 26 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/repairs/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/repairs/services/ignore_all.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.repairs import DOMAIN 8 | from homeassistant.helpers import issue_registry as ir 9 | 10 | from ....services import AbstractSpookService 11 | 12 | if TYPE_CHECKING: 13 | from homeassistant.core import ServiceCall 14 | 15 | 16 | class SpookService(AbstractSpookService): 17 | """Home Assistant Repairs service for ignoring all issues.""" 18 | 19 | domain = DOMAIN 20 | service = "ignore_all" 21 | 22 | async def async_handle_service(self, _: ServiceCall) -> None: 23 | """Handle the service call.""" 24 | issue_registry = ir.async_get(self.hass) 25 | for domain, issue_id in issue_registry.issues: 26 | issue_registry.async_ignore(domain, issue_id, ignore=True) 27 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/repairs/services/remove.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN 10 | from homeassistant.helpers import config_validation as cv, issue_registry as ir 11 | 12 | from ....const import DOMAIN 13 | from ....services import AbstractSpookService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookService): 20 | """Home Assistant Repairs service to create your own issues.""" 21 | 22 | domain = REPAIRS_DOMAIN 23 | service = "remove" 24 | schema = {vol.Required("issue_id"): cv.string} 25 | 26 | async def async_handle_service(self, call: ServiceCall) -> None: 27 | """Handle the service call.""" 28 | ir.async_delete_issue(self.hass, DOMAIN, f"user_{call.data['issue_id']}") 29 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/repairs/services/unignore_all.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.repairs import DOMAIN 8 | from homeassistant.helpers import issue_registry as ir 9 | 10 | from ....services import AbstractSpookService 11 | 12 | if TYPE_CHECKING: 13 | from homeassistant.core import ServiceCall 14 | 15 | 16 | class SpookService(AbstractSpookService): 17 | """Home Assistant Repairs service for unignoring all issues.""" 18 | 19 | domain = DOMAIN 20 | service = "unignore_all" 21 | 22 | async def async_handle_service(self, _: ServiceCall) -> None: 23 | """Handle the service call.""" 24 | issue_registry = ir.async_get(self.hass) 25 | for domain, issue_id in issue_registry.issues: 26 | issue_registry.async_ignore(domain, issue_id, ignore=False) 27 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/scene/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/scene/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/script/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/script/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/select/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/select/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/select/services/random.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | import random 6 | from typing import TYPE_CHECKING 7 | 8 | import voluptuous as vol 9 | 10 | from homeassistant.components.select import DOMAIN, SelectEntity 11 | from homeassistant.helpers import config_validation as cv 12 | 13 | from ....services import AbstractSpookEntityComponentService 14 | 15 | if TYPE_CHECKING: 16 | from homeassistant.core import ServiceCall 17 | 18 | 19 | class SpookService(AbstractSpookEntityComponentService[SelectEntity]): 20 | """Select entity service, select a random option.""" 21 | 22 | domain = DOMAIN 23 | service = "random" 24 | schema = {vol.Optional("options"): [cv.string]} 25 | 26 | async def async_handle_service( 27 | self, 28 | entity: SelectEntity, 29 | call: ServiceCall, 30 | ) -> None: 31 | """Handle the service call.""" 32 | option = random.choice(call.data.get("options", entity.options)) # noqa: S311 33 | if option not in entity.options: 34 | msg = f"Option {option} not valid for {entity.entity_id}" 35 | raise ValueError(msg) 36 | await entity.async_select_option(option) 37 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/spook/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/spook/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/spook/services/boo.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.exceptions import HomeAssistantError 8 | 9 | from ....const import DOMAIN 10 | from ....services import AbstractSpookService 11 | 12 | if TYPE_CHECKING: 13 | from homeassistant.core import ServiceCall 14 | 15 | 16 | class SpookService(AbstractSpookService): 17 | """Spook service to fail a service call.""" 18 | 19 | domain = DOMAIN 20 | service = "boo" 21 | 22 | async def async_handle_service(self, _: ServiceCall) -> None: 23 | """Handle the service call.""" 24 | msg = "Spooked!" 25 | raise HomeAssistantError(msg) 26 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/spook/services/random_fail.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | import random 6 | from typing import TYPE_CHECKING 7 | 8 | from homeassistant.exceptions import HomeAssistantError 9 | 10 | from ....const import DOMAIN 11 | from ....services import AbstractSpookService 12 | 13 | if TYPE_CHECKING: 14 | from homeassistant.core import ServiceCall 15 | 16 | 17 | class SpookService(AbstractSpookService): 18 | """Spook service to randomly fail a service call.""" 19 | 20 | domain = DOMAIN 21 | service = "random_fail" 22 | 23 | async def async_handle_service(self, _: ServiceCall) -> None: 24 | """Handle the service call.""" 25 | if random.choice([True, False]): # noqa: S311 26 | msg = "Spooked!" 27 | raise HomeAssistantError(msg) 28 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/switch_as_x/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/switch_as_x/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/timer/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/timer/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/trend/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/trend/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/utility_meter/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/utility_meter/repairs/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/zone/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/zone/services/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/ectoplasms/zone/services/create.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.zone import CREATE_FIELDS, DOMAIN, ZoneStorageCollection 8 | 9 | from ....services import AbstractSpookAdminService 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.core import ServiceCall 13 | 14 | 15 | class SpookService(AbstractSpookAdminService): 16 | """Zone service to create zones on the fly.""" 17 | 18 | domain = DOMAIN 19 | service = "create" 20 | schema = CREATE_FIELDS 21 | 22 | async def async_handle_service(self, call: ServiceCall) -> None: 23 | """Handle the service call.""" 24 | collection: ZoneStorageCollection 25 | if DOMAIN in self.hass.data: 26 | collection = self.hass.data[DOMAIN] 27 | else: 28 | # Home zone is set in YAML, as a result Home Assistant doesn't 29 | # set the storage collection into hass data. 30 | # Major hack to get around this. 👻 31 | collection = self.hass.data["websocket_api"]["zone/list"][ 32 | 0 33 | ].__self__.storage_collection 34 | 35 | await collection.async_create_item(call.data.copy()) 36 | -------------------------------------------------------------------------------- /custom_components/spook/entity.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from dataclasses import dataclass 6 | 7 | from homeassistant.helpers.entity import Entity, EntityDescription 8 | 9 | 10 | @dataclass(frozen=True, kw_only=True) 11 | class SpookEntityDescription(EntityDescription): 12 | """Defines an base Spook entity description.""" 13 | 14 | entity_id: str | None = None 15 | 16 | 17 | class SpookEntity(Entity): 18 | """Defines an base Spook entity.""" 19 | 20 | entity_description: SpookEntityDescription 21 | 22 | _attr_has_entity_name = True 23 | 24 | def __init__(self, description: SpookEntityDescription) -> None: 25 | """Initialize the entity.""" 26 | self.entity_description = description 27 | if description.entity_id: 28 | self.entity_id = description.entity_id 29 | -------------------------------------------------------------------------------- /custom_components/spook/event.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.const import Platform 8 | 9 | from .util import async_forward_platform_entry_setups_to_ectoplasm 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | 16 | 17 | async def async_setup_entry( 18 | hass: HomeAssistant, 19 | entry: ConfigEntry, 20 | async_add_entities: AddEntitiesCallback, 21 | ) -> None: 22 | """Set up Spook events.""" 23 | await async_forward_platform_entry_setups_to_ectoplasm( 24 | hass, 25 | entry, 26 | async_add_entities, 27 | Platform.EVENT, 28 | ) 29 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/__init__.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/binary_sensor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.components.binary_sensor import BinarySensorEntity 8 | from homeassistant.const import CONF_ENTITY_ID, STATE_ON, STATE_UNKNOWN 9 | from homeassistant.core import HomeAssistant, State, callback 10 | from homeassistant.helpers import entity_registry as er 11 | 12 | from .entity import InverseEntity 13 | 14 | if TYPE_CHECKING: 15 | from homeassistant.config_entries import ConfigEntry 16 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 17 | 18 | 19 | async def async_setup_entry( 20 | hass: HomeAssistant, 21 | config_entry: ConfigEntry, 22 | async_add_entities: AddEntitiesCallback, 23 | ) -> None: 24 | """Initialize inverse config entry.""" 25 | er.async_validate_entity_id( 26 | er.async_get(hass), 27 | config_entry.options[CONF_ENTITY_ID], 28 | ) 29 | async_add_entities([InverseBinarySensor(config_entry)]) 30 | 31 | 32 | class InverseBinarySensor(InverseEntity, BinarySensorEntity): 33 | """Inverse binary sensor.""" 34 | 35 | @callback 36 | def async_update_state(self, state: State) -> None: 37 | """Query the source and determine the binary sensor state.""" 38 | if state.state == STATE_UNKNOWN: 39 | self._attr_is_on = None 40 | else: 41 | self._attr_is_on = state.state != STATE_ON 42 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/const.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from homeassistant.const import Platform 4 | 5 | DOMAIN = "spook_inverse" 6 | PLATFORMS = [ 7 | Platform.BINARY_SENSOR, 8 | Platform.SWITCH, 9 | ] 10 | 11 | CONF_HIDE_SOURCE = "hide_source" 12 | CONF_INVERSE_POSTITION = "inverse_position" 13 | CONF_INVERSE_TILT = "inverse_tilt" 14 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "spook_inverse", 3 | "name": "Inverse 👻", 4 | "codeowners": ["@frenck"], 5 | "config_flow": true, 6 | "documentation": "https://spook.boo", 7 | "integration_type": "helper", 8 | "iot_class": "calculated", 9 | "issue_tracker": "https://github.com/frenck/spook/issues", 10 | "requirements": [], 11 | "version": "0.0.0" 12 | } 13 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "switch": { 5 | "data": { 6 | "name": "الاسم", 7 | "hide_source": "إخفاء وحدة المصدر", 8 | "entity_id": "وحدة المصدر" 9 | } 10 | }, 11 | "user": { 12 | "description": "يتيح لك هذا المساعد عكس سلوك الوحدة. على سبيل المثال، عكس فتح/إغلاق أو تشغيل/إيقاف أجهزة الاستشعار الثنائية والمفاتيح.", 13 | "menu_options": { 14 | "switch": "عكس مفتاح", 15 | "binary_sensor": "عكس مستشعر ثنائي" 16 | } 17 | }, 18 | "binary_sensor": { 19 | "data": { 20 | "hide_source": "إخفاء وحدة المصدر", 21 | "name": "الاسم", 22 | "entity_id": "وحدة المصدر" 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/bg.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/ca.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "name": "Nom" 7 | } 8 | }, 9 | "switch": { 10 | "data": { 11 | "name": "Nom" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "binary_sensor": { 19 | "data": { 20 | "name": "Nom" 21 | } 22 | }, 23 | "switch": { 24 | "data": { 25 | "name": "Nom" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "Zdrojová entita", 7 | "hide_source": "Skrýt zdrojovou entitu", 8 | "name": "Název" 9 | }, 10 | "title": "Invertovat 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "Zdrojová entita", 15 | "hide_source": "Skrýt zdrojovou entitu", 16 | "name": "Název" 17 | }, 18 | "title": "Invertovat 👻" 19 | }, 20 | "user": { 21 | "title": "Invertovat 👻", 22 | "menu_options": { 23 | "binary_sensor": "Invertovat binární senzor", 24 | "switch": "Invertovat přepínač" 25 | }, 26 | "description": "Tento pomocník umožňuje invertovat chování entity. Například otočení otevřeno/zavřeno nebo zapnuto/vypnuto binárního senzoru či přepínače." 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "entity_id": "Zdrojová entita", 35 | "hide_source": "Skrýt zdrojovou entitu", 36 | "name": "Název" 37 | }, 38 | "title": "Invertovat 👻" 39 | }, 40 | "switch": { 41 | "data": { 42 | "entity_id": "Zdrojová entita", 43 | "hide_source": "Skrýt zdrojovou entitu", 44 | "name": "Název" 45 | }, 46 | "title": "Invertovat 👻" 47 | } 48 | } 49 | }, 50 | "title": "Invertovat 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Umkehren 👻", 3 | "config": { 4 | "step": { 5 | "switch": { 6 | "data": { 7 | "name": "Name", 8 | "hide_source": "Quell-Entität verbergen", 9 | "entity_id": "Quell-Entität" 10 | }, 11 | "title": "Umkehren 👻" 12 | }, 13 | "user": { 14 | "description": "Dieser Helfer ermöglicht es Ihnen, das Verhalten einer Entität umzukehren. Zum Beispiel können Sie den Zustand von binären Sensoren und Schaltern umkehren, wie z.B. Öffnen/Schließen oder Ein/Aus.", 15 | "menu_options": { 16 | "switch": "Einen Schalter umkehren", 17 | "binary_sensor": "Einen binären Sensor umkehren" 18 | }, 19 | "title": "Umkehren 👻" 20 | }, 21 | "binary_sensor": { 22 | "data": { 23 | "hide_source": "Quell-Entität verbergen", 24 | "name": "Name", 25 | "entity_id": "Quell-Entität" 26 | }, 27 | "title": "Umkehren 👻" 28 | } 29 | } 30 | }, 31 | "options": { 32 | "step": { 33 | "switch": { 34 | "data": { 35 | "hide_source": "Quell-Entität verbergen", 36 | "entity_id": "Quell-Entität", 37 | "name": "Name" 38 | }, 39 | "title": "Umkehren 👻" 40 | }, 41 | "binary_sensor": { 42 | "title": "Umkehren 👻", 43 | "data": { 44 | "name": "Name", 45 | "hide_source": "Quell-Entität verbergen", 46 | "entity_id": "Quell-Entität" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/el.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Αντιστροφή 👻", 3 | "config": { 4 | "step": { 5 | "switch": { 6 | "data": { 7 | "name": "Ονομα", 8 | "hide_source": "Απόκρυψη οντότητας πηγής", 9 | "entity_id": "Οντότητα πηγής" 10 | }, 11 | "title": "Αντιστροφή 👻" 12 | }, 13 | "user": { 14 | "description": "Αυτός ο βοηθός σάς επιτρέπει να αντιστρέψετε τη συμπεριφορά μιας οντότητας. Για παράδειγμα, αντιστρέψτε το άνοιγμα/κλείσιμο ή την ενεργοποίηση/απενεργοποίηση δυαδικών αισθητήρων και διακοπτών.", 15 | "menu_options": { 16 | "switch": "Αντιστροφή ενός διακόπτη", 17 | "binary_sensor": "Αντιστροφή ενός δυαδικού αισθητήρα" 18 | }, 19 | "title": "Αντιστροφή 👻" 20 | }, 21 | "binary_sensor": { 22 | "data": { 23 | "hide_source": "Απόκρυψη οντότητας πηγής", 24 | "name": "Ονομα", 25 | "entity_id": "Οντότητα πηγής" 26 | }, 27 | "title": "Αντιστροφή 👻" 28 | } 29 | } 30 | }, 31 | "options": { 32 | "step": { 33 | "switch": { 34 | "data": { 35 | "hide_source": "Απόκρυψη οντότητας πηγής", 36 | "entity_id": "Οντότητα πηγής", 37 | "name": "Ονομα" 38 | }, 39 | "title": "Αντιστροφή 👻" 40 | }, 41 | "binary_sensor": { 42 | "title": "Αντιστροφή 👻", 43 | "data": { 44 | "name": "Ονομα", 45 | "hide_source": "Απόκρυψη οντότητας πηγής", 46 | "entity_id": "Οντότητα πηγής" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "Source entity", 7 | "hide_source": "Hide source entity", 8 | "name": "Name" 9 | }, 10 | "title": "Inverse 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "Source entity", 15 | "hide_source": "Hide source entity", 16 | "name": "Name" 17 | }, 18 | "title": "Inverse 👻" 19 | }, 20 | "user": { 21 | "description": "This helper allow you to inverse the behavior of an entity. For example, reverse the open/close or on/off of binary sensors, and switches.", 22 | "menu_options": { 23 | "binary_sensor": "Inverse a binary sensor", 24 | "switch": "Inverse a switch" 25 | }, 26 | "title": "Inverse 👻" 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "entity_id": "Source entity", 35 | "hide_source": "Hide source entity", 36 | "name": "Name" 37 | }, 38 | "title": "Inverse 👻" 39 | }, 40 | "switch": { 41 | "data": { 42 | "entity_id": "Source entity", 43 | "hide_source": "Hide source entity", 44 | "name": "Name" 45 | }, 46 | "title": "Inverse 👻" 47 | } 48 | } 49 | }, 50 | "title": "Inverse 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Inverso 👻", 3 | "config": { 4 | "step": { 5 | "switch": { 6 | "data": { 7 | "name": "Nombre", 8 | "hide_source": "Ocultar entidad origen", 9 | "entity_id": "Entidad origen" 10 | }, 11 | "title": "Inverso 👻" 12 | }, 13 | "user": { 14 | "description": "Este ayudante te permite invertir el comportamiento de una entidad. Por ejemplo, invertir abrir/cerrar o encender/apagar de sensores binarios, e interruptores.", 15 | "menu_options": { 16 | "switch": "Invertir un interruptor", 17 | "binary_sensor": "Invertir un sensor binario" 18 | }, 19 | "title": "Inverso 👻" 20 | }, 21 | "binary_sensor": { 22 | "data": { 23 | "hide_source": "Ocultar entidad origen", 24 | "name": "Nombre", 25 | "entity_id": "Entidad origen" 26 | }, 27 | "title": "Inverso 👻" 28 | } 29 | } 30 | }, 31 | "options": { 32 | "step": { 33 | "switch": { 34 | "data": { 35 | "hide_source": "Ocultar entidad origen", 36 | "entity_id": "Entidad origen", 37 | "name": "Nombre" 38 | }, 39 | "title": "Inverso 👻" 40 | }, 41 | "binary_sensor": { 42 | "title": "Inverso 👻", 43 | "data": { 44 | "name": "Nombre", 45 | "hide_source": "Ocultar entidad origen", 46 | "entity_id": "Entidad origen" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/et.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "Lähteolem", 7 | "hide_source": "Peida lähteolem", 8 | "name": "Nimi" 9 | }, 10 | "title": "Pööra ümber" 11 | }, 12 | "switch": { 13 | "data": { 14 | "hide_source": "Peida lähteolem", 15 | "name": "Nimi", 16 | "entity_id": "Lähteolem" 17 | }, 18 | "title": "Pööra ümber" 19 | }, 20 | "user": { 21 | "description": "See abimees võimaldab olemi käitumist ümber pöörata. Näiteks pöörake binaarandurite ja lülitite avatud/suletud või sisse/välja.", 22 | "title": "Pööra ümber", 23 | "menu_options": { 24 | "binary_sensor": "Binaaranduri ümberpööramine", 25 | "switch": "Lüliti ümberpööramine" 26 | } 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "entity_id": "Lähteolem", 35 | "hide_source": "Peida lähteolem" 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "name": "Nom", 7 | "hide_source": "Cacher l'entité d'origine", 8 | "entity_id": "Entité d'origine" 9 | }, 10 | "title": "Inverser 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "Entité d'origine", 15 | "name": "Nom", 16 | "hide_source": "Cacher l'entité d'origine" 17 | }, 18 | "title": "Inverser 👻" 19 | }, 20 | "user": { 21 | "menu_options": { 22 | "binary_sensor": "Inverser un capteur binaire", 23 | "switch": "Inverser un commutateur" 24 | }, 25 | "title": "Inverser 👻", 26 | "description": "Cette entrée permet d'inverser l'état d'une entité. Par exemple, inverser l'état ouvert/fermé ou allumé/éteint de capteurs binaires, ou de commutateurs." 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "name": "Nom", 35 | "entity_id": "Entité d'origine", 36 | "hide_source": "Cacher l'entité d'origine" 37 | }, 38 | "title": "Inverser 👻" 39 | }, 40 | "switch": { 41 | "data": { 42 | "hide_source": "Cacher l'entité d'origine", 43 | "name": "Nom", 44 | "entity_id": "Entité d'origine" 45 | }, 46 | "title": "Inverser 👻" 47 | } 48 | } 49 | }, 50 | "title": "Inverser 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/gl.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Inverso 👻", 3 | "config": { 4 | "step": { 5 | "switch": { 6 | "data": { 7 | "name": "Nome", 8 | "hide_source": "Ocultar entidade orixe", 9 | "entity_id": "Entidade orixe" 10 | }, 11 | "title": "Inverso 👻" 12 | }, 13 | "user": { 14 | "description": "Este axudante che permite invertir o comportamiento dunha entidade. Por exemplo, invertir abrir/cerrar ou encender/apagar de sensores binarios, e interruptores.", 15 | "menu_options": { 16 | "switch": "Invertir un interruptor", 17 | "binary_sensor": "Invertir un sensor binario" 18 | }, 19 | "title": "Inverso 👻" 20 | }, 21 | "binary_sensor": { 22 | "data": { 23 | "hide_source": "Ocultar entidade orixe", 24 | "name": "Nome", 25 | "entity_id": "Entidade orixe" 26 | }, 27 | "title": "Inverso 👻" 28 | } 29 | } 30 | }, 31 | "options": { 32 | "step": { 33 | "switch": { 34 | "data": { 35 | "hide_source": "Ocultar entidade orixe", 36 | "entity_id": "Entidade orixe", 37 | "name": "Nome" 38 | }, 39 | "title": "Inverso 👻" 40 | }, 41 | "binary_sensor": { 42 | "title": "Inverso 👻", 43 | "data": { 44 | "name": "Nome", 45 | "hide_source": "Ocultar entidade orixe", 46 | "entity_id": "Entidade orixe" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/he.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/hr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "Izvorni entitet", 7 | "hide_source": "Sakrij izvorni entitet", 8 | "name": "Ime" 9 | }, 10 | "title": "Obrnite 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "Izvorni entitet", 15 | "hide_source": "Sakrij izvorni entitet", 16 | "name": "Ime" 17 | }, 18 | "title": "Obrnite 👻" 19 | }, 20 | "user": { 21 | "description": "Ovaj pomoćnik vam omogućuje da promijenite ponašanje entiteta. Na primjer, inverzija otvaranja/zatvaranja ili uključivanja/isključivanja binarnih senzora i prekidača.", 22 | "menu_options": { 23 | "binary_sensor": "Obrnite binarni senzor", 24 | "switch": "Obrnite prekidač" 25 | }, 26 | "title": "Obrnite 👻" 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "entity_id": "Izvorni entitet", 35 | "hide_source": "Sakrij izvorni entitet", 36 | "name": "Ime" 37 | }, 38 | "title": "Obrnite 👻" 39 | }, 40 | "switch": { 41 | "data": { 42 | "entity_id": "Izvorni entitet", 43 | "hide_source": "Sakrij izvorni entitet", 44 | "name": "Ime" 45 | }, 46 | "title": "Obrnite 👻" 47 | } 48 | } 49 | }, 50 | "title": "Obrnite 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Inverz 👻", 3 | "config": { 4 | "step": { 5 | "switch": { 6 | "data": { 7 | "name": "Megnevezés", 8 | "hide_source": "Forrás entitás elrejtése", 9 | "entity_id": "Forrás entitás" 10 | }, 11 | "title": "Inverz 👻" 12 | }, 13 | "user": { 14 | "description": "Ez a segítő lehetővé teszi egy entitás állapotának megfordítását. Például megfordíthatja a bináris érzékelők és kapcsolók állapotát: nyitva/zárva, illetve be-/kikapcsolt.", 15 | "menu_options": { 16 | "switch": "Egy kapcsoló állapotának megfordítása", 17 | "binary_sensor": "Egy bináris érzékelő állapotának megfordítása" 18 | }, 19 | "title": "Inverz 👻" 20 | }, 21 | "binary_sensor": { 22 | "data": { 23 | "hide_source": "Forrás entitás elrejtése", 24 | "name": "Megnevezés", 25 | "entity_id": "Forrás entitás" 26 | }, 27 | "title": "Inverz 👻" 28 | } 29 | } 30 | }, 31 | "options": { 32 | "step": { 33 | "switch": { 34 | "data": { 35 | "hide_source": "Forrás entitás elrejtése", 36 | "entity_id": "Forrás entitás", 37 | "name": "Megnevezés" 38 | }, 39 | "title": "Inverz 👻" 40 | }, 41 | "binary_sensor": { 42 | "title": "Inverz 👻", 43 | "data": { 44 | "name": "Megnevezés", 45 | "hide_source": "Forrás entitás elrejtése", 46 | "entity_id": "Forrás entitás" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "Entità sorgente", 7 | "hide_source": "Nascondi entità sorgente", 8 | "name": "Nome" 9 | }, 10 | "title": "Inverso 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "Entità sorgente", 15 | "hide_source": "Nascondi entità sorgente", 16 | "name": "Nome" 17 | }, 18 | "title": "Inverso 👻" 19 | }, 20 | "user": { 21 | "description": "Questo aiutante ti permette di invertire il comportamento di un'entità. Ad esempio, apri/chiudi o accendi/spegni per un binary_sensor o uno switch", 22 | "menu_options": { 23 | "binary_sensor": "Inverti un binary_sensor", 24 | "switch": "Inverti uno switch" 25 | }, 26 | "title": "Inverso 👻" 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "switch": { 33 | "data": { 34 | "entity_id": "Entità sorgente", 35 | "hide_source": "Nascondi entità sorgente", 36 | "name": "Nome" 37 | }, 38 | "title": "Inverso 👻" 39 | }, 40 | "binary_sensor": { 41 | "data": { 42 | "entity_id": "Entità sorgente", 43 | "hide_source": "Nascondi entità sorgente", 44 | "name": "Nome" 45 | }, 46 | "title": "Inverso 👻" 47 | } 48 | } 49 | }, 50 | "title": "Inverso 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/ja.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/ka.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "출처 구성요소", 7 | "hide_source": "출처 구성요소 숨김", 8 | "name": "이름" 9 | }, 10 | "title": "반전 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "name": "이름", 15 | "entity_id": "출처 구성요소", 16 | "hide_source": "출처 구성요소 숨김" 17 | }, 18 | "title": "반전 👻" 19 | }, 20 | "user": { 21 | "description": "이 도우미는 구성요소의 동작을 반전시키는 데 도움이 됩니다. 예를 들어, 바이너리 센서 및 스위치의 열림/닫힘 또는 켜짐/꺼짐을 반전시킵니다.", 22 | "menu_options": { 23 | "switch": "스위치 반전", 24 | "binary_sensor": "이진 센서 반전" 25 | }, 26 | "title": "반전 👻" 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "hide_source": "출처 구성요소 숨김", 35 | "entity_id": "출처 구성요소", 36 | "name": "이름" 37 | }, 38 | "title": "반전 👻" 39 | }, 40 | "switch": { 41 | "data": { 42 | "name": "이름", 43 | "entity_id": "출처 구성요소", 44 | "hide_source": "출처 구성요소 숨김" 45 | }, 46 | "title": "반전 👻" 47 | } 48 | } 49 | }, 50 | "title": "반전 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/lb.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/lv.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/mt.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "name": "Isem", 7 | "entity_id": "Entita' tal-oriġini", 8 | "hide_source": "Aħbi l-entita' tal-oriġini" 9 | }, 10 | "title": "Il-Maqlub 👻" 11 | }, 12 | "user": { 13 | "description": "Dan l-assistent iħallik taqleb l-imġieba ta' entita'. Per eżempju, taqleb il-propjetajiet ta' iftaħ/agħlaq jew mixgħul/mitfi ta' sensors binarji, u swiċċijiet.", 14 | "menu_options": { 15 | "binary_sensor": "Aqleb sensor binarju", 16 | "switch": "Aqleb swiċċ" 17 | }, 18 | "title": "Maqlub 👻" 19 | }, 20 | "switch": { 21 | "data": { 22 | "hide_source": "Aħbi l-entita' tal-oriġini", 23 | "entity_id": "Entita' tal-oriġini", 24 | "name": "Isem" 25 | }, 26 | "title": "Aqleb 👻" 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "entity_id": "Entita' tal-oriġini", 35 | "hide_source": "Aħbi l-entita' tal-oriġini", 36 | "name": "Isem" 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Omgekeerd 👻", 3 | "config": { 4 | "step": { 5 | "switch": { 6 | "data": { 7 | "name": "Naam", 8 | "hide_source": "Bron entiteit verbergen", 9 | "entity_id": "Bron entiteit" 10 | }, 11 | "title": "Omgekeerd 👻" 12 | }, 13 | "user": { 14 | "description": "Deze helper stelt je in staat om het gedrag van een entiteit om te keren. Bijvoorbeeld, het omkeren van de open/sluit of aan/uit status van binaire sensoren en schakelaars.", 15 | "menu_options": { 16 | "switch": "Een schakelaar omkeren", 17 | "binary_sensor": "Een binaire sensor omkeren" 18 | }, 19 | "title": "Omgekeerd 👻" 20 | }, 21 | "binary_sensor": { 22 | "data": { 23 | "hide_source": "Verberg bron entiteit", 24 | "name": "Naam", 25 | "entity_id": "Bron entiteit" 26 | }, 27 | "title": "Omgekeerd 👻" 28 | } 29 | } 30 | }, 31 | "options": { 32 | "step": { 33 | "switch": { 34 | "data": { 35 | "hide_source": "Bron entiteit verbergen", 36 | "entity_id": "Bron entiteit", 37 | "name": "Naam" 38 | }, 39 | "title": "Omgekeerd 👻" 40 | }, 41 | "binary_sensor": { 42 | "title": "Omgekeerd 👻", 43 | "data": { 44 | "name": "Naam", 45 | "hide_source": "Bron entiteit verbergen", 46 | "entity_id": "Bron entiteit" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "Źródłowa encja", 7 | "name": "Nazwa", 8 | "hide_source": "Ukryj encję źródłową" 9 | }, 10 | "title": "Inverse 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "name": "Nazwa", 15 | "hide_source": "Ukryj encję źródłową", 16 | "entity_id": "Źródłowa encja" 17 | }, 18 | "title": "Inverse 👻" 19 | }, 20 | "user": { 21 | "menu_options": { 22 | "switch": "Odwróć przełącznik", 23 | "binary_sensor": "Odwróć czujnik binarny" 24 | }, 25 | "title": "Inverse 👻", 26 | "description": "Ten pomocnik umożliwia odwrócenie zachowania obiektu. Na przykład odwróć otwieranie/zamykanie lub włączanie/wyłączanie czujników binarnych i przełączników." 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "name": "Nazwa", 35 | "entity_id": "Encja źródłowa", 36 | "hide_source": "Ukryj encję źródłową" 37 | }, 38 | "title": "Inverse 👻" 39 | }, 40 | "switch": { 41 | "data": { 42 | "entity_id": "Encja źródłowa", 43 | "name": "Nazwa", 44 | "hide_source": "Ukryj encję źródłową" 45 | }, 46 | "title": "Inverse 👻" 47 | } 48 | } 49 | }, 50 | "title": "Inverse 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/ro.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "Источник", 7 | "name": "Имя", 8 | "hide_source": "Скрыть источник" 9 | }, 10 | "title": "Инвертировать 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "Источник", 15 | "name": "Имя", 16 | "hide_source": "Скрыть источник" 17 | }, 18 | "title": "Инвертировать 👻" 19 | }, 20 | "user": { 21 | "menu_options": { 22 | "binary_sensor": "Инвертировать бинарный сенсор", 23 | "switch": "Инвертировать переключатель" 24 | }, 25 | "title": "Инвертировать 👻", 26 | "description": "Этот помощник позволяет вам инвертировать поведение объекта. Например, можно изменить направление открытия/закрытия или включения/выключения бинарных сенсоров и переключателей." 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "entity_id": "Источник", 35 | "name": "Имя", 36 | "hide_source": "Скрыть источник" 37 | }, 38 | "title": "Инвертировать 👻" 39 | }, 40 | "switch": { 41 | "title": "Инвертировать 👻", 42 | "data": { 43 | "hide_source": "Скрыть источник", 44 | "entity_id": "Источник", 45 | "name": "Имя" 46 | } 47 | } 48 | } 49 | }, 50 | "title": "Инвертировать 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "Zdroj entity", 7 | "hide_source": "Skryť zdrojovú entitu", 8 | "name": "Názov" 9 | }, 10 | "title": "Inverse 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "Zdroj entity", 15 | "hide_source": "Skryť zdrojovú entitu", 16 | "name": "Názov" 17 | }, 18 | "title": "Inverse 👻" 19 | }, 20 | "user": { 21 | "menu_options": { 22 | "switch": "Inverse prepínač", 23 | "binary_sensor": "Inverse a binárne senzor" 24 | }, 25 | "description": "Tento pomocník vám umožní inverzovať správanie subjektu. Napríklad, zvrátiť otvorené / uzavreté alebo na / vypnutie binárnych senzorov a spínačov.", 26 | "title": "Inverse 👻" 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "switch": { 33 | "title": "Inverse 👻", 34 | "data": { 35 | "name": "Názov", 36 | "hide_source": "Skryť zdrojovú entitu", 37 | "entity_id": "Zdroj entity" 38 | } 39 | }, 40 | "binary_sensor": { 41 | "data": { 42 | "entity_id": "Zdroj entity", 43 | "hide_source": "Skryť zdrojovú entitu", 44 | "name": "Názov" 45 | }, 46 | "title": "Inverse 👻" 47 | } 48 | } 49 | }, 50 | "title": "Inverse 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/sl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/th.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "name": "İsim", 7 | "entity_id": "Kaynak nesnesi", 8 | "hide_source": "Kaynak nesnesini gizle" 9 | }, 10 | "title": "Ters 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "Kaynak nesnesi", 15 | "name": "İsim", 16 | "hide_source": "Kaynak nesnesini gizle" 17 | }, 18 | "title": "Ters 👻" 19 | }, 20 | "user": { 21 | "menu_options": { 22 | "binary_sensor": "İkili sensörü tersle", 23 | "switch": "Anahtarı tersle" 24 | }, 25 | "description": "Bu yardımcı, bir varlığın davranışını terslemenize olanak tanır. Örneğin, ikili sensörlerin ve anahtarların açma/kapama veya açma/kapama işlemlerini tersine çevirir.", 26 | "title": "Terslemek 👻" 27 | } 28 | } 29 | }, 30 | "title": "Tersle👻", 31 | "options": { 32 | "step": { 33 | "binary_sensor": { 34 | "data": { 35 | "entity_id": "Kaynak nesnesi", 36 | "hide_source": "Kaynak nesnesini gizle", 37 | "name": "İsim" 38 | }, 39 | "title": "Tersle 👻" 40 | }, 41 | "switch": { 42 | "data": { 43 | "entity_id": "Kaynak nesnesi", 44 | "hide_source": "Kaynak nesnesini gizle", 45 | "name": "İsim" 46 | }, 47 | "title": "Tersle👻" 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/uk.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/ur.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "الٹا 👻", 3 | "config": { 4 | "step": { 5 | "switch": { 6 | "data": { 7 | "name": "نام", 8 | "hide_source": "ماخذ ہستی کو چھپائیں", 9 | "entity_id": "ماخذ ہستی" 10 | }, 11 | "title": "الٹا 👻" 12 | }, 13 | "user": { 14 | "description": "یہ مددگار آپ کو کسی ہستی کے رویے کو الٹا کرنے کی اجازت دیتا ہے۔ مثال کے طور پر، بائنری سینسرز اور سوئچز کے کھلے/بند یا آن/آف کو ریورس کریں۔", 15 | "menu_options": { 16 | "switch": "ایک سوئچ کو الٹا کریں", 17 | "binary_sensor": "بائنری سینسر کو الٹا کریں" 18 | }, 19 | "title": "الٹا 👻" 20 | }, 21 | "binary_sensor": { 22 | "data": { 23 | "hide_source": "ماخذ ہستی کو چھپائیں", 24 | "name": "نام", 25 | "entity_id": "ماخذ ہستی" 26 | }, 27 | "title": "الٹا 👻" 28 | } 29 | } 30 | }, 31 | "options": { 32 | "step": { 33 | "switch": { 34 | "data": { 35 | "hide_source": "ماخذ ہستی کو چھپائیں", 36 | "entity_id": "ماخذ ہستی", 37 | "name": "نام" 38 | }, 39 | "title": "الٹا 👻" 40 | }, 41 | "binary_sensor": { 42 | "title": "الٹا 👻", 43 | "data": { 44 | "name": "نام", 45 | "hide_source": "ماخذ ہستی کو چھپائیں", 46 | "entity_id": "ماخذ ہستی" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/zh_Hans.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "源实体", 7 | "hide_source": "隐藏源实体", 8 | "name": "名称" 9 | }, 10 | "title": "翻转 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "源实体", 15 | "hide_source": "隐藏源实体", 16 | "name": "名称" 17 | }, 18 | "title": "翻转 👻" 19 | }, 20 | "user": { 21 | "description": "此工具帮您翻转一个实体的表现。例如,翻转二进制传感器和开关的开/关状态。", 22 | "menu_options": { 23 | "switch": "翻转开关", 24 | "binary_sensor": "翻转二级制传感器" 25 | }, 26 | "title": "翻转 👻" 27 | } 28 | } 29 | }, 30 | "title": "翻转 👻", 31 | "options": { 32 | "step": { 33 | "binary_sensor": { 34 | "data": { 35 | "entity_id": "源实体", 36 | "hide_source": "隐藏源实体", 37 | "name": "名称" 38 | }, 39 | "title": "翻转 👻" 40 | }, 41 | "switch": { 42 | "data": { 43 | "entity_id": "源实体", 44 | "hide_source": "隐藏源实体", 45 | "name": "名称" 46 | }, 47 | "title": "翻转 👻" 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/integrations/spook_inverse/translations/zh_Hant.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "binary_sensor": { 5 | "data": { 6 | "entity_id": "源實體", 7 | "hide_source": "隱藏源實體", 8 | "name": "名稱" 9 | }, 10 | "title": "翻轉 👻" 11 | }, 12 | "switch": { 13 | "data": { 14 | "entity_id": "源實體", 15 | "hide_source": "隱藏源實體", 16 | "name": "名稱" 17 | }, 18 | "title": "翻轉 👻" 19 | }, 20 | "user": { 21 | "menu_options": { 22 | "binary_sensor": "翻轉二級製傳感器", 23 | "switch": "翻轉開關" 24 | }, 25 | "title": "翻轉 👻", 26 | "description": "此工具幫您翻轉一個實體的表現。例如,翻轉二進製傳感器和開關的開/關狀態。" 27 | } 28 | } 29 | }, 30 | "options": { 31 | "step": { 32 | "binary_sensor": { 33 | "data": { 34 | "entity_id": "源實體", 35 | "hide_source": "隱藏源實體", 36 | "name": "名稱" 37 | }, 38 | "title": "翻轉 👻" 39 | }, 40 | "switch": { 41 | "title": "翻轉 👻", 42 | "data": { 43 | "entity_id": "源實體", 44 | "hide_source": "隱藏源實體", 45 | "name": "名稱" 46 | } 47 | } 48 | } 49 | }, 50 | "title": "翻轉 👻" 51 | } 52 | -------------------------------------------------------------------------------- /custom_components/spook/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "spook", 3 | "name": "Spook", 4 | "after_dependencies": ["blueprint", "cloud", "timer", "proximity"], 5 | "codeowners": ["@frenck"], 6 | "config_flow": true, 7 | "dependencies": [ 8 | "homeassistant", 9 | "input_number", 10 | "input_select", 11 | "lovelace", 12 | "number", 13 | "person", 14 | "recorder", 15 | "repairs", 16 | "select", 17 | "zone" 18 | ], 19 | "documentation": "https://spook.boo", 20 | "integration_type": "hub", 21 | "iot_class": "local_push", 22 | "issue_tracker": "https://github.com/frenck/spook/issues", 23 | "requirements": [], 24 | "version": "0.0.0" 25 | } 26 | -------------------------------------------------------------------------------- /custom_components/spook/number.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.const import Platform 8 | 9 | from .util import async_forward_platform_entry_setups_to_ectoplasm 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | 16 | 17 | async def async_setup_entry( 18 | hass: HomeAssistant, 19 | entry: ConfigEntry, 20 | async_add_entities: AddEntitiesCallback, 21 | ) -> None: 22 | """Set up Spook numbers.""" 23 | await async_forward_platform_entry_setups_to_ectoplasm( 24 | hass, 25 | entry, 26 | async_add_entities, 27 | Platform.NUMBER, 28 | ) 29 | -------------------------------------------------------------------------------- /custom_components/spook/select.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.const import Platform 8 | 9 | from .util import async_forward_platform_entry_setups_to_ectoplasm 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | 16 | 17 | async def async_setup_entry( 18 | hass: HomeAssistant, 19 | entry: ConfigEntry, 20 | async_add_entities: AddEntitiesCallback, 21 | ) -> None: 22 | """Set up Spook selects.""" 23 | await async_forward_platform_entry_setups_to_ectoplasm( 24 | hass, 25 | entry, 26 | async_add_entities, 27 | Platform.SELECT, 28 | ) 29 | -------------------------------------------------------------------------------- /custom_components/spook/sensor.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.const import Platform 8 | 9 | from .util import async_forward_platform_entry_setups_to_ectoplasm 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | 16 | 17 | async def async_setup_entry( 18 | hass: HomeAssistant, 19 | entry: ConfigEntry, 20 | async_add_entities: AddEntitiesCallback, 21 | ) -> None: 22 | """Set up Spook sensors.""" 23 | await async_forward_platform_entry_setups_to_ectoplasm( 24 | hass, 25 | entry, 26 | async_add_entities, 27 | Platform.SENSOR, 28 | ) 29 | -------------------------------------------------------------------------------- /custom_components/spook/switch.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.const import Platform 8 | 9 | from .util import async_forward_platform_entry_setups_to_ectoplasm 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | 16 | 17 | async def async_setup_entry( 18 | hass: HomeAssistant, 19 | entry: ConfigEntry, 20 | async_add_entities: AddEntitiesCallback, 21 | ) -> None: 22 | """Set up Spook switches.""" 23 | await async_forward_platform_entry_setups_to_ectoplasm( 24 | hass, 25 | entry, 26 | async_add_entities, 27 | Platform.SWITCH, 28 | ) 29 | -------------------------------------------------------------------------------- /custom_components/spook/time.py: -------------------------------------------------------------------------------- 1 | """Spook - Your homie.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from homeassistant.const import Platform 8 | 9 | from .util import async_forward_platform_entry_setups_to_ectoplasm 10 | 11 | if TYPE_CHECKING: 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 15 | 16 | 17 | async def async_setup_entry( 18 | hass: HomeAssistant, 19 | entry: ConfigEntry, 20 | async_add_entities: AddEntitiesCallback, 21 | ) -> None: 22 | """Set up Spook times.""" 23 | await async_forward_platform_entry_setups_to_ectoplasm( 24 | hass, 25 | entry, 26 | async_add_entities, 27 | Platform.TIME, 28 | ) 29 | -------------------------------------------------------------------------------- /custom_components/spook/translations/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": { 3 | "user_issue": { 4 | "fix_flow": { 5 | "step": { 6 | "confirm": { 7 | "description": "{description}", 8 | "title": "{title}" 9 | } 10 | } 11 | }, 12 | "title": "{title}" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /custom_components/spook/translations/bg.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": { 3 | "user_issue": { 4 | "fix_flow": { 5 | "step": { 6 | "confirm": { 7 | "title": "{title}", 8 | "description": "{description}" 9 | } 10 | } 11 | }, 12 | "title": "{title}" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /custom_components/spook/translations/et.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": { 3 | "user_issue": { 4 | "title": "{title}", 5 | "fix_flow": { 6 | "step": { 7 | "confirm": { 8 | "description": "{description}", 9 | "title": "{title}" 10 | } 11 | } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /custom_components/spook/translations/ka.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": { 3 | "user_issue": { 4 | "title": "{title}", 5 | "fix_flow": { 6 | "step": { 7 | "confirm": { 8 | "description": "{description}", 9 | "title": "{title}" 10 | } 11 | } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /custom_components/spook/translations/lb.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": { 3 | "user_issue": { 4 | "fix_flow": { 5 | "step": { 6 | "confirm": { 7 | "description": "{description}", 8 | "title": "{title}" 9 | } 10 | } 11 | }, 12 | "title": "{title}" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /custom_components/spook/translations/lv.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": { 3 | "user_issue": { 4 | "fix_flow": { 5 | "step": { 6 | "confirm": { 7 | "title": "{title}", 8 | "description": "{description}" 9 | } 10 | } 11 | }, 12 | "title": "{title}" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /custom_components/spook/translations/ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": { 3 | "user_issue": { 4 | "fix_flow": { 5 | "step": { 6 | "confirm": { 7 | "description": "{description}", 8 | "title": "{title}" 9 | } 10 | } 11 | }, 12 | "title": "{title}" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /custom_components/spook/translations/sl.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": { 3 | "user_issue": { 4 | "fix_flow": { 5 | "step": { 6 | "confirm": { 7 | "description": "{description}", 8 | "title": "{title}" 9 | } 10 | } 11 | }, 12 | "title": "{title}" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /documentation/.nvmrc: -------------------------------------------------------------------------------- 1 | 22.16.0 2 | -------------------------------------------------------------------------------- /documentation/blueprints.md: -------------------------------------------------------------------------------- 1 | --- 2 | subject: Reference 3 | title: Blueprints using Spook's features 4 | short_title: Blueprints 5 | subtitle: Don't be a blueprint. Be an original. 6 | thumbnail: images/social.png 7 | description: A list of blueprints using Spook's features, that you can use in your Home Assistant instance. 8 | date: 2023-08-09T21:29:00+02:00 9 | --- 10 | 11 | Spook is currently unaware of any blueprints that are using features provided by Spook. 12 | 13 | Spook is sad 😭. 14 | 15 | --- 16 | 17 | Found or made a bluepint leveraging Spook? [Let us know](https://github.com/frenck/spook/discussions), and we'll add it to the list! 🙏 18 | -------------------------------------------------------------------------------- /documentation/disclaimers.md: -------------------------------------------------------------------------------- 1 | --- 2 | subject: Boring stuff 3 | title: Disclaimers 4 | subtitle: Some disclaimer stuff you should read. 5 | thumbnail: images/social.png 6 | description: Some disclaimer stuff you should read. 7 | date: 2023-08-09T21:29:00+02:00 8 | --- 9 | 10 | All product names, logos, brands, trademarks and registered trademarks are property of their respective owners. All company, product, and service names used are for identification purposes only. Use of these names, logos, trademarks, and brands does not imply affiliation or endorsement. 11 | 12 | --- 13 | 14 | I guess it goes without saying that this custom integration is not affiliated with, endorsed, or recommended by the Home Assistant project. 15 | 16 | :::{warning} Just to be clear... 17 | **Spook is not supported by the Home Assistant project.** 18 | ::: 19 | 20 | This custom integration is provided as-is, without any warranty. 21 | 22 | _#SorryNotSorry_ 23 | -------------------------------------------------------------------------------- /documentation/helpers.md: -------------------------------------------------------------------------------- 1 | --- 2 | subject: Features 3 | title: Helpers 4 | subtitle: Although, some people, cannot be helped. 🤷 5 | date: 2023-09-21T21:29:00+02:00 6 | --- 7 | 8 | Spook also provides {term}`helpers `. The helpers allows you to perform calculations or modifications on existing {term}`entities ` and return the result of that as a new entity. 9 | 10 | ::::{grid} 1 1 1 1 11 | 12 | :::{card} Inverse 13 | :footer: 📚 [Learn more](helpers/inverse) 14 | 15 | Inverts the behavior of a switch or binary sensor entities. On becomes off, and off becomes on. The world is upside down! 16 | 17 | ::: 18 | 19 | :::: 20 | -------------------------------------------------------------------------------- /documentation/images/areas/add_alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/add_alias.png -------------------------------------------------------------------------------- /documentation/images/areas/add_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/add_device.png -------------------------------------------------------------------------------- /documentation/images/areas/add_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/add_entity.png -------------------------------------------------------------------------------- /documentation/images/areas/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/create.png -------------------------------------------------------------------------------- /documentation/images/areas/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/delete.png -------------------------------------------------------------------------------- /documentation/images/areas/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/example.png -------------------------------------------------------------------------------- /documentation/images/areas/remove_alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/remove_alias.png -------------------------------------------------------------------------------- /documentation/images/areas/remove_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/remove_device.png -------------------------------------------------------------------------------- /documentation/images/areas/remove_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/remove_entity.png -------------------------------------------------------------------------------- /documentation/images/areas/set_aliases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/areas/set_aliases.png -------------------------------------------------------------------------------- /documentation/images/devices/disable_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/devices/disable_device.png -------------------------------------------------------------------------------- /documentation/images/devices/enable_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/devices/enable_device.png -------------------------------------------------------------------------------- /documentation/images/devices_entities/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/devices_entities/example.png -------------------------------------------------------------------------------- /documentation/images/entities/delete_all_orphaned_entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/entities/delete_all_orphaned_entities.png -------------------------------------------------------------------------------- /documentation/images/entities/disable_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/entities/disable_entity.png -------------------------------------------------------------------------------- /documentation/images/entities/enable_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/entities/enable_entity.png -------------------------------------------------------------------------------- /documentation/images/entities/hide_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/entities/hide_entity.png -------------------------------------------------------------------------------- /documentation/images/entities/list_orphaned_database_entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/entities/list_orphaned_database_entities.png -------------------------------------------------------------------------------- /documentation/images/entities/rename_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/entities/rename_entity.png -------------------------------------------------------------------------------- /documentation/images/entities/unhide_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/entities/unhide_entity.png -------------------------------------------------------------------------------- /documentation/images/entities/update_entity_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/entities/update_entity_id.png -------------------------------------------------------------------------------- /documentation/images/floors/add_alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/floors/add_alias.png -------------------------------------------------------------------------------- /documentation/images/floors/add_area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/floors/add_area.png -------------------------------------------------------------------------------- /documentation/images/floors/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/floors/create.png -------------------------------------------------------------------------------- /documentation/images/floors/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/floors/delete.png -------------------------------------------------------------------------------- /documentation/images/floors/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/floors/example.png -------------------------------------------------------------------------------- /documentation/images/floors/remove_alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/floors/remove_alias.png -------------------------------------------------------------------------------- /documentation/images/floors/remove_area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/floors/remove_area.png -------------------------------------------------------------------------------- /documentation/images/floors/set_aliases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/floors/set_aliases.png -------------------------------------------------------------------------------- /documentation/images/helpers/inverse/configure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/helpers/inverse/configure.png -------------------------------------------------------------------------------- /documentation/images/helpers/inverse/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/helpers/inverse/done.png -------------------------------------------------------------------------------- /documentation/images/helpers/inverse/helper_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/helpers/inverse/helper_dialog.png -------------------------------------------------------------------------------- /documentation/images/helpers/inverse/select_entity_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/helpers/inverse/select_entity_type.png -------------------------------------------------------------------------------- /documentation/images/installation/accept_license.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/installation/accept_license.png -------------------------------------------------------------------------------- /documentation/images/installation/add_integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/installation/add_integration.png -------------------------------------------------------------------------------- /documentation/images/installation/experimental_restart_home_assistant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/installation/experimental_restart_home_assistant.png -------------------------------------------------------------------------------- /documentation/images/installation/find_spook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/installation/find_spook.png -------------------------------------------------------------------------------- /documentation/images/installation/hacs_experimental_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/installation/hacs_experimental_download.png -------------------------------------------------------------------------------- /documentation/images/installation/hacs_experimental_download_fab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/installation/hacs_experimental_download_fab.png -------------------------------------------------------------------------------- /documentation/images/installation/hacs_experimental_find_spook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/installation/hacs_experimental_find_spook.png -------------------------------------------------------------------------------- /documentation/images/installation/restart_home_assistant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/installation/restart_home_assistant.png -------------------------------------------------------------------------------- /documentation/images/integration/disable_config_entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integration/disable_config_entry.png -------------------------------------------------------------------------------- /documentation/images/integration/disable_polling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integration/disable_polling.png -------------------------------------------------------------------------------- /documentation/images/integration/enable_config_entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integration/enable_config_entry.png -------------------------------------------------------------------------------- /documentation/images/integration/enable_polling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integration/enable_polling.png -------------------------------------------------------------------------------- /documentation/images/integration/ignore_all_discovered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integration/ignore_all_discovered.png -------------------------------------------------------------------------------- /documentation/images/integrations/automation/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/automation/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/automation/unknown_areas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/automation/unknown_areas.png -------------------------------------------------------------------------------- /documentation/images/integrations/automation/unknown_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/automation/unknown_device.png -------------------------------------------------------------------------------- /documentation/images/integrations/blueprint/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/blueprint/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/cloud/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/cloud/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/group/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/group/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_number/decrease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_number/decrease.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_number/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_number/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_number/increase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_number/increase.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_number/maximum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_number/maximum.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_number/minimum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_number/minimum.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_select/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_select/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_select/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_select/random.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_select/shuffle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_select/shuffle.png -------------------------------------------------------------------------------- /documentation/images/integrations/input_select/sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/input_select/sort.png -------------------------------------------------------------------------------- /documentation/images/integrations/lovelace/unknown_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/lovelace/unknown_entity.png -------------------------------------------------------------------------------- /documentation/images/integrations/number/decrease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/number/decrease.png -------------------------------------------------------------------------------- /documentation/images/integrations/number/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/number/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/number/increase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/number/increase.png -------------------------------------------------------------------------------- /documentation/images/integrations/number/maximum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/number/maximum.png -------------------------------------------------------------------------------- /documentation/images/integrations/number/minimum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/number/minimum.png -------------------------------------------------------------------------------- /documentation/images/integrations/person/add_device_tracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/person/add_device_tracker.png -------------------------------------------------------------------------------- /documentation/images/integrations/person/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/person/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/person/remove_device_tracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/person/remove_device_tracker.png -------------------------------------------------------------------------------- /documentation/images/integrations/recorder/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/recorder/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/recorder/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/recorder/import.png -------------------------------------------------------------------------------- /documentation/images/integrations/repairs/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/repairs/create.png -------------------------------------------------------------------------------- /documentation/images/integrations/repairs/create_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/repairs/create_result.png -------------------------------------------------------------------------------- /documentation/images/integrations/repairs/devices_entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/repairs/devices_entities.png -------------------------------------------------------------------------------- /documentation/images/integrations/repairs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/repairs/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/repairs/ignore_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/repairs/ignore_all.png -------------------------------------------------------------------------------- /documentation/images/integrations/repairs/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/repairs/remove.png -------------------------------------------------------------------------------- /documentation/images/integrations/repairs/unignore_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/repairs/unignore_all.png -------------------------------------------------------------------------------- /documentation/images/integrations/scene/unknown_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/scene/unknown_entity.png -------------------------------------------------------------------------------- /documentation/images/integrations/script/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/script/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/script/unknown_area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/script/unknown_area.png -------------------------------------------------------------------------------- /documentation/images/integrations/script/unknown_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/script/unknown_device.png -------------------------------------------------------------------------------- /documentation/images/integrations/select/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/select/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/zone/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/zone/create.png -------------------------------------------------------------------------------- /documentation/images/integrations/zone/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/zone/delete.png -------------------------------------------------------------------------------- /documentation/images/integrations/zone/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/zone/example.png -------------------------------------------------------------------------------- /documentation/images/integrations/zone/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/integrations/zone/update.png -------------------------------------------------------------------------------- /documentation/images/labels/add_to_area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/add_to_area.png -------------------------------------------------------------------------------- /documentation/images/labels/add_to_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/add_to_device.png -------------------------------------------------------------------------------- /documentation/images/labels/add_to_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/add_to_entity.png -------------------------------------------------------------------------------- /documentation/images/labels/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/create.png -------------------------------------------------------------------------------- /documentation/images/labels/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/delete.png -------------------------------------------------------------------------------- /documentation/images/labels/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/example.png -------------------------------------------------------------------------------- /documentation/images/labels/remove_from_area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/remove_from_area.png -------------------------------------------------------------------------------- /documentation/images/labels/remove_from_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/remove_from_device.png -------------------------------------------------------------------------------- /documentation/images/labels/remove_from_entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/labels/remove_from_entity.png -------------------------------------------------------------------------------- /documentation/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/logo.png -------------------------------------------------------------------------------- /documentation/images/logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/logo_dark.png -------------------------------------------------------------------------------- /documentation/images/misc/restart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/misc/restart.png -------------------------------------------------------------------------------- /documentation/images/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/social.png -------------------------------------------------------------------------------- /documentation/images/spook/boo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/spook/boo.png -------------------------------------------------------------------------------- /documentation/images/spook/random_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/spook/random_fail.png -------------------------------------------------------------------------------- /documentation/images/support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/support.png -------------------------------------------------------------------------------- /documentation/images/usage/device_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/usage/device_example.png -------------------------------------------------------------------------------- /documentation/images/usage/repairs_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/usage/repairs_example.png -------------------------------------------------------------------------------- /documentation/images/usage/services_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/images/usage/services_example.png -------------------------------------------------------------------------------- /documentation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spook 👻 Your homie. 3 | subtitle: A scary powerful toolbox for Home Assistant 4 | thumbnail: images/social.png 5 | description: Spook is a scary powerful toolbox for Home Assistant 👻 It can be installed as an custom integration in your Home Assistant via the Home Assistant Community Store. 6 | --- 7 | 8 | ```{image} ./images/logo.png 9 | :alt: The Spook logo 10 | :align: center 11 | ``` 12 | 13 | _Spook is a scary powerful toolbox for Home Assistant, but don't be scared, Spook is your homie!_ 14 | 15 | Hi! 👋 16 | 17 | Welcome to my (one and only) {term}`custom integration ` for {term}`Home Assistant`: Spook! 👻 18 | 19 | Spook is my friend and my personal little pet project that I develop in my free spare time to add some features to Home Assistant that I (or others) think are missing; but are not a good fit to ship with Home Assistant itself. 20 | 21 | If you would ever ask me, if you should use Spook? I would say: Absofuckinglutely! (But, I might be biased 😉) Spook has become a scary powerful toolbox for Home Assistant, that many have come to love. 22 | 23 | Want to start using Spook? I would recommend picking up at the [getting started](about) section of the documentation. If you like to learn more about Spook its origin first, check out the [background and history](background_and_history) page for more information on that. Lastly, maybe the [](faq) page can help you. 24 | 25 | Boo! 👻 26 | 27 | ../Frenck 28 | -------------------------------------------------------------------------------- /documentation/integrations_devices.md: -------------------------------------------------------------------------------- 1 | --- 2 | subject: Documentation 3 | title: Integrations 4 | subtitle: A little bit of ectoplasm goes a long way. 🧪 5 | date: 2023-08-09T21:29:00+02:00 6 | --- 7 | 8 | Spook enhances the following Home Assistant integrations by sprinkling some 9 | ectoplasmic goodness on top of them. 10 | 11 | ::::{grid} 1 1 2 3 12 | 13 | :::: 14 | -------------------------------------------------------------------------------- /documentation/myst.yml: -------------------------------------------------------------------------------- 1 | # See docs at: https://mystmd.org/guide/frontmatter 2 | version: 1 3 | site: 4 | title: Spook 👻 a scary powerful toolbox for Home Assistant. 5 | template: book-theme 6 | nav: [] 7 | actions: 8 | - title: "👻 Install via HACS 🏡" 9 | url: https://my.home-assistant.io/redirect/hacs_repository/?owner=frenck&repository=spook&category=integrations 10 | - title: 💬 Support 11 | url: https://github.com/frenck/spook/discussions 12 | domains: 13 | - "frenck.github.io" 14 | options: 15 | logo: ./images/logo.png 16 | logo_dark: ./images/logo_dark.png 17 | twitter: frenck 18 | 19 | project: 20 | title: Spook 👻 Your homie. 21 | description: "👻 Spook is a scary powerful toolbox for Home Assistant." 22 | keywords: 23 | - spook 24 | - "Home Assistant" 25 | - "Home Assistant Community Store" 26 | - "Home Assistant Community" 27 | - "hass" 28 | - "hacs" 29 | - "custom integration" 30 | - "powertools" 31 | - "iot" 32 | - "internet of things" 33 | - "home automation" 34 | - "smart home" 35 | github: https://github.com/frenck/spook 36 | abbreviations: 37 | HACS: Home Assistant Community Store 38 | HA: Home Assistant 39 | -------------------------------------------------------------------------------- /documentation/previous_features.md: -------------------------------------------------------------------------------- 1 | --- 2 | subject: About the project 3 | title: Features previously available in Spook 4 | short_title: Previous features 5 | subtitle: Adieu, goodbye, auf Wiederseh'n! 6 | thumbnail: images/social.png 7 | description: Sometimes features are removed from Spook. Most of the time, this is because the feature is now available in Home Assistant itself. This page is dedicated to those fallen features. 8 | date: 2023-08-09T21:29:00+02:00 9 | --- 10 | 11 | :::{iframe} https://www.youtube.com/embed/skl6N3zGv-s 12 | :width: 100% 13 | ::: 14 | 15 | Some features have been removed from Spook over time; this page is dedicated to those fallen features. 16 | 17 | ```{card} 18 | :header: **Template engine extensions** 19 | :footer: As of Home Assistant 2025.4, these template functions are now available in Home Assistant core. 20 | Spook provided several template functions to enhance Home Assistant's template engine, including: 21 | - `flatten`: Flatten lists of lists 22 | - `md5`: Calculate MD5 hashes 23 | - `sha1`: Calculate SHA1 hashes 24 | - `sha256`: Calculate SHA256 hashes 25 | - `sha512`: Calculate SHA512 hashes 26 | - `shuffle`: Shuffle lists 27 | - `typeof`: Reveal the type of a value 28 | ``` 29 | 30 | ```{card} 31 | :header: **Obsolete integration & platform YAML configuration repairs** 32 | :footer: As of Home Assistant 2023.6, Home Assistant will raise repair issues for these cases itself. 33 | Spook looked for YAML configuration of integrations (and older integration platforms) that no longer support being configured via YAML. If it found those, it would raise a repair issue in your repairs dashboard to keep your YAML configuration nice and clean. 34 | ``` 35 | -------------------------------------------------------------------------------- /documentation/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/documentation/public/favicon.ico -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Spook 👻 Your homie", 3 | "render_readme": true, 4 | "homeassistant": "2025.2.0", 5 | "hacs": "1.34.0", 6 | "zip_release": true, 7 | "filename": "spook.zip" 8 | } 9 | -------------------------------------------------------------------------------- /logos/icon_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_1024x1024.png -------------------------------------------------------------------------------- /logos/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_128x128.png -------------------------------------------------------------------------------- /logos/icon_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_2048x2048.png -------------------------------------------------------------------------------- /logos/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_256x256.png -------------------------------------------------------------------------------- /logos/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_32x32.png -------------------------------------------------------------------------------- /logos/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_512x512.png -------------------------------------------------------------------------------- /logos/icon_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_64x64.png -------------------------------------------------------------------------------- /logos/icon_transparent_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_transparent_1024x1024.png -------------------------------------------------------------------------------- /logos/icon_transparent_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_transparent_128x128.png -------------------------------------------------------------------------------- /logos/icon_transparent_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_transparent_2048x2048.png -------------------------------------------------------------------------------- /logos/icon_transparent_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_transparent_256x256.png -------------------------------------------------------------------------------- /logos/icon_transparent_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_transparent_32x32.png -------------------------------------------------------------------------------- /logos/icon_transparent_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_transparent_512x512.png -------------------------------------------------------------------------------- /logos/icon_transparent_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/icon_transparent_64x64.png -------------------------------------------------------------------------------- /logos/logo_wordmark_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_1024x1024.png -------------------------------------------------------------------------------- /logos/logo_wordmark_1024x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_1024x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_128x128.png -------------------------------------------------------------------------------- /logos/logo_wordmark_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_2048x2048.png -------------------------------------------------------------------------------- /logos/logo_wordmark_2048x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_2048x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_256_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_256_64.png -------------------------------------------------------------------------------- /logos/logo_wordmark_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_256x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_512x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_512x128.png -------------------------------------------------------------------------------- /logos/logo_wordmark_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_512x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_1024x1024.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_1024x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_1024x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_2048x2048.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_2048x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_2048x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_256x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_256x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_256x64.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_512x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_512x128.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_512x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_1024x1024.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_2048x2048.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_512x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_alt_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_alt_1024x1024.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_alt_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_alt_2048x2048.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_alt_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_alt_256x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_alt_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_alt_512x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_transparent_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_transparent_1024x1024.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_transparent_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_transparent_2048x2048.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_transparent_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_transparent_512x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_transparent_alt_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_transparent_alt_1024x1024.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_transparent_alt_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_transparent_alt_2048x2048.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_transparent_alt_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_transparent_alt_256x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_margin_transparent_alt_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_margin_transparent_alt_512x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_transparent_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_transparent_1024x1024.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_transparent_1024x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_transparent_1024x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_transparent_128x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_transparent_128x64.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_transparent_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_transparent_2048x2048.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_transparent_2048x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_transparent_2048x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_transparent_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_transparent_256x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_transparent_512x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_transparent_512x128.png -------------------------------------------------------------------------------- /logos/logo_wordmark_catchphrase_transparent_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_catchphrase_transparent_512x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_1024x1024.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_1024x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_1024x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_128x128.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_2048x2048.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_2048x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_2048x512.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_256x256.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_256x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_256x64.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_512x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_512x128.png -------------------------------------------------------------------------------- /logos/logo_wordmark_transparent_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frenck/spook/3c71446fc122a319a81e98f1816173d287b6a84b/logos/logo_wordmark_transparent_512x512.png -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.organization=frenck 2 | sonar.projectKey=frenck_spook 3 | sonar.projectName=Spook - Your homie 4 | sonar.projectVersion=1.0 5 | 6 | sonar.links.homepage=https://github.com/frenck/spook 7 | sonar.links.ci=https://github.com/frenck/spook/actions 8 | sonar.links.issue=https://github.com/frenck/spook/issues 9 | sonar.links.scm=https://github.com/frenck/spook/tree/main 10 | 11 | sonar.language=py 12 | sonar.sourceEncoding=UTF-8 13 | sonar.sources=custom_components/spook 14 | 15 | sonar.python.version=3.11 16 | --------------------------------------------------------------------------------