#!/usr/bin/env python3 """ Google Calendar Skill for OpenClaw 提供日历读取和写入功能 """ import json import os from datetime import datetime, timedelta from typing import Optional, Dict, List, Any try: from google.oauth2 import service_account from googleapiclient.discovery import build from googleapiclient.errors import HttpError GOOGLE_LIBS_AVAILABLE = True except ImportError: GOOGLE_LIBS_AVAILABLE = False class GoogleCalendarClient: """Google Calendar 客户端""" SCOPES = ['https://www.googleapis.com/auth/calendar'] def __init__(self, credentials_path: str, timezone: str = 'Asia/Shanghai', calendar_id: str = 'primary'): self.credentials_path = credentials_path self.timezone = timezone self.calendar_id = calendar_id self.service = None self._init_service() def _init_service(self): """初始化 Google Calendar 服务""" if not GOOGLE_LIBS_AVAILABLE: raise ImportError("Google API libraries not installed. Run: pip install google-auth google-api-python-client") if not os.path.exists(self.credentials_path): raise FileNotFoundError(f"Credentials file not found: {self.credentials_path}") try: credentials = service_account.Credentials.from_service_account_file( self.credentials_path, scopes=self.SCOPES ) self.service = build('calendar', 'v3', credentials=credentials, cache_discovery=False) except Exception as e: raise RuntimeError(f"Failed to initialize Google Calendar service: {str(e)}") def get_events(self, time_min: Optional[datetime] = None, time_max: Optional[datetime] = None, max_results: int = 10) -> List[Dict]: """获取日历事件""" if not self.service: return [] try: now = datetime.utcnow().isoformat() + 'Z' events_result = self.service.events().list( calendarId=self.calendar_id, timeMin=time_min.isoformat() + 'Z' if time_min else now, timeMax=time_max.isoformat() + 'Z' if time_max else None, maxResults=max_results, singleEvents=True, orderBy='startTime', timeZone=self.timezone ).execute() events = events_result.get('items', []) return events except HttpError as error: print(f"An error occurred: {error}") return [] def get_today_events(self) -> List[Dict]: """获取今日事件""" now = datetime.now() start_of_day = datetime(now.year, now.month, now.day) end_of_day = start_of_day + timedelta(days=1) return self.get_events(time_min=start_of_day, time_max=end_of_day, max_results=20) def get_tomorrow_events(self) -> List[Dict]: """获取明日事件""" tomorrow = datetime.now() + timedelta(days=1) start_of_day = datetime(tomorrow.year, tomorrow.month, tomorrow.day) end_of_day = start_of_day + timedelta(days=1) return self.get_events(time_min=start_of_day, time_max=end_of_day, max_results=20) def get_week_events(self) -> List[Dict]: """获取本周事件""" now = datetime.now() start_of_week = now - timedelta(days=now.weekday()) start_of_week = datetime(start_of_week.year, start_of_week.month, start_of_week.day) end_of_week = start_of_week + timedelta(days=7) return self.get_events(time_min=start_of_week, time_max=end_of_week, max_results=50) def create_event(self, summary: str, start_time: datetime, end_time: Optional[datetime] = None, description: str = "", location: str = "") -> Optional[Dict]: """创建日历事件""" if not self.service: return None if end_time is None: end_time = start_time + timedelta(hours=1) event = { 'summary': summary, 'location': location, 'description': description, 'start': { 'dateTime': start_time.isoformat(), 'timeZone': self.timezone, }, 'end': { 'dateTime': end_time.isoformat(), 'timeZone': self.timezone, }, } try: event = self.service.events().insert( calendarId=self.calendar_id, body=event ).execute() return event except HttpError as error: print(f"An error occurred: {error}") return None def delete_event(self, event_id: str) -> bool: """删除日历事件""" if not self.service: return False try: self.service.events().delete( calendarId=self.calendar_id, eventId=event_id ).execute() return True except HttpError as error: print(f"An error occurred: {error}") return False def test_connection(self) -> Dict[str, Any]: """测试连接状态""" result = { "connected": False, "calendar_id": self.calendar_id, "timezone": self.timezone, "error": None } try: calendar = self.service.calendars().get(calendarId=self.calendar_id).execute() result["connected"] = True result["calendar_name"] = calendar.get('summary', 'Unknown') result["calendar_description"] = calendar.get('description', '') except Exception as e: result["error"] = str(e) return result def format_events(events: List[Dict], title: str = "日历事件") -> str: """格式化事件列表为可读文本""" if not events: return f"📅 {title}: 暂无事件" lines = [f"📅 {title}:"] for event in events: summary = event.get('summary', '无标题') start = event.get('start', {}) start_time = start.get('dateTime', start.get('date', '未知时间')) # 格式化时间 try: dt = datetime.fromisoformat(start_time.replace('Z', '+00:00')) time_str = dt.strftime('%m/%d %H:%M') except: time_str = start_time location = event.get('location', '') location_str = f" @ {location}" if location else "" lines.append(f" • {time_str} {summary}{location_str}") return '\n'.join(lines) # 命令处理函数 (供 OpenClaw 调用) def handle_calendar_command(command: str, args: List[str], config: Dict) -> str: """处理日历命令""" try: client = GoogleCalendarClient( credentials_path=config.get('credentials_path', '/root/.openclaw/credentials/google-calendar.json'), timezone=config.get('timezone', 'Asia/Shanghai'), calendar_id=config.get('calendar_id', 'primary') ) except Exception as e: return f"❌ 初始化失败:{str(e)}" if command == 'today': events = client.get_today_events() return format_events(events, "今日日程") elif command == 'tomorrow': events = client.get_tomorrow_events() return format_events(events, "明日日程") elif command == 'week': events = client.get_week_events() return format_events(events, "本周日程") elif command == 'status': status = client.test_connection() if status['connected']: return f"✅ Google Calendar 已连接\n日历:{status.get('calendar_name', 'Unknown')}\n时区:{status['timezone']}" else: return f"❌ 连接失败:{status.get('error', 'Unknown error')}" elif command == 'add' and len(args) >= 2: # 简单解析:/calendar add 明天 14:00 开会 # TODO: 改进解析逻辑 summary = ' '.join(args[2:]) if len(args) > 2 else '新事件' start_time = datetime.now() + timedelta(hours=1) event = client.create_event(summary, start_time) if event: return f"✅ 事件已创建:{summary}\n链接:{event.get('htmlLink', '')}" else: return "❌ 创建事件失败" elif command == 'help': return """📅 Google Calendar 命令帮助: /calendar today - 查看今日日程 /calendar tomorrow - 查看明日日程 /calendar week - 查看本周日程 /calendar status - 检查连接状态 /calendar add <时间> <事件> - 添加新事件 /calendar help - 显示帮助""" else: return f"❌ 未知命令:{command}\n使用 /calendar help 查看帮助" if __name__ == '__main__': # 测试 import sys if len(sys.argv) > 1: cmd = sys.argv[1] args = sys.argv[2:] config = { 'credentials_path': '/root/.openclaw/credentials/google-calendar.json', 'timezone': 'Asia/Shanghai' } result = handle_calendar_command(cmd, args, config) print(result) else: print("Usage: python google_calendar.py [args]") print("Commands: today, tomorrow, week, status, add, help")