222 lines
7.4 KiB
Python
222 lines
7.4 KiB
Python
#!/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()
|