From 6826b5570c2de5f0383f64c176493e71dd6eb326 Mon Sep 17 00:00:00 2001 From: dayanFreitas Date: Fri, 23 May 2025 16:00:12 -0300 Subject: [PATCH] Adds integration with Google Calendar: includes API configuration, event management nodes, and project structure in the README --- .../pocketflow-google-calendar/.env.exemplo | 6 + .../pocketflow-google-calendar/.gitignore | 4 + cookbook/pocketflow-google-calendar/Pipfile | 16 +++ cookbook/pocketflow-google-calendar/README.md | 118 ++++++++++++++++++ cookbook/pocketflow-google-calendar/main.py | 53 ++++++++ cookbook/pocketflow-google-calendar/nodes.py | 81 ++++++++++++ .../utils/__init__.py | 0 .../utils/google_calendar.py | 94 ++++++++++++++ 8 files changed, 372 insertions(+) create mode 100644 cookbook/pocketflow-google-calendar/.env.exemplo create mode 100644 cookbook/pocketflow-google-calendar/.gitignore create mode 100644 cookbook/pocketflow-google-calendar/Pipfile create mode 100644 cookbook/pocketflow-google-calendar/README.md create mode 100644 cookbook/pocketflow-google-calendar/main.py create mode 100644 cookbook/pocketflow-google-calendar/nodes.py create mode 100644 cookbook/pocketflow-google-calendar/utils/__init__.py create mode 100644 cookbook/pocketflow-google-calendar/utils/google_calendar.py diff --git a/cookbook/pocketflow-google-calendar/.env.exemplo b/cookbook/pocketflow-google-calendar/.env.exemplo new file mode 100644 index 0000000..7a1a12d --- /dev/null +++ b/cookbook/pocketflow-google-calendar/.env.exemplo @@ -0,0 +1,6 @@ +# Google Calendar API Configuration +GOOGLE_CALENDAR_ID=your_calendar_id@group.calendar.google.com +GOOGLE_APPLICATION_CREDENTIALS=credentials.json + +# Application Configuration +TIMEZONE=America/Sao_Paulo # or your preferred timezone diff --git a/cookbook/pocketflow-google-calendar/.gitignore b/cookbook/pocketflow-google-calendar/.gitignore new file mode 100644 index 0000000..92c2e15 --- /dev/null +++ b/cookbook/pocketflow-google-calendar/.gitignore @@ -0,0 +1,4 @@ +.env +Pipfile.lock +credentials.json +token.pickle \ No newline at end of file diff --git a/cookbook/pocketflow-google-calendar/Pipfile b/cookbook/pocketflow-google-calendar/Pipfile new file mode 100644 index 0000000..b5a5a7b --- /dev/null +++ b/cookbook/pocketflow-google-calendar/Pipfile @@ -0,0 +1,16 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +python-dotenv = ">=0.19.0" +pocketflow = ">=0.0.2" +google-auth-oauthlib = ">=1.0.0" +google-auth-httplib2 = ">=0.1.0" +google-api-python-client = ">=2.0.0" + +[dev-packages] + +[requires] +python_version = "3.13" diff --git a/cookbook/pocketflow-google-calendar/README.md b/cookbook/pocketflow-google-calendar/README.md new file mode 100644 index 0000000..e2238da --- /dev/null +++ b/cookbook/pocketflow-google-calendar/README.md @@ -0,0 +1,118 @@ +# Pocket Google Calendar + +An application based on the Pocket Flow framework for Google Calendar integration. + +## 📋 Description + +This project implements a Google Calendar integration using the Pocket Flow framework, allowing efficient management of events and appointments through a simple and intuitive interface. + +## 🚀 Features + +- Google Calendar API Integration +- Event Management +- Appointment Viewing +- Flow-based Interface using Pocket Flow + +## 🛠️ Technologies Used + +- Python +- Pocket Flow Framework +- Google Calendar API +- Pipenv for dependency management + +## 📦 Installation + +1. Clone the repository: +```bash +git clone [REPOSITORY_URL] +cd pocket-google-calendar +``` + +2. Install dependencies using Pipenv: +```bash +pipenv install +``` + +## 🔑 Credentials Setup + +1. Go to the [Google Cloud Console](https://console.cloud.google.com/) +2. Create a new project or select an existing one +3. Enable the Google Calendar API for your project +4. Create credentials: + - Go to "APIs & Services" > "Credentials" + - Click "Create Credentials" > "OAuth client ID" + - Choose "Desktop application" as the application type + - Download the credentials file + - Rename it to `credentials.json` + - Place it in the root directory of the project + +## 🌍 Environment Variables + +Create a `.env` file in the root directory with the following variables: + +```env +# Google Calendar API Configuration +GOOGLE_CALENDAR_ID=your_calendar_id@group.calendar.google.com +GOOGLE_APPLICATION_CREDENTIALS=credentials.json + +# Application Configuration +TIMEZONE=America/Sao_Paulo # or your preferred timezone +``` + +## 🔧 Configuration + +1. Activate the virtual environment: +```bash +pipenv shell +``` + +2. Run the application: +```bash +python main.py +``` + +## Expected Output + +When running the example, you'll see an output similar to this: + +``` +=== Listing your calendars === +- Primary Calendar +- Work +- Personal + +=== Creating an example event === +Event created successfully! +Event ID: abc123xyz +``` + + +## 📁 Project Structure + +``` +pocket-google-calendar/ +├── main.py # Application entry point +├── nodes.py # Pocket Flow node definitions +├── utils/ # Utilities and helper functions +├── Pipfile # Pipenv configuration +├── credentials.json # Google Calendar API credentials +├── .env # Environment variables +└── token.pickle # Google Calendar authentication token +``` + +## 🤝 Contributing + +1. Fork the project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +## 📝 License + +This project is under the MIT License. See the [LICENSE](LICENSE) file for more details. + +## ✨ Acknowledgments + +- [Pocket Flow](https://github.com/the-pocket/PocketFlow) - Framework used +- [Google Calendar API](https://developers.google.com/calendar) - Integration API \ No newline at end of file diff --git a/cookbook/pocketflow-google-calendar/main.py b/cookbook/pocketflow-google-calendar/main.py new file mode 100644 index 0000000..ac8cb31 --- /dev/null +++ b/cookbook/pocketflow-google-calendar/main.py @@ -0,0 +1,53 @@ +from pocketflow import Flow +from nodes import CreateCalendarEventNode, ListCalendarEventsNode, ListCalendarsNode +from datetime import datetime, timedelta + +def create_calendar_flow(): + """Creates a flow to manage calendar events.""" + # Create nodes + create_event_node = CreateCalendarEventNode() + list_events_node = ListCalendarEventsNode() + + # Connect nodes + create_event_node - "success" >> list_events_node + create_event_node - "error" >> None + + # Create flow + return Flow(start=create_event_node) + +def list_calendars_flow(): + """Creates a flow to list all user calendars.""" + list_calendars_node = ListCalendarsNode() + return Flow(start=list_calendars_node) + +def main(): + # Example: List all calendars + print("=== Listing your calendars ===") + flow = list_calendars_flow() + shared = {} + flow.run(shared) + + if 'available_calendars' in shared: + for cal in shared['available_calendars']: + print(f"- {cal.get('summary')}") + + # Example: Create a simple event + print("\n=== Creating an example event ===") + flow = create_calendar_flow() + + shared = { + 'event_summary': 'Example Meeting', + 'event_description': 'An example meeting created by PocketFlow', + 'event_start_time': datetime.now() + timedelta(days=1), + 'event_end_time': datetime.now() + timedelta(days=1, hours=1), + 'days_to_list': 7 + } + + flow.run(shared) + + if 'last_created_event' in shared: + print("Event created successfully!") + print(f"Event ID: {shared['last_created_event']['id']}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/cookbook/pocketflow-google-calendar/nodes.py b/cookbook/pocketflow-google-calendar/nodes.py new file mode 100644 index 0000000..5ec6c8d --- /dev/null +++ b/cookbook/pocketflow-google-calendar/nodes.py @@ -0,0 +1,81 @@ +from pocketflow import Node +from utils.google_calendar import create_event, list_events, list_calendar_lists +from datetime import datetime, timedelta + +class CreateCalendarEventNode(Node): + def prep(self, shared): + """Prepares the necessary data to create an event.""" + return { + 'summary': shared.get('event_summary'), + 'description': shared.get('event_description'), + 'start_time': shared.get('event_start_time'), + 'end_time': shared.get('event_end_time') + } + + def exec(self, event_data): + """Creates a new calendar event.""" + try: + event = create_event( + summary=event_data['summary'], + description=event_data['description'], + start_time=event_data['start_time'], + end_time=event_data['end_time'] + ) + return {'success': True, 'event': event} + except Exception as e: + return {'success': False, 'error': str(e)} + + def post(self, shared, prep_res, exec_res): + """Stores the event creation result.""" + if exec_res['success']: + shared['last_created_event'] = exec_res['event'] + return 'success' + else: + shared['error'] = exec_res['error'] + return 'error' + +class ListCalendarEventsNode(Node): + def prep(self, shared): + """Prepares parameters to list events.""" + return { + 'days': shared.get('days_to_list', 7) + } + + def exec(self, params): + """Lists calendar events.""" + try: + events = list_events(days=params['days']) + return {'success': True, 'events': events} + except Exception as e: + return {'success': False, 'error': str(e)} + + def post(self, shared, prep_res, exec_res): + """Stores the list of events.""" + if exec_res['success']: + shared['calendar_events'] = exec_res['events'] + return 'success' + else: + shared['error'] = exec_res['error'] + return 'error' + +class ListCalendarsNode(Node): + def prep(self, shared): + """No special preparation needed to list calendars.""" + return {} + + def exec(self, params): + """Lists all available calendars for the user.""" + try: + calendars = list_calendar_lists() + return {'success': True, 'calendars': calendars} + except Exception as e: + return {'success': False, 'error': str(e)} + + def post(self, shared, prep_res, exec_res): + """Stores the list of calendars in the shared store.""" + if exec_res['success']: + shared['available_calendars'] = exec_res['calendars'] + return 'success' + else: + shared['error'] = exec_res['error'] + return 'error' \ No newline at end of file diff --git a/cookbook/pocketflow-google-calendar/utils/__init__.py b/cookbook/pocketflow-google-calendar/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cookbook/pocketflow-google-calendar/utils/google_calendar.py b/cookbook/pocketflow-google-calendar/utils/google_calendar.py new file mode 100644 index 0000000..8627410 --- /dev/null +++ b/cookbook/pocketflow-google-calendar/utils/google_calendar.py @@ -0,0 +1,94 @@ +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 +import os.path +import os +import pickle +from datetime import datetime, timedelta +from dotenv import load_dotenv + +load_dotenv() + +CALENDAR_ID = os.getenv('GOOGLE_CALENDAR_ID') +GOOGLE_APPLICATION_CREDENTIALS = os.getenv('GOOGLE_APPLICATION_CREDENTIALS') +TIMEZONE = os.getenv('TIMEZONE') + +SCOPES = ['https://www.googleapis.com/auth/calendar'] + +def get_calendar_service(): + """Gets the authenticated Google Calendar service.""" + creds = None + if os.path.exists('token.pickle'): + with open('token.pickle', 'rb') as token: + creds = pickle.load(token) + + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + GOOGLE_APPLICATION_CREDENTIALS, SCOPES) + creds = flow.run_local_server(port=0) + with open('token.pickle', 'wb') as token: + pickle.dump(creds, token) + + return build('calendar', 'v3', credentials=creds) + +def create_event(summary, description, start_time, end_time, timezone=TIMEZONE): + """Creates a new event in Google Calendar.""" + service = get_calendar_service() + + event = { + 'summary': summary, + 'description': description, + 'start': { + 'dateTime': start_time.isoformat(), + 'timeZone': timezone, + }, + 'end': { + 'dateTime': end_time.isoformat(), + 'timeZone': timezone, + }, + } + + event = service.events().insert(calendarId=CALENDAR_ID, body=event).execute() + return event + +def list_events(days=7): + """Lists events for the next X days.""" + service = get_calendar_service() + + now = datetime.utcnow() + time_min = now.isoformat() + 'Z' + time_max = (now + timedelta(days=days)).isoformat() + 'Z' + + events_result = service.events().list( + calendarId=CALENDAR_ID, + timeMin=time_min, + timeMax=time_max, + singleEvents=True, + orderBy='startTime' + ).execute() + + return events_result.get('items', []) + +def create_custom_calendar(calendar_name, description=""): + """Creates a new custom calendar in Google Calendar.""" + service = get_calendar_service() + + calendar = { + 'summary': calendar_name, + 'description': description, + 'timeZone': TIMEZONE + } + + created_calendar = service.calendars().insert(body=calendar).execute() + return created_calendar + +def list_calendar_lists(): + """Lists all available calendars for the user.""" + service = get_calendar_service() + + calendar_list = service.calendarList().list().execute() + return calendar_list.get('items', []) \ No newline at end of file