Files
claude-code/skills/morning-report/scripts/collectors/gtasks.py
OpenCode Test 45b7e4bcf7 Improve morning report collectors and add section toggling
- Add is_section_enabled() to support per-section enable/disable in config
- Update Python path from 3.13 to 3.14 for gmail venv
- Disable tasks section by default (enabled: false in config)
- Apply code formatting improvements (black/ruff style)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 23:44:24 -08:00

182 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Google Tasks collector."""
import json
import os
import sys
from datetime import datetime
from pathlib import Path
# Add gmail venv to path for Google API libraries
venv_site = Path.home() / ".claude/mcp/gmail/venv/lib/python3.14/site-packages"
if str(venv_site) not in sys.path:
sys.path.insert(0, str(venv_site))
# Google Tasks API
try:
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
GOOGLE_API_AVAILABLE = True
except ImportError:
GOOGLE_API_AVAILABLE = False
SCOPES = ["https://www.googleapis.com/auth/tasks.readonly"]
TOKEN_PATH = Path.home() / ".gmail-mcp/tasks_token.json"
CREDS_PATH = Path.home() / ".gmail-mcp/credentials.json"
def get_credentials():
"""Get or refresh Google credentials for Tasks API."""
creds = None
if TOKEN_PATH.exists():
creds = Credentials.from_authorized_user_file(str(TOKEN_PATH), SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
if not CREDS_PATH.exists():
return None
flow = InstalledAppFlow.from_client_secrets_file(str(CREDS_PATH), SCOPES)
creds = flow.run_local_server(port=0)
TOKEN_PATH.write_text(creds.to_json())
return creds
def fetch_tasks(max_results: int = 10) -> list:
"""Fetch tasks from Google Tasks API."""
if not GOOGLE_API_AVAILABLE:
return [{"error": "Google API libraries not installed"}]
try:
creds = get_credentials()
if not creds:
return [
{
"error": "Tasks API not authenticated - run: ~/.claude/mcp/gmail/venv/bin/python ~/.claude/skills/morning-report/scripts/collectors/gtasks.py --auth"
}
]
service = build("tasks", "v1", credentials=creds)
# Get default task list
tasklists = service.tasklists().list(maxResults=1).execute()
if not tasklists.get("items"):
return []
tasklist_id = tasklists["items"][0]["id"]
# Get tasks
results = (
service.tasks()
.list(
tasklist=tasklist_id,
maxResults=max_results,
showCompleted=False,
showHidden=False,
)
.execute()
)
tasks = results.get("items", [])
return tasks
except Exception as e:
return [{"error": str(e)}]
def format_tasks(tasks: list, max_display: int = 5) -> str:
"""Format tasks - no LLM needed, structured data."""
if not tasks:
return "No pending tasks"
if len(tasks) == 1 and "error" in tasks[0]:
return f"⚠️ Could not fetch tasks: {tasks[0]['error']}"
lines = []
# Count and header
total = len(tasks)
due_today = 0
today_str = datetime.now().strftime("%Y-%m-%d")
for task in tasks:
due = task.get("due", "")
if due and due.startswith(today_str):
due_today += 1
header = f"{total} pending"
if due_today > 0:
header += f", {due_today} due today"
lines.append(header)
# List tasks
for task in tasks[:max_display]:
title = task.get("title", "(No title)")
due = task.get("due", "")
due_str = ""
if due:
try:
due_date = datetime.fromisoformat(due.replace("Z", "+00:00"))
if due_date.date() == datetime.now().date():
due_str = " (due today)"
elif due_date.date() < datetime.now().date():
due_str = " (overdue!)"
else:
due_str = f" (due {due_date.strftime('%b %d')})"
except ValueError:
pass
lines.append(f"{title}{due_str}")
if total > max_display:
lines.append(f" ... and {total - max_display} more")
return "\n".join(lines)
def collect(config: dict) -> dict:
"""Main collector entry point."""
tasks_config = config.get("tasks", {})
max_display = tasks_config.get("max_display", 5)
tasks = fetch_tasks(max_display + 5)
formatted = format_tasks(tasks, max_display)
has_error = tasks and len(tasks) == 1 and "error" in tasks[0]
return {
"section": "Tasks",
"icon": "",
"content": formatted,
"raw": tasks if not has_error else None,
"count": len(tasks) if not has_error else 0,
"error": tasks[0].get("error") if has_error else None,
}
if __name__ == "__main__":
import sys
if "--auth" in sys.argv:
print("Starting Tasks API authentication...")
creds = get_credentials()
if creds:
print(f"✅ Authentication successful! Token saved to {TOKEN_PATH}")
else:
print("❌ Authentication failed")
sys.exit(0)
config = {"tasks": {"max_display": 5}}
result = collect(config)
print(f"## {result['icon']} {result['section']}")
print(result["content"])