170 lines
5.2 KiB
Python
170 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
查询今天/明天/本周未完成的待办并汇总输出
|
|
|
|
用法:
|
|
python todo_daily_summary.py # 默认查今天
|
|
python todo_daily_summary.py today # 今天的待办
|
|
python todo_daily_summary.py tomorrow # 明天的待办
|
|
python todo_daily_summary.py week # 本周的待办
|
|
python todo_daily_summary.py --dry-run # 仅显示将执行的命令
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import subprocess
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
PRIORITY_MAP = {10: '低', 20: '普通', 30: '较高', 40: '紧急'}
|
|
PAGE_SIZE = 50
|
|
MAX_PAGES = 10
|
|
|
|
|
|
def run_dws(args: List[str], dry_run: bool = False) -> Optional[Any]:
|
|
cmd = ['dws'] + args
|
|
if dry_run:
|
|
print(f"[dry-run] {' '.join(cmd)}")
|
|
return None
|
|
try:
|
|
result = subprocess.run(
|
|
cmd, capture_output=True, text=True, timeout=60
|
|
)
|
|
if result.returncode != 0:
|
|
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
|
return None
|
|
return json.loads(result.stdout)
|
|
except subprocess.TimeoutExpired:
|
|
print('错误:命令执行超时', file=sys.stderr)
|
|
return None
|
|
except (json.JSONDecodeError, FileNotFoundError) as e:
|
|
print(f"错误:{e}", file=sys.stderr)
|
|
return None
|
|
|
|
|
|
def get_date_range(scope: str):
|
|
now = datetime.now()
|
|
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
if scope == 'today':
|
|
return today_start, today_start + timedelta(days=1)
|
|
elif scope == 'tomorrow':
|
|
tmr = today_start + timedelta(days=1)
|
|
return tmr, tmr + timedelta(days=1)
|
|
elif scope == 'week':
|
|
week_start = today_start - timedelta(days=today_start.weekday())
|
|
return week_start, week_start + timedelta(days=7)
|
|
return today_start, today_start + timedelta(days=1)
|
|
|
|
|
|
def fetch_all_todos(
|
|
dry_run: bool = False,
|
|
) -> List[Dict[str, Any]]:
|
|
all_todos: List[Dict[str, Any]] = []
|
|
for page in range(1, MAX_PAGES + 1):
|
|
data = run_dws([
|
|
'todo', 'task', 'list',
|
|
'--page', str(page),
|
|
'--size', str(PAGE_SIZE),
|
|
'--status', 'false',
|
|
'--format', 'json',
|
|
], dry_run=dry_run)
|
|
if dry_run:
|
|
return []
|
|
if not data:
|
|
break
|
|
items = data if isinstance(data, list) else data.get(
|
|
'result', data.get('todoCards', [])
|
|
)
|
|
if not items or not isinstance(items, list):
|
|
break
|
|
all_todos.extend(items)
|
|
if len(items) < PAGE_SIZE:
|
|
break
|
|
return all_todos
|
|
|
|
|
|
def format_priority(p) -> str:
|
|
try:
|
|
return PRIORITY_MAP.get(int(p), str(p))
|
|
except (ValueError, TypeError):
|
|
return '普通'
|
|
|
|
|
|
def format_due(due_ms) -> str:
|
|
if not due_ms:
|
|
return '无截止时间'
|
|
try:
|
|
dt = datetime.fromtimestamp(int(due_ms) / 1000)
|
|
return dt.strftime('%Y-%m-%d %H:%M')
|
|
except (ValueError, TypeError, OSError):
|
|
return str(due_ms)
|
|
|
|
|
|
def filter_by_due(
|
|
todos: List[Dict[str, Any]], start: datetime, end: datetime,
|
|
) -> List[Dict[str, Any]]:
|
|
start_ms = int(start.timestamp() * 1000)
|
|
end_ms = int(end.timestamp() * 1000)
|
|
result = []
|
|
for t in todos:
|
|
due = t.get('dueTime') or t.get('due')
|
|
if not due:
|
|
result.append(t)
|
|
continue
|
|
try:
|
|
due_val = int(due)
|
|
if start_ms <= due_val < end_ms:
|
|
result.append(t)
|
|
except (ValueError, TypeError):
|
|
result.append(t)
|
|
return result
|
|
|
|
|
|
def print_summary(
|
|
todos: List[Dict[str, Any]], scope: str,
|
|
start: datetime, end: datetime,
|
|
):
|
|
scope_label = {
|
|
'today': '今天', 'tomorrow': '明天', 'week': '本周',
|
|
}.get(scope, scope)
|
|
print(f"\n📋 {scope_label}未完成待办 "
|
|
f"({start.strftime('%m-%d')} ~ {end.strftime('%m-%d')})")
|
|
print('=' * 50)
|
|
if not todos:
|
|
print(' ✅ 暂无待办,轻松一下!')
|
|
return
|
|
urgent = [t for t in todos if format_priority(
|
|
t.get('priority')) == '紧急']
|
|
if urgent:
|
|
print(f"\n🔴 紧急 ({len(urgent)} 条)")
|
|
for t in urgent:
|
|
title = t.get('subject') or t.get('title', '无标题')
|
|
print(f" • {title} ⏰ {format_due(t.get('dueTime'))}")
|
|
normal = [t for t in todos if t not in urgent]
|
|
if normal:
|
|
print(f"\n📌 其他 ({len(normal)} 条)")
|
|
for t in normal:
|
|
title = t.get('subject') or t.get('title', '无标题')
|
|
pri = format_priority(t.get('priority'))
|
|
print(f" • [{pri}] {title} ⏰ {format_due(t.get('dueTime'))}")
|
|
print(f"\n合计: {len(todos)} 条待办")
|
|
|
|
|
|
def main():
|
|
dry_run = '--dry-run' in sys.argv
|
|
args = [a for a in sys.argv[1:] if a != '--dry-run']
|
|
scope = args[0] if args else 'today'
|
|
if scope not in ('today', 'tomorrow', 'week'):
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
start, end = get_date_range(scope)
|
|
todos = fetch_all_todos(dry_run=dry_run)
|
|
if dry_run:
|
|
return
|
|
filtered = filter_by_due(todos, start, end)
|
|
print_summary(filtered, scope, start, end)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|