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>
150 lines
6.1 KiB
Python
150 lines
6.1 KiB
Python
"""Promemoria di macOS: elenca, aggiunge e completa promemoria (EventKit, sincronizzati con iPhone)."""
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
from datetime import datetime, timedelta
|
|
|
|
_store = None
|
|
_lock = threading.Lock()
|
|
|
|
|
|
def _get_store():
|
|
global _store
|
|
with _lock:
|
|
if _store is not None:
|
|
return _store
|
|
import EventKit
|
|
store = EventKit.EKEventStore.alloc().init()
|
|
done, res = threading.Event(), {}
|
|
|
|
def cb(granted, err):
|
|
res["granted"] = bool(granted)
|
|
done.set()
|
|
|
|
if hasattr(store, "requestFullAccessToRemindersWithCompletion_"):
|
|
store.requestFullAccessToRemindersWithCompletion_(cb)
|
|
else:
|
|
store.requestAccessToEntityType_completion_(EventKit.EKEntityTypeReminder, cb)
|
|
done.wait(60)
|
|
if not res.get("granted"):
|
|
raise RuntimeError("Accesso ai Promemoria negato: concedilo in Impostazioni di Sistema > Privacy e sicurezza > Promemoria.")
|
|
_store = store
|
|
return store
|
|
|
|
|
|
def _parse_dt(s: str) -> datetime | None:
|
|
s = (s or "").strip().replace("T", " ")
|
|
if not s:
|
|
return None
|
|
for fmt in ("%Y-%m-%d %H:%M", "%Y-%m-%d"):
|
|
try:
|
|
return datetime.strptime(s, fmt)
|
|
except ValueError:
|
|
continue
|
|
raise ValueError(f"Data non valida: '{s}'. Usa AAAA-MM-GG HH:MM.")
|
|
|
|
|
|
def _due(r) -> datetime | None:
|
|
comps = r.dueDateComponents()
|
|
if comps is None:
|
|
return None
|
|
try:
|
|
return datetime(comps.year(), comps.month(), comps.day(), comps.hour() if comps.hour() != 9223372036854775807 else 0,
|
|
comps.minute() if comps.minute() != 9223372036854775807 else 0)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def _fetch_incomplete():
|
|
store = _get_store()
|
|
pred = store.predicateForIncompleteRemindersWithDueDateStarting_ending_calendars_(None, None, None)
|
|
done, out = threading.Event(), []
|
|
|
|
def cb(items):
|
|
out.extend(list(items or []))
|
|
done.set()
|
|
|
|
store.fetchRemindersMatchingPredicate_completion_(pred, cb)
|
|
done.wait(30)
|
|
return out
|
|
|
|
|
|
def _fmt(r) -> str:
|
|
d = _due(r)
|
|
when = f" (entro {d:%d/%m %H:%M})" if d and (d.hour or d.minute) else (f" (entro {d:%d/%m})" if d else "")
|
|
return f"- {r.title()}{when} [{r.calendar().title()}]"
|
|
|
|
|
|
def elenca(params: dict, ctx: dict) -> str:
|
|
items = _fetch_incomplete()
|
|
quando = str(params.get("quando", "tutti")).lower()
|
|
today = datetime.now().date()
|
|
if quando == "oggi":
|
|
items = [r for r in items if (d := _due(r)) and d.date() <= today]
|
|
elif quando == "settimana":
|
|
items = [r for r in items if (d := _due(r)) and d.date() <= today + timedelta(days=7)]
|
|
items.sort(key=lambda r: (_due(r) or datetime.max))
|
|
if not items:
|
|
return "Nessun promemoria da fare" + (" per oggi." if quando == "oggi" else ".")
|
|
return f"Promemoria da fare ({len(items)}):\n" + "\n".join(_fmt(r) for r in items[:30])
|
|
|
|
|
|
def aggiungi(params: dict, ctx: dict) -> str:
|
|
import EventKit
|
|
from Foundation import NSDateComponents
|
|
store = _get_store()
|
|
titolo = str(params.get("titolo", "")).strip()
|
|
if not titolo:
|
|
return "Errore: serve il titolo."
|
|
r = EventKit.EKReminder.reminderWithEventStore_(store)
|
|
r.setTitle_(titolo)
|
|
lista = str(params.get("lista", "")).strip()
|
|
cal = None
|
|
if lista:
|
|
for c in store.calendarsForEntityType_(EventKit.EKEntityTypeReminder):
|
|
if c.title().lower() == lista.lower():
|
|
cal = c
|
|
r.setCalendar_(cal or store.defaultCalendarForNewReminders())
|
|
scadenza = _parse_dt(str(params.get("scadenza", "")))
|
|
if scadenza:
|
|
comps = NSDateComponents.alloc().init()
|
|
comps.setYear_(scadenza.year); comps.setMonth_(scadenza.month); comps.setDay_(scadenza.day)
|
|
comps.setHour_(scadenza.hour); comps.setMinute_(scadenza.minute)
|
|
r.setDueDateComponents_(comps)
|
|
from Foundation import NSDate
|
|
alarm = EventKit.EKAlarm.alarmWithAbsoluteDate_(NSDate.dateWithTimeIntervalSince1970_(scadenza.timestamp()))
|
|
r.addAlarm_(alarm)
|
|
if params.get("note"):
|
|
r.setNotes_(str(params["note"]))
|
|
ok, err = store.saveReminder_commit_error_(r, True, None)
|
|
return f"Promemoria aggiunto: {_fmt(r)[2:]}" if ok else f"Errore: {err}"
|
|
|
|
|
|
def completa(params: dict, ctx: dict) -> str:
|
|
titolo = str(params.get("titolo", "")).strip().lower()
|
|
if not titolo:
|
|
return "Errore: serve il titolo (anche parziale)."
|
|
found = [r for r in _fetch_incomplete() if titolo in (r.title() or "").lower()]
|
|
if not found:
|
|
return "Nessun promemoria con quel titolo."
|
|
if len(found) > 1 and not params.get("tutti"):
|
|
return "Ho trovato più promemoria, dimmi quale:\n" + "\n".join(_fmt(r) for r in found)
|
|
store = _get_store()
|
|
for r in found:
|
|
r.setCompleted_(True)
|
|
store.saveReminder_commit_error_(r, True, None)
|
|
return "Completato: " + ", ".join(r.title() for r in found)
|
|
|
|
|
|
TOOLS = [
|
|
{"name": "promemoria_elenca", "description": "Elenca i promemoria da fare dell'app Promemoria (tutti, di oggi o della settimana).",
|
|
"parameters": {"type": "object", "properties": {"quando": {"type": "string", "enum": ["tutti", "oggi", "settimana"]}}}, "run": elenca},
|
|
{"name": "promemoria_aggiungi", "description": "Aggiunge un promemoria (o una voce a una lista, per esempio 'Spesa'). Converti 'domani alle 17' in AAAA-MM-GG HH:MM usando la data del prompt.",
|
|
"parameters": {"type": "object", "properties": {"titolo": {"type": "string"}, "scadenza": {"type": "string", "description": "AAAA-MM-GG HH:MM, facoltativa."},
|
|
"lista": {"type": "string", "description": "Nome della lista (facoltativo)."}, "note": {"type": "string"}}, "required": ["titolo"]},
|
|
"run": aggiungi},
|
|
{"name": "promemoria_completa", "description": "Segna come fatto un promemoria cercandolo per titolo.",
|
|
"parameters": {"type": "object", "properties": {"titolo": {"type": "string"}, "tutti": {"type": "boolean", "description": "Completa tutti quelli che corrispondono."}}, "required": ["titolo"]},
|
|
"run": completa},
|
|
]
|