Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 06:25:41 -06:00
commit 80bef6f524
63 changed files with 5674 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
"""Quiet hours — when the pet is allowed to make noise on its own.
Napping only ever suppresses *proactive* noise (heartbeat announcements,
forwarded notifications) and wandering. The wake word, click-to-talk and
push-to-talk still work: telling it to be quiet shouldn't mean it stops
answering when spoken to.
Pure logic (parsing + a time comparison) so it's testable without waiting
for 11pm.
"""
from __future__ import annotations
from datetime import time as dtime
from typing import Iterable, Optional
class QuietHoursError(ValueError):
pass
def _parse_clock(value: str) -> dtime:
parts = value.strip().split(":")
if len(parts) != 2:
raise QuietHoursError(f"expected HH:MM, got {value!r}")
try:
hour, minute = int(parts[0]), int(parts[1])
except ValueError as exc:
raise QuietHoursError(f"expected HH:MM, got {value!r}") from exc
if not (0 <= hour <= 23 and 0 <= minute <= 59):
raise QuietHoursError(f"{value!r} is not a real time of day")
return dtime(hour, minute)
def parse_ranges(spec: str) -> list[tuple[dtime, dtime]]:
""""23:00-08:00, 13:00-14:00" -> [(23:00, 08:00), (13:00, 14:00)].
Empty/blank spec means "no quiet hours"."""
ranges: list[tuple[dtime, dtime]] = []
for chunk in (spec or "").split(","):
chunk = chunk.strip()
if not chunk:
continue
start, sep, end = chunk.partition("-")
if not sep:
raise QuietHoursError(f"expected HH:MM-HH:MM, got {chunk!r}")
ranges.append((_parse_clock(start), _parse_clock(end)))
return ranges
def in_ranges(now: dtime, ranges: Iterable[tuple[dtime, dtime]]) -> bool:
for start, end in ranges:
if start == end:
continue # a zero-length range is a typo, not "all day"
if start < end:
if start <= now < end:
return True
elif now >= start or now < end: # wraps past midnight
return True
return False
def is_quiet(spec: str, now: Optional[dtime] = None, on_error=None) -> bool:
"""True if *now* (defaults to the local wall clock) falls inside *spec*.
A malformed spec is reported via *on_error* and treated as "not quiet"
a config typo shouldn't silently mute the pet forever."""
if not (spec or "").strip():
return False
try:
ranges = parse_ranges(spec)
except QuietHoursError as exc:
if on_error is not None:
on_error(exc)
return False
if now is None:
from datetime import datetime
now = datetime.now().time()
return in_ranges(now, ranges)