329 files

This commit is contained in:
Home Assistant Version Control
2026-08-06 13:56:25 +00:00
parent 0df89406fa
commit 7afe7add1d
330 changed files with 13098 additions and 5942 deletions
+4
View File
@@ -392,3 +392,7 @@ NOTIF_TYPE_MANDATORY_PARENT_ALERT: Final = "mandatory_parent_alert"
NOTIF_TYPE_MONTHLY_REPORT: Final = "monthly_report"
NOTIF_TYPE_SEASON_CHAMPION: Final = "season_champion"
NOTIF_TYPE_FAMILY_GOAL_REACHED: Final = "family_goal_reached"
# Default notification tap target. Must match PANEL_URL_PATH in panel.py —
# a bare /taskmate is the static-files prefix and returns 403, not the panel.
DEFAULT_NOTIFICATION_NAV_URL: Final = "/taskmate-admin"
@@ -19,6 +19,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.event import async_track_time_change
from .const import (
DEFAULT_NOTIFICATION_NAV_URL,
NOTIF_TYPE_ALL_CHORES_DONE,
NOTIF_TYPE_BADGE_EARNED,
NOTIF_TYPE_BEDTIME_REMINDER,
@@ -88,6 +89,27 @@ NOTIFICATION_TYPES_BY_ID: dict[str, NotificationTypeMeta] = {
}
def _validate_nav_url(value: str) -> str:
"""Normalise and vet a notification tap target.
The tap target is opened on the *recipient's* device, so only dashboard
paths, web URLs and the companion app's noAction sentinel are allowed —
never intent:// / app:// / homeassistant:// style deep links.
"""
value = (value or "").strip()
if value in ("", "noAction"):
return value
if value.lower().startswith(("http://", "https://")):
return value
if (
value.startswith("/")
and not value.startswith("//")
and not any(ord(c) <= 32 or ord(c) == 127 for c in value)
):
return value
raise ValueError("nav_url must be a /path, an http(s) URL, or noAction")
def _parse_hhmm(value: str) -> tuple[int, int] | None:
"""Parse "HH:MM" into (hour, minute); None if blank/malformed."""
if not value:
@@ -315,7 +337,11 @@ class NotificationCoordinator:
per_type = (getattr(cfg, "nav_url", "") or "").strip()
if per_type:
return per_type
return str(self.storage.get_setting("notification_nav_url", "/taskmate") or "").strip()
return str(
self.storage.get_setting(
"notification_nav_url", DEFAULT_NOTIFICATION_NAV_URL
) or ""
).strip()
def _resolve_notify_service(self, recipient_id: str) -> str:
if recipient_id.startswith("child:"):
@@ -711,8 +737,10 @@ class NotificationCoordinator:
async def set_nav_url(self, type_id: str | None, nav_url: str) -> None:
"""Set the tap target — global (type_id falsy) or per notification type."""
nav_url = (nav_url or "").strip()
nav_url = _validate_nav_url(nav_url)
if type_id:
if type_id not in NOTIFICATION_TYPES_BY_ID:
raise ValueError(f"Unknown notification type {type_id}")
self.storage.set_notification_nav_url(type_id, nav_url)
else:
self.storage.set_setting("notification_nav_url", nav_url)
+1 -1
View File
@@ -17,5 +17,5 @@
"iot_class": "calculated",
"issue_tracker": "https://github.com/tempus2016/taskmate/issues",
"requirements": [],
"version": "5.0.2"
"version": "5.0.3"
}
+17 -1
View File
@@ -8,7 +8,7 @@ from typing import Any
from homeassistant.core import HomeAssistant
from homeassistant.helpers.storage import Store
from .const import DOMAIN
from .const import DEFAULT_NOTIFICATION_NAV_URL, DOMAIN
from .models import (
AwardedBadge,
Badge,
@@ -131,6 +131,8 @@ class TaskMateStorage:
self._run_notifications_migration()
self._data["notifications_migration_done"] = True
self._migrate_nav_url_default()
# Badge migration / seeding
self._seed_builtin_badges(is_fresh=is_fresh)
@@ -664,6 +666,20 @@ class TaskMateStorage:
if is_fresh:
self._data["badges_backfill_pending"] = True
def _migrate_nav_url_default(self) -> None:
"""Rewrite the broken v5.0.2 notification tap target.
v5.0.2 shipped "/taskmate" as the default, but the panel lives at
/taskmate-admin (/taskmate is the static-files prefix and 403s), so a
persisted "/taskmate" is broken for every install. Idempotent.
"""
settings = self._data.get("settings", {}) or {}
if settings.get("notification_nav_url") == "/taskmate":
settings["notification_nav_url"] = DEFAULT_NOTIFICATION_NAV_URL
for cfg in (self._data.get("notification_config", {}) or {}).values():
if isinstance(cfg, dict) and cfg.get("nav_url") == "/taskmate":
cfg["nav_url"] = DEFAULT_NOTIFICATION_NAV_URL
def _run_notifications_migration(self) -> None:
"""Seed parent_recipients + notification_config from legacy notify_service.
+10 -2
View File
@@ -57,7 +57,13 @@ from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant
from . import photos
from .const import DEFAULT_TIME_PERIODS, DOMAIN, MAX_TIME_PERIODS, TIME_CATEGORY_ICONS
from .const import (
DEFAULT_NOTIFICATION_NAV_URL,
DEFAULT_TIME_PERIODS,
DOMAIN,
MAX_TIME_PERIODS,
TIME_CATEGORY_ICONS,
)
from .coordinator import TaskMateCoordinator
from .models import BonusSubTask, Reward
@@ -1910,7 +1916,9 @@ async def ws_notif_get_state(hass, connection, msg, coordinator):
"streak_at_risk_cutoff_time": c.storage.get_streak_at_risk_cutoff(),
"mandatory_escalation_reminder_minutes": c.storage.get_escalation_reminder_minutes(),
"mandatory_escalation_parent_minutes": c.storage.get_escalation_parent_minutes(),
"notification_nav_url": c.storage.get_setting("notification_nav_url", "/taskmate"),
"notification_nav_url": c.storage.get_setting(
"notification_nav_url", DEFAULT_NOTIFICATION_NAV_URL
),
},
}
connection.send_result(msg["id"], state)
@@ -867,7 +867,7 @@
"panel.notif_section_recipients_desc": "Weise jedem Kind und Elternteil einen Home Assistant Benachrichtigungsdienst zu. Leer lassen, um Benachrichtigungen für diese Person zu überspringen.",
"panel.notif_streak_cutoff_label": "Frist",
"panel.notif_nav_url_global_label": "Beim Tippen öffnen",
"panel.notif_nav_url_global_hint": "Wohin eine angetippte Benachrichtigung führt: ein Pfad wie /taskmate oder /lovelace/parents, eine vollständige URL oder noAction. Gilt für alle Benachrichtigungen, sofern unten nicht pro Typ überschrieben.",
"panel.notif_nav_url_global_hint": "Wohin eine angetippte Benachrichtigung führt: ein Pfad wie /taskmate-admin oder /lovelace/parents, eine vollständige URL oder noAction. Gilt für alle Benachrichtigungen, sofern unten nicht pro Typ überschrieben.",
"panel.notif_nav_url_row_placeholder": "Nutzt globalen Standard",
"panel.notif_tab_title": "Benachrichtigungen",
"panel.template_assignment_mode_label": "Zuweisungsmodus",
@@ -917,7 +917,7 @@
"panel.notif_section_recipients_desc": "Map each child and parent to a Home Assistant notify service. Leave blank to skip notifications for that person.",
"panel.notif_streak_cutoff_label": "Cutoff",
"panel.notif_nav_url_global_label": "When tapped, open",
"panel.notif_nav_url_global_hint": "Where a tapped notification opens: a path like /taskmate or /lovelace/parents, a full URL, or noAction. Applies to all notifications unless overridden per type below.",
"panel.notif_nav_url_global_hint": "Where a tapped notification opens: a path like /taskmate-admin or /lovelace/parents, a full URL, or noAction. Applies to all notifications unless overridden per type below.",
"panel.notif_nav_url_row_placeholder": "Uses global default",
"panel.notif_send_test": "Send test",
"panel.notif_test_sent": "Test sent to {count} recipient(s) + persistent notification",
@@ -917,7 +917,7 @@
"panel.notif_section_recipients_desc": "Map each child and parent to a Home Assistant notify service. Leave blank to skip notifications for that person.",
"panel.notif_streak_cutoff_label": "Cutoff",
"panel.notif_nav_url_global_label": "When tapped, open",
"panel.notif_nav_url_global_hint": "Where a tapped notification opens: a path like /taskmate or /lovelace/parents, a full URL, or noAction. Applies to all notifications unless overridden per type below.",
"panel.notif_nav_url_global_hint": "Where a tapped notification opens: a path like /taskmate-admin or /lovelace/parents, a full URL, or noAction. Applies to all notifications unless overridden per type below.",
"panel.notif_nav_url_row_placeholder": "Uses global default",
"panel.notif_send_test": "Send test",
"panel.notif_test_sent": "Test sent to {count} recipient(s) + persistent notification",
@@ -867,7 +867,7 @@
"panel.notif_section_recipients_desc": "Associez chaque enfant et parent à un service de notification Home Assistant. Laisser vide pour ignorer les notifications pour cette personne.",
"panel.notif_streak_cutoff_label": "Limite",
"panel.notif_nav_url_global_label": "Au toucher, ouvrir",
"panel.notif_nav_url_global_hint": "Où mène une notification touchée : un chemin comme /taskmate ou /lovelace/parents, une URL complète ou noAction. S'applique à toutes les notifications sauf remplacement par type ci-dessous.",
"panel.notif_nav_url_global_hint": "Où mène une notification touchée : un chemin comme /taskmate-admin ou /lovelace/parents, une URL complète ou noAction. S'applique à toutes les notifications sauf remplacement par type ci-dessous.",
"panel.notif_nav_url_row_placeholder": "Utilise la valeur globale",
"panel.notif_tab_title": "Notifications",
"panel.template_assignment_mode_label": "Mode d'assignation",
@@ -867,7 +867,7 @@
"panel.notif_section_recipients_desc": "Koble hvert barn og foresatt til en Home Assistant-varslingstjeneste. La stå tomt for å hoppe over varsler for den personen.",
"panel.notif_streak_cutoff_label": "Grense",
"panel.notif_nav_url_global_label": "Ved trykk, åpne",
"panel.notif_nav_url_global_hint": "Hvor et trykk på et varsel åpner: en sti som /taskmate eller /lovelace/parents, en full URL, eller noAction. Gjelder alle varsler med mindre det overstyres per type nedenfor.",
"panel.notif_nav_url_global_hint": "Hvor et trykk på et varsel åpner: en sti som /taskmate-admin eller /lovelace/parents, en full URL, eller noAction. Gjelder alle varsler med mindre det overstyres per type nedenfor.",
"panel.notif_nav_url_row_placeholder": "Bruker global standard",
"panel.notif_tab_title": "Varsler",
"panel.template_assignment_mode_label": "Tildelingsmodus",
@@ -867,7 +867,7 @@
"panel.notif_section_recipients_desc": "Kople kvart born og kvar føresett til ein Home Assistant-varslingsteneste. La stå tomt for å hoppa over varsel for den personen.",
"panel.notif_streak_cutoff_label": "Grense",
"panel.notif_nav_url_global_label": "Ved trykk, opne",
"panel.notif_nav_url_global_hint": "Kvar eit trykk på eit varsel opnar: ein sti som /taskmate eller /lovelace/parents, ein full URL, eller noAction. Gjeld alle varsel med mindre det vert overstyrt per type nedanfor.",
"panel.notif_nav_url_global_hint": "Kvar eit trykk på eit varsel opnar: ein sti som /taskmate-admin eller /lovelace/parents, ein full URL, eller noAction. Gjeld alle varsel med mindre det vert overstyrt per type nedanfor.",
"panel.notif_nav_url_row_placeholder": "Brukar global standard",
"panel.notif_tab_title": "Varsel",
"panel.template_assignment_mode_label": "Tildelingsmodus",
@@ -867,7 +867,7 @@
"panel.notif_section_recipients_desc": "Associe cada criança e responsável a um serviço de notificação do Home Assistant. Deixe em branco para pular notificações para essa pessoa.",
"panel.notif_streak_cutoff_label": "Limite",
"panel.notif_nav_url_global_label": "Ao tocar, abrir",
"panel.notif_nav_url_global_hint": "Para onde uma notificação tocada abre: um caminho como /taskmate ou /lovelace/parents, uma URL completa ou noAction. Aplica-se a todas as notificações, exceto se substituída por tipo abaixo.",
"panel.notif_nav_url_global_hint": "Para onde uma notificação tocada abre: um caminho como /taskmate-admin ou /lovelace/parents, uma URL completa ou noAction. Aplica-se a todas as notificações, exceto se substituída por tipo abaixo.",
"panel.notif_nav_url_row_placeholder": "Usa o padrão global",
"panel.notif_tab_title": "Notificações",
"panel.template_assignment_mode_label": "Modo de atribuição",
@@ -872,7 +872,7 @@
"panel.notif_section_recipients_desc": "Associe cada criança e encarregado a um serviço de notificação do Home Assistant. Deixe em branco para ignorar notificações para essa pessoa.",
"panel.notif_streak_cutoff_label": "Limite",
"panel.notif_nav_url_global_label": "Ao tocar, abrir",
"panel.notif_nav_url_global_hint": "Para onde uma notificação tocada abre: um caminho como /taskmate ou /lovelace/parents, um URL completo ou noAction. Aplica-se a todas as notificações, salvo substituição por tipo abaixo.",
"panel.notif_nav_url_global_hint": "Para onde uma notificação tocada abre: um caminho como /taskmate-admin ou /lovelace/parents, um URL completo ou noAction. Aplica-se a todas as notificações, salvo substituição por tipo abaixo.",
"panel.notif_nav_url_row_placeholder": "Usa o padrão global",
"panel.notif_tab_title": "Notificações",
"panel.template_assignment_mode_label": "Modo de atribuição",
@@ -4599,7 +4599,7 @@ class TaskMatePanel extends HTMLElement {
<label style="font-weight:500">${this._t("panel.notif_nav_url_global_label")}</label>
<input type="text" class="tm-input" style="flex:1;min-width:180px"
value="${this._esc((ns.settings && ns.settings.notification_nav_url) || "")}"
data-act="notif-set-nav-url-global" placeholder="/taskmate">
data-act="notif-set-nav-url-global" placeholder="/taskmate-admin">
<div class="tm-meta" style="flex-basis:100%">${this._t("panel.notif_nav_url_global_hint")}</div>
</div>
<div class="tm-table-wrap">