-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathreminders.py
More file actions
173 lines (153 loc) · 6.3 KB
/
Copy pathreminders.py
File metadata and controls
173 lines (153 loc) · 6.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# ai_services/reminders.py
"""
Reminders System — Local task/reminder storage with optional Google Calendar sync.
Supports creating, listing, updating, and deleting reminders.
"""
import os
import json
import time
import uuid
from typing import List, Dict, Any, Optional
from .logger import log
from .settings import SettingsManager
REMINDERS_FILE = os.path.join(os.path.dirname(__file__), "reminders.json")
class ReminderManager:
"""Manages local reminders with optional calendar sync."""
@staticmethod
def _load_all() -> List[Dict[str, Any]]:
if not os.path.exists(REMINDERS_FILE):
return []
try:
with open(REMINDERS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return []
@staticmethod
def _save_all(reminders: List[Dict]):
with open(REMINDERS_FILE, "w", encoding="utf-8") as f:
json.dump(reminders, f, ensure_ascii=False, indent=2)
@staticmethod
def list_reminders(include_completed: bool = False) -> List[Dict[str, Any]]:
"""List all reminders, optionally including completed ones."""
reminders = ReminderManager._load_all()
if not include_completed:
reminders = [r for r in reminders if not r.get("completed", False)]
return sorted(reminders, key=lambda r: r.get("due_date", "9999"))
@staticmethod
def create_reminder(
title: str,
description: str = "",
due_date: str = "",
priority: str = "normal",
category: str = "general",
sync_calendar: bool = False,
) -> Dict[str, Any]:
"""
Create a new reminder.
due_date: ISO format string, e.g. '2026-04-15T10:00:00-05:00'
priority: 'low', 'normal', 'high', 'urgent'
"""
reminders = ReminderManager._load_all()
reminder = {
"id": f"rem_{uuid.uuid4().hex[:12]}",
"title": title,
"description": description,
"due_date": due_date,
"priority": priority,
"category": category,
"completed": False,
"notified": False,
"calendar_event_id": None,
"created_at": time.time(),
"updated_at": time.time(),
}
# Optional Google Calendar sync
if sync_calendar and due_date:
try:
from .google_service import GoogleManager
gm = GoogleManager()
if gm.is_authenticated:
import datetime as dt
# Create a 30-min event from the due date
start = due_date
# Parse and add 30 min for end time
try:
from datetime import datetime, timedelta
dt_start = datetime.fromisoformat(start)
dt_end = dt_start + timedelta(minutes=30)
end = dt_end.isoformat()
except Exception:
end = start # Fallback
event = gm.calendar_create_event(
summary=f"🔔 {title}",
start=start,
end=end,
description=description,
)
if event:
reminder["calendar_event_id"] = event.get("id")
log.info(f"[REMINDERS] Synced to Calendar: {event.get('link')}")
except Exception as e:
log.warning(f"[REMINDERS] Calendar sync failed: {e}")
reminders.append(reminder)
ReminderManager._save_all(reminders)
log.info(f"[REMINDERS] Created: {title}")
return reminder
@staticmethod
def update_reminder(reminder_id: str, updates: Dict[str, Any]) -> Optional[Dict]:
"""Update a reminder's fields."""
reminders = ReminderManager._load_all()
for i, r in enumerate(reminders):
if r["id"] == reminder_id:
for key in ("title", "description", "due_date", "priority", "category", "completed", "notified"):
if key in updates:
reminders[i][key] = updates[key]
reminders[i]["updated_at"] = time.time()
ReminderManager._save_all(reminders)
return reminders[i]
return None
@staticmethod
def complete_reminder(reminder_id: str) -> Optional[Dict]:
"""Mark a reminder as completed."""
return ReminderManager.update_reminder(reminder_id, {"completed": True})
@staticmethod
def delete_reminder(reminder_id: str) -> bool:
"""Delete a reminder by ID."""
reminders = ReminderManager._load_all()
original_len = len(reminders)
reminders = [r for r in reminders if r["id"] != reminder_id]
if len(reminders) < original_len:
ReminderManager._save_all(reminders)
log.info(f"[REMINDERS] Deleted: {reminder_id}")
return True
return False
def get_due_reminders() -> List[Dict]:
"""Get reminders that are due (past due_date, not completed, and not yet notified)."""
import datetime
import pytz
tz_name = SettingsManager.get_timezone()
try:
tz = pytz.timezone(tz_name)
except Exception:
tz = pytz.UTC
now = datetime.datetime.now(tz)
reminders = ReminderManager._load_all()
due = []
updated = False
for r in reminders:
if not r.get("completed") and not r.get("notified") and r.get("due_date"):
try:
# Parse due_date (ISO)
r_due = datetime.datetime.fromisoformat(r["due_date"])
# Ensure it has timezone info for comparison
if r_due.tzinfo is None:
r_due = tz.localize(r_due)
if r_due <= now:
due.append(r)
r["notified"] = True
updated = True
except Exception as e:
log.error(f"[REMINDERS] Error comparing time: {e}")
if updated:
ReminderManager._save_all(reminders)
return due