#!/usr/bin/env python3 """ Daily Reminder Checker Reads reminders from markdown table, filters due items, sends notification """ import re import os from datetime import datetime, timedelta from pathlib import Path # Paths BASE_DIR = Path.home() / ".openclaw/workspace/reminders" ACTIVE_FILE = BASE_DIR / "active.md" ARCHIVE_DIR = BASE_DIR / "archive" # Priority mapping (lower number = higher priority) PRIORITY_MAP = { '高': 0, 'urgent': 0, 'high': 0, '中': 1, 'normal': 1, 'medium': 1, '低': 2, 'low': 2 } def parse_table(content): """Parse markdown table into list of dicts""" lines = content.strip().split('\n') reminders = [] for line in lines: # Skip header lines and separators if line.startswith('|') and '---' not in line and '事项' not in line: cells = [cell.strip() for cell in line.split('|')[1:-1]] if len(cells) >= 4 and cells[0] and cells[0] != '事项': reminder = { '事项': cells[0], '截止日期': cells[1] if len(cells) > 1 else '', '优先级': cells[2] if len(cells) > 2 else '', '状态': cells[3] if len(cells) > 3 else 'pending', '备注': cells[4] if len(cells) > 4 else '' } reminders.append(reminder) return reminders def get_default_date(): """Return tomorrow's date as string""" tomorrow = datetime.now() + timedelta(days=1) return tomorrow.strftime('%Y-%m-%d') def normalize_reminder(reminder): """Apply defaults and normalize""" # Default priority if not reminder['优先级']: reminder['优先级'] = '中' # Default date if not reminder['截止日期']: reminder['截止日期'] = get_default_date() # Normalize status reminder['状态'] = reminder['状态'].lower() if reminder['状态'] else 'pending' return reminder def get_days_until(due_date_str): """Calculate days until due date""" try: due_date = datetime.strptime(due_date_str, '%Y-%m-%d') today = datetime.now() delta = (due_date.date() - today.date()).days return delta except ValueError: return None def get_urgency_label(days): """Get urgency label based on days until due""" if days is None: return "❓ 日期未知" elif days < 0: return f"🔴 逾期 {-days} 天" elif days == 0: return "🔴 今天" elif days == 1: return "🟡 明天" elif days <= 3: return f"🟡 {days} 天后" else: return f"🟢 {days} 天后" def sort_reminders(reminders): """Sort by priority (high first), then by date (earlier first)""" def sort_key(r): priority = PRIORITY_MAP.get(r['优先级'].lower(), 1) try: date = datetime.strptime(r['截止日期'], '%Y-%m-%d') except ValueError: date = datetime.max return (priority, date) return sorted(reminders, key=sort_key) def format_notification(pending_reminders): """Format all pending reminders for notification""" if not pending_reminders: return None today_str = datetime.now().strftime('%Y-%m-%d') lines = [f"📋 今日待办清单 ({today_str})", "=" * 50] # Group by priority groups = {'高': [], '中': [], '低': []} for r in pending_reminders: prio = r['优先级'] if prio in groups: groups[prio].append(r) # Output high priority if groups['高']: lines.append("\n🔴 高优先级:") for r in groups['高']: days = get_days_until(r['截止日期']) urgency = get_urgency_label(days) note = f" | {r['备注']}" if r['备注'] else "" lines.append(f" • {r['事项']} ({urgency}){note}") # Output medium priority if groups['中']: lines.append("\n🟡 中优先级:") for r in groups['中']: days = get_days_until(r['截止日期']) urgency = get_urgency_label(days) note = f" | {r['备注']}" if r['备注'] else "" lines.append(f" • {r['事项']} ({urgency}){note}") # Output low priority if groups['低']: lines.append("\n🟢 低优先级:") for r in groups['低']: days = get_days_until(r['截止日期']) urgency = get_urgency_label(days) note = f" | {r['备注']}" if r['备注'] else "" lines.append(f" • {r['事项']} ({urgency}){note}") lines.append("\n" + "=" * 50) lines.append("📝 完成事项后请修改状态为 done") lines.append("📁 管理文件: ~/.openclaw/workspace/reminders/active.md") return '\n'.join(lines) def archive_done_reminders(reminders): """Move done reminders to archive""" done = [r for r in reminders if r['状态'] == 'done'] if not done: return # Create archive filename with current quarter now = datetime.now() quarter = (now.month - 1) // 3 + 1 archive_file = ARCHIVE_DIR / f"{now.year}-Q{quarter}.md" # Append to archive with open(archive_file, 'a', encoding='utf-8') as f: for r in done: f.write(f"| {r['事项']} | {r['截止日期']} | {r['优先级']} | done | {r['备注']} |\n") def update_active_file(reminders): """Rewrite active file without done items""" pending = [r for r in reminders if r['状态'] != 'done'] with open(ACTIVE_FILE, 'w', encoding='utf-8') as f: f.write("# 提醒事项表\n\n") f.write("## 待办事项(Pending)\n\n") f.write("| 事项 | 截止日期 | 优先级 | 状态 | 备注 |\n") f.write("|------|----------|--------|------|------|\n") for r in pending: f.write(f"| {r['事项']} | {r['截止日期']} | {r['优先级']} | {r['状态']} | {r['备注']} |\n") f.write("\n## 使用说明\n\n") f.write("1. **添加事项**:在表格中新增一行\n") f.write("2. **截止日期**:格式 YYYY-MM-DD,空着默认为明天\n") f.write("3. **优先级**:高/中/低,空着默认为中\n") f.write("4. **状态**:pending(待办)/ done(已完成)\n") f.write("5. **每天早上8:00自动检查**,到期事项会通知你\n\n") f.write("## 已完成归档\n\n") f.write("已完成的事项会自动移动到 archive/ 目录\n") def main(): """Main function - show all pending reminders as todo list""" # Check if file exists if not ACTIVE_FILE.exists(): print("No reminders file found") return # Read and parse with open(ACTIVE_FILE, 'r', encoding='utf-8') as f: content = f.read() reminders = parse_table(content) # Normalize and filter for pending only reminders = [normalize_reminder(r) for r in reminders] pending_reminders = [r for r in reminders if r['状态'] == 'pending'] if not pending_reminders: # No pending reminders - silent return # Sort and format pending_reminders = sort_reminders(pending_reminders) notification = format_notification(pending_reminders) if notification: print(notification) # Archive done items (optional - uncomment if you want auto-archive) # archive_done_reminders(reminders) # update_active_file(reminders) if __name__ == "__main__": main()