auto backup 2026-07-02 11:00
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
扫描已过截止时间但未完成的待办,输出逾期清单
|
||||
|
||||
用法:
|
||||
python todo_overdue_check.py
|
||||
python todo_overdue_check.py --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
PAGE_SIZE = 50
|
||||
MAX_PAGES = 10
|
||||
PRIORITY_MAP = {10: '低', 20: '普通', 30: '较高', 40: '紧急'}
|
||||
|
||||
|
||||
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, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def fetch_all_undone(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 or 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 find_overdue(todos: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
now_ms = int(datetime.now().timestamp() * 1000)
|
||||
overdue = []
|
||||
for t in todos:
|
||||
due = t.get('dueTime') or t.get('due')
|
||||
if not due:
|
||||
continue
|
||||
try:
|
||||
if int(due) < now_ms:
|
||||
overdue.append(t)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
return overdue
|
||||
|
||||
|
||||
def days_overdue(due_ms) -> int:
|
||||
now = datetime.now()
|
||||
try:
|
||||
due_dt = datetime.fromtimestamp(int(due_ms) / 1000)
|
||||
return max(0, (now - due_dt).days)
|
||||
except (ValueError, TypeError, OSError):
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
todos = fetch_all_undone(dry_run=dry_run)
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
overdue = find_overdue(todos)
|
||||
overdue.sort(
|
||||
key=lambda t: int(t.get('dueTime') or t.get('due', 0))
|
||||
)
|
||||
|
||||
print(f"\n⏰ 逾期待办检查 ({datetime.now().strftime('%Y-%m-%d %H:%M')})")
|
||||
print('=' * 50)
|
||||
|
||||
if not overdue:
|
||||
print(' ✅ 没有逾期待办,继续保持!')
|
||||
return
|
||||
|
||||
for t in overdue:
|
||||
title = t.get('subject') or t.get('title', '无标题')
|
||||
due = t.get('dueTime') or t.get('due')
|
||||
days = days_overdue(due)
|
||||
pri = PRIORITY_MAP.get(
|
||||
int(t.get('priority', 20)), '普通'
|
||||
)
|
||||
due_str = datetime.fromtimestamp(
|
||||
int(due) / 1000
|
||||
).strftime('%Y-%m-%d')
|
||||
print(f" 🔴 [{pri}] {title}")
|
||||
print(f" 截止: {due_str} 逾期: {days} 天")
|
||||
|
||||
print(f"\n合计: {len(overdue)} 条逾期待办")
|
||||
sys.exit(1 if overdue else 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user