Fresh start - excluded large ROM JSON files
This commit is contained in:
270
tools/reminder-manager.py
Normal file
270
tools/reminder-manager.py
Normal file
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Reminder Manager for OpenClaw Discord
|
||||
Handles one-shot and recurring reminders via OpenClaw cron + SQLite
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
DB_PATH = os.path.expanduser("~/.openclaw/workspace/data/reminders.db")
|
||||
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
||||
|
||||
|
||||
def init_db():
|
||||
"""Create the reminders database."""
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS reminders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
channel_id TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
remind_at TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
is_recurring INTEGER DEFAULT 0,
|
||||
recurrence_rule TEXT,
|
||||
cron_job_id TEXT,
|
||||
active INTEGER DEFAULT 1
|
||||
)''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def parse_time(time_str: str) -> datetime:
|
||||
"""Parse various time formats into datetime."""
|
||||
now = datetime.now()
|
||||
time_str = time_str.lower().strip()
|
||||
|
||||
# Relative times: 20m, 2h, 1h30m
|
||||
match = re.match(r'^(\d+)m$', time_str)
|
||||
if match:
|
||||
return now + timedelta(minutes=int(match.group(1)))
|
||||
|
||||
match = re.match(r'^(\d+)h$', time_str)
|
||||
if match:
|
||||
return now + timedelta(hours=int(match.group(1)))
|
||||
|
||||
match = re.match(r'^(\d+)h(\d+)m$', time_str)
|
||||
if match:
|
||||
return now + timedelta(hours=int(match.group(1)), minutes=int(match.group(2)))
|
||||
|
||||
# Tomorrow
|
||||
if time_str == 'tomorrow':
|
||||
return now + timedelta(days=1)
|
||||
|
||||
# Tomorrow at time: tomorrow 9am, tomorrow 14:00
|
||||
match = re.match(r'^tomorrow\s+([\d:]+)(am|pm)?$', time_str)
|
||||
if match:
|
||||
time_part = match.group(1)
|
||||
ampm = match.group(2)
|
||||
tomorrow = now + timedelta(days=1)
|
||||
|
||||
if ':' in time_part:
|
||||
hour, minute = map(int, time_part.split(':'))
|
||||
else:
|
||||
hour, minute = int(time_part), 0
|
||||
|
||||
if ampm == 'pm' and hour != 12:
|
||||
hour += 12
|
||||
if ampm == 'am' and hour == 12:
|
||||
hour = 0
|
||||
|
||||
return tomorrow.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||||
|
||||
# Today at time: 9am, 14:00, 2:30pm
|
||||
match = re.match(r'^(\d{1,2}):(\d{2})(am|pm)?$', time_str)
|
||||
if match:
|
||||
hour = int(match.group(1))
|
||||
minute = int(match.group(2))
|
||||
ampm = match.group(3)
|
||||
|
||||
if ampm == 'pm' and hour != 12:
|
||||
hour += 12
|
||||
if ampm == 'am' and hour == 12:
|
||||
hour = 0
|
||||
|
||||
result = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||||
if result < now:
|
||||
result += timedelta(days=1)
|
||||
return result
|
||||
|
||||
match = re.match(r'^(\d{1,2})(am|pm)$', time_str)
|
||||
if match:
|
||||
hour = int(match.group(1))
|
||||
ampm = match.group(2)
|
||||
|
||||
if ampm == 'pm' and hour != 12:
|
||||
hour += 12
|
||||
if ampm == 'am' and hour == 12:
|
||||
hour = 0
|
||||
|
||||
result = now.replace(hour=hour, minute=0, second=0, microsecond=0)
|
||||
if result < now:
|
||||
result += timedelta(days=1)
|
||||
return result
|
||||
|
||||
raise ValueError(f"Can't parse time: {time_str}")
|
||||
|
||||
|
||||
def add_reminder(user_id: str, channel_id: str, message: str, time_str: str) -> dict:
|
||||
"""Add a new reminder and schedule it via OpenClaw cron."""
|
||||
init_db()
|
||||
|
||||
remind_at = parse_time(time_str)
|
||||
|
||||
if remind_at < datetime.now():
|
||||
return {"error": "Reminder time is in the past"}
|
||||
|
||||
# Insert into DB
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('''INSERT INTO reminders (user_id, channel_id, message, remind_at, active)
|
||||
VALUES (?, ?, ?, ?, 1)''',
|
||||
(user_id, channel_id, message, remind_at.isoformat()))
|
||||
reminder_id = c.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Schedule via OpenClaw cron (will be handled by caller)
|
||||
return {
|
||||
"id": reminder_id,
|
||||
"message": message,
|
||||
"remind_at": remind_at.isoformat(),
|
||||
"user_id": user_id,
|
||||
"channel_id": channel_id
|
||||
}
|
||||
|
||||
|
||||
def list_reminders(user_id: str = None) -> list:
|
||||
"""List active reminders for a user or all users."""
|
||||
init_db()
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
|
||||
if user_id:
|
||||
c.execute('''SELECT id, message, remind_at, channel_id
|
||||
FROM reminders
|
||||
WHERE user_id = ? AND active = 1 AND remind_at > datetime('now')
|
||||
ORDER BY remind_at''', (user_id,))
|
||||
else:
|
||||
c.execute('''SELECT id, message, remind_at, channel_id, user_id
|
||||
FROM reminders
|
||||
WHERE active = 1 AND remind_at > datetime('now')
|
||||
ORDER BY remind_at''')
|
||||
|
||||
results = c.fetchall()
|
||||
conn.close()
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def delete_reminder(reminder_id: int, user_id: str = None) -> bool:
|
||||
"""Delete/cancel a reminder."""
|
||||
init_db()
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
|
||||
if user_id:
|
||||
c.execute('DELETE FROM reminders WHERE id = ? AND user_id = ?',
|
||||
(reminder_id, user_id))
|
||||
else:
|
||||
c.execute('DELETE FROM reminders WHERE id = ?', (reminder_id,))
|
||||
|
||||
deleted = c.rowcount > 0
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return deleted
|
||||
|
||||
|
||||
def delete_past_reminders():
|
||||
"""Clean up old reminder entries."""
|
||||
init_db()
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('DELETE FROM reminders WHERE remind_at < datetime("now", "-1 day")')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def cron_callback(reminder_id: int):
|
||||
"""Called when a cron job fires - returns the reminder details."""
|
||||
init_db()
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT message, user_id, channel_id FROM reminders WHERE id = ?',
|
||||
(reminder_id,))
|
||||
result = c.fetchone()
|
||||
|
||||
if result:
|
||||
# Mark as inactive after firing
|
||||
c.execute('UPDATE reminders SET active = 0 WHERE id = ?', (reminder_id,))
|
||||
conn.commit()
|
||||
|
||||
conn.close()
|
||||
|
||||
if result:
|
||||
return {
|
||||
"message": result[0],
|
||||
"user_id": result[1],
|
||||
"channel_id": result[2]
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
command = sys.argv[1] if len(sys.argv) > 1 else "help"
|
||||
|
||||
if command == "add":
|
||||
# Usage: reminder-manager.py add "user_id" "channel_id" "message" "time"
|
||||
user_id = sys.argv[2]
|
||||
channel_id = sys.argv[3]
|
||||
message = sys.argv[4]
|
||||
time_str = sys.argv[5]
|
||||
|
||||
result = add_reminder(user_id, channel_id, message, time_str)
|
||||
print(json.dumps(result))
|
||||
|
||||
elif command == "list":
|
||||
user_id = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
reminders = list_reminders(user_id)
|
||||
print(json.dumps([{
|
||||
"id": r[0],
|
||||
"message": r[1],
|
||||
"remind_at": r[2],
|
||||
"channel_id": r[3],
|
||||
"user_id": r[4] if len(r) > 4 else None
|
||||
} for r in reminders]))
|
||||
|
||||
elif command == "delete":
|
||||
reminder_id = int(sys.argv[2])
|
||||
user_id = sys.argv[3] if len(sys.argv) > 3 else None
|
||||
deleted = delete_reminder(reminder_id, user_id)
|
||||
print(json.dumps({"deleted": deleted}))
|
||||
|
||||
elif command == "callback":
|
||||
reminder_id = int(sys.argv[2])
|
||||
result = cron_callback(reminder_id)
|
||||
print(json.dumps(result) if result else "null")
|
||||
|
||||
elif command == "cleanup":
|
||||
delete_past_reminders()
|
||||
print("Cleanup complete")
|
||||
|
||||
else:
|
||||
print("""Usage:
|
||||
reminder-manager.py add "user_id" "channel_id" "message" "time"
|
||||
reminder-manager.py list [user_id]
|
||||
reminder-manager.py delete <reminder_id> [user_id]
|
||||
reminder-manager.py callback <reminder_id>
|
||||
reminder-manager.py cleanup
|
||||
""")
|
||||
Reference in New Issue
Block a user