Files
avatar/plugins/timer.py
T
lucianoandClaude Fable 5.1 ff79832c30 AvatarPy: assistente personale con volto 3D, voce, memoria e plugin
Riscrittura in Python dell'assistente Avatar con interfaccia HUD (derivata da Mark LIV, CC BY-NC 4.0, vedi NOTICE.md).
Tre motori (Claude API, server locale OpenAI-compatibile, Claude Code), voce Kokoro/macOS, Whisper MLX,
avatar 3D con sincronizzazione labiale, memoria per categorie, allegati con OCR, monitor con avvisi,
plugin per Calendario, Mail, Promemoria, Note, Musica, app, Mac, timer, meteo, contatti, Messaggi,
file, Comandi Rapidi, browser, Telegram, WhatsApp (archivio e tempo reale).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 16:21:39 +02:00

110 lines
4.8 KiB
Python

"""Timer, sveglie e attività programmate: l'app avvisa a voce o esegue un comando all'ora stabilita, anche ogni giorno."""
from __future__ import annotations
import json
import sys
import uuid
from datetime import datetime, timedelta
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from avatar.settings import DATA_DIR # noqa: E402
FILE = DATA_DIR / "timers.json"
def load() -> list[dict]:
try:
return json.loads(FILE.read_text(encoding="utf-8"))
except Exception:
return []
def save(items: list[dict]) -> None:
FILE.parent.mkdir(parents=True, exist_ok=True)
FILE.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")
def _when(params: dict) -> datetime:
now = datetime.now()
if params.get("minuti"):
return now + timedelta(minutes=float(params["minuti"]))
s = str(params.get("ora", "")).strip().replace("T", " ")
if not s:
raise ValueError("Serve 'minuti' oppure 'ora'.")
if len(s) <= 5: # HH:MM oggi (o domani se già passata)
t = datetime.strptime(s, "%H:%M").time()
dt = datetime.combine(now.date(), t)
return dt if dt > now else dt + timedelta(days=1)
return datetime.strptime(s, "%Y-%m-%d %H:%M")
def imposta(params: dict, ctx: dict) -> str:
testo = str(params.get("testo", "")).strip() or "Timer scaduto"
try:
at = _when(params)
except ValueError as err:
return f"Errore: {err}"
item = {"id": uuid.uuid4().hex[:6], "at": at.strftime("%Y-%m-%d %H:%M"), "testo": testo,
"ripeti": "giorno" if params.get("ogni_giorno") else "no", "comando": bool(params.get("esegui_come_comando"))}
items = load(); items.append(item); save(items)
quando = at.strftime("%H:%M") if at.date() == datetime.now().date() else at.strftime("%d/%m alle %H:%M")
tipo = "comando" if item["comando"] else "avviso"
return f"{tipo.capitalize()} programmato per le {quando}{' ogni giorno' if item['ripeti'] == 'giorno' else ''}: «{testo}» [id {item['id']}]."
def elenca(params: dict, ctx: dict) -> str:
items = sorted(load(), key=lambda x: x["at"])
if not items:
return "Nessun timer o attività programmata."
return "Programmati:\n" + "\n".join(f"- [{i['id']}] {i['at']}{' ogni giorno' if i.get('ripeti') == 'giorno' else ''}: {i['testo']}{' (comando)' if i.get('comando') else ''}" for i in items)
def annulla(params: dict, ctx: dict) -> str:
key = str(params.get("id", "")).strip().lower()
items = load()
keep = [i for i in items if i["id"] != key and key not in i["testo"].lower()] if key else []
if key and len(keep) == len(items):
return "Nessun timer corrispondente."
if not key:
save([]); return "Tutti i timer annullati."
save(keep)
return f"Annullati {len(items) - len(keep)} timer."
def due(now: datetime | None = None) -> list[dict]:
"""Restituisce gli elementi scaduti e aggiorna il file (usato dallo scheduler dell'app)."""
now = now or datetime.now()
items, fired, keep = load(), [], []
for i in items:
try:
at = datetime.strptime(i["at"], "%Y-%m-%d %H:%M")
except ValueError:
continue
if at <= now:
fired.append(i)
if i.get("ripeti") == "giorno":
nxt = at + timedelta(days=1)
while nxt <= now:
nxt += timedelta(days=1)
keep.append({**i, "at": nxt.strftime("%Y-%m-%d %H:%M")})
else:
keep.append(i)
if fired:
save(keep)
return fired
TOOLS = [
{"name": "timer_imposta",
"description": "Imposta un timer ('tra 10 minuti'), una sveglia/avviso a un'ora, o un'attività programmata anche giornaliera. Con esegui_come_comando=true, all'ora stabilita il testo viene eseguito come richiesta all'assistente (es. 'leggimi la posta'), altrimenti viene annunciato a voce.",
"parameters": {"type": "object", "properties": {"testo": {"type": "string", "description": "Cosa annunciare, o il comando da eseguire."},
"minuti": {"type": "number", "description": "Fra quanti minuti."},
"ora": {"type": "string", "description": "HH:MM oppure AAAA-MM-GG HH:MM."},
"ogni_giorno": {"type": "boolean"}, "esegui_come_comando": {"type": "boolean"}}, "required": ["testo"]},
"run": imposta},
{"name": "timer_elenca", "description": "Elenca timer e attività programmate.", "parameters": {"type": "object", "properties": {}}, "run": elenca},
{"name": "timer_annulla", "description": "Annulla un timer per id o parola del testo; senza id annulla tutti.",
"parameters": {"type": "object", "properties": {"id": {"type": "string"}}}, "run": annulla},
]