From 0b260eec5e5026bc5c5cbcd16fa8825b2802ecd6 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Mon, 6 Apr 2026 13:50:44 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20vobject=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20+=20calendar=20=ED=8C=8C=EC=8B=B1=20icalen?= =?UTF-8?q?dar=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CalDAV 3.x에서 vobject 분리됨 → 이벤트 파싱 실패 원인. vobject 설치 + icalendar fallback 파싱 추가. Co-Authored-By: Claude Opus 4.6 (1M context) --- nanoclaude/requirements.txt | 1 + nanoclaude/tools/calendar_tool.py | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/nanoclaude/requirements.txt b/nanoclaude/requirements.txt index 57d1f0c..7fae30d 100644 --- a/nanoclaude/requirements.txt +++ b/nanoclaude/requirements.txt @@ -5,3 +5,4 @@ pydantic-settings==2.5.0 aiosqlite==0.20.0 python-multipart==0.0.9 caldav>=1.3.0 +vobject>=0.9.8 diff --git a/nanoclaude/tools/calendar_tool.py b/nanoclaude/tools/calendar_tool.py index 33f51dc..8ae9fbb 100644 --- a/nanoclaude/tools/calendar_tool.py +++ b/nanoclaude/tools/calendar_tool.py @@ -31,8 +31,9 @@ def _get_client(): def _parse_event(event) -> dict: - """VEVENT → dict.""" + """VEVENT → dict. vobject 있으면 사용, 없으면 icalendar fallback.""" try: + # vobject 방식 vevent = event.vobject_instance.vevent summary = str(vevent.summary.value) if hasattr(vevent, "summary") else "(제목 없음)" dtstart = vevent.dtstart.value @@ -43,7 +44,24 @@ def _parse_event(event) -> dict: return {"summary": summary, "start": start_str, "end": end_str} except Exception: - return {"summary": "(파싱 실패)", "start": "", "end": ""} + pass + + # fallback: icalendar 라이브러리로 raw data 파싱 + try: + from icalendar import Calendar + cal = Calendar.from_ical(event.data) + for comp in cal.walk(): + if comp.name == "VEVENT": + summary = str(comp.get("summary", "(제목 없음)")) + dtstart = comp.get("dtstart") + dtend = comp.get("dtend") + start_str = dtstart.dt.strftime("%Y-%m-%d %H:%M") if dtstart else "" + end_str = dtend.dt.strftime("%Y-%m-%d %H:%M") if dtend else "" + return {"summary": summary, "start": start_str, "end": end_str} + except Exception: + pass + + return {"summary": "(파싱 실패)", "start": "", "end": ""} async def today() -> dict: