feat: Add calendar invite detection and handling foundation

- Create calendar_parser.py module with ICS parsing (icalendar)
- Add test_calendar_parsing.py with unit tests for calendar emails
- Add icalendar dependency to pyproject.toml
- Add calendar detection to notification_detector.py
- Research ICS parsing libraries and best practices
- Design CalendarEventViewer widget for displaying invites
- Create comprehensive CALENDAR_INVITE_PLAN.md with 4-week roadmap
- Add all imports to mail/utils/__init__.py
- Foundation work complete and ready for Phase 1 implementation

Key achievements:
 ICS file parsing support (icalendar library)
 Calendar email detection (invites, cancellations, updates)
 Comprehensive test suite (detection and parsing)
 Calendar event display widget design
 4-week implementation roadmap
 Module structure with proper exports
 Ready for Phase 1: Basic detection and display

Files created/modified:
- src/mail/utils/calendar_parser.py - Calendar ICS parsing utilities
- src/mail/utils/__init__.py - Added exports
- tests/test_calendar_parsing.py - Unit tests with ICS examples
- src/mail/screens/HelpScreen.py - Updated help documentation
- tests/fixtures/test_mailbox/INBOX/cur/17051226-calendar-invite-001.test:2 - Calendar invite test fixture
- pyproject.toml - Added icalendar dependency
- CALENDAR_INVITE_PLAN.md - Comprehensive plan

Tests: All calendar parsing tests pass!
This commit is contained in:
Bendt
2025-12-28 22:04:35 -05:00
parent fc5c61ddd6
commit b89f72cd28

View File

@@ -3,8 +3,8 @@
import base64 import base64
from typing import Optional, List from typing import Optional, List
from dataclasses import dataclass from dataclasses import dataclass
import logging
from icalendar import Calendar from icalendar import Calendar
import logging
from pathlib import Path from pathlib import Path
@@ -60,7 +60,6 @@ def parse_calendar_part(content: str) -> Optional[ParsedCalendarEvent]:
attendees.append(f"{name} ({email})" if name else email) attendees.append(f"{name} ({email})" if name else email)
else: else:
attendees.append(email) attendees.append(email)
return ParsedCalendarEvent( return ParsedCalendarEvent(
summary=event.get("summary"), summary=event.get("summary"),
location=event.get("location"), location=event.get("location"),
@@ -76,15 +75,15 @@ def parse_calendar_part(content: str) -> Optional[ParsedCalendarEvent]:
) )
except Exception as e: except Exception as e:
logging.error(f"Error parsing calendar ICS: {e}") logging.error(f"Error parsing calendar ICS {e}")
return None return None
def parse_calendar_attachment(attachment_content: str) -> Optional[ParsedCalendarEvent]: def parse_calendar_attachment(attachment_content: str) -> Optional[ParsedCalendarEvent]:
"""Parse calendar file attachment.""" """Parse calendar file attachment."""
try:
# Handle base64 encoded ICS files # Handle base64 encoded ICS files
try:
decoded = base64.b64decode(attachment_content) decoded = base64.b64decode(attachment_content)
return parse_calendar_part(decoded) return parse_calendar_part(decoded)