From 1c7afafce78f7cb00f3dbf3e9fa7efa29be532ba Mon Sep 17 00:00:00 2001 From: Tim Bendt Date: Tue, 22 Apr 2025 11:36:29 -0600 Subject: [PATCH] add cred caching and change output dir --- .gitignore | 2 ++ fetch_outlook_events.py | 52 ++++++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 5d85715..1880e23 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ output_markdown_files/output_3.md output_markdown_files/output_4.md output_markdown_files/output_5.md output_markdown_files/output_6.md +token_cache.bin +output_ics/outlook_events_latest.ics diff --git a/fetch_outlook_events.py b/fetch_outlook_events.py index e4902ef..420df1c 100644 --- a/fetch_outlook_events.py +++ b/fetch_outlook_events.py @@ -4,6 +4,7 @@ import requests import json from datetime import datetime from dateutil import parser +from dateutil.tz import UTC # Read Azure app credentials from environment variables client_id = os.getenv('AZURE_CLIENT_ID') @@ -12,35 +13,54 @@ tenant_id = os.getenv('AZURE_TENANT_ID') if not client_id or not tenant_id: raise ValueError("Please set the AZURE_CLIENT_ID and AZURE_TENANT_ID environment variables.") +# Token cache +cache = msal.SerializableTokenCache() +cache_file = 'token_cache.bin' + +if os.path.exists(cache_file): + cache.deserialize(open(cache_file, 'r').read()) + # Authentication authority = f'https://login.microsoftonline.com/{tenant_id}' scopes = ['https://graph.microsoft.com/Calendars.Read'] -app = msal.PublicClientApplication(client_id, authority=authority) -flow = app.initiate_device_flow(scopes=scopes) -if 'user_code' not in flow: - raise Exception("Failed to create device flow") -print(flow['message']) -token_response = app.acquire_token_by_device_flow(flow) +app = msal.PublicClientApplication(client_id, authority=authority, token_cache=cache) +accounts = app.get_accounts() + +if accounts: + token_response = app.acquire_token_silent(scopes, account=accounts[0]) +else: + flow = app.initiate_device_flow(scopes=scopes) + if 'user_code' not in flow: + raise Exception("Failed to create device flow") + print(flow['message']) + token_response = app.acquire_token_by_device_flow(flow) if 'access_token' not in token_response: raise Exception("Failed to acquire token") +# Save token cache +with open(cache_file, 'w') as f: + f.write(cache.serialize()) + access_token = token_response['access_token'] # Fetch events with pagination and expand recurring events headers = {'Authorization': f'Bearer {access_token}'} events_url = 'https://graph.microsoft.com/v1.0/me/events?$top=100&$expand=instances' events = [] - +print("Fetching events...") while events_url: response = requests.get(events_url, headers=headers) response_data = response.json() events.extend(response_data.get('value', [])) + print(f"Fetched {len(events)} events so far...", end='\r') events_url = response_data.get('@odata.nextLink') # Save events to a file in iCalendar format -with open('outlook_events.ics', 'w') as f: +output_file = f'output_ics/outlook_events_latest.ics' +print(f"Saving events to {output_file}...") +with open(output_file, 'w') as f: f.write("BEGIN:VCALENDAR\nVERSION:2.0\n") for event in events: if 'start' in event and 'end' in event: @@ -49,5 +69,21 @@ with open('outlook_events.ics', 'w') as f: f.write(f"BEGIN:VEVENT\nSUMMARY:{event['subject']}\n") f.write(f"DTSTART:{start.strftime('%Y%m%dT%H%M%S')}\n") f.write(f"DTEND:{end.strftime('%Y%m%dT%H%M%S')}\n") + if 'recurrence' in event and event['recurrence']: # Check if 'recurrence' exists and is not None + for rule in event['recurrence']: + if rule.startswith('RRULE'): + rule_parts = rule.split(';') + new_rule_parts = [] + for part in rule_parts: + if part.startswith('UNTIL='): + until_value = part.split('=')[1] + until_date = parser.isoparse(until_value) + if start.tzinfo is not None and until_date.tzinfo is None: + until_date = until_date.replace(tzinfo=UTC) + new_rule_parts.append(f"UNTIL={until_date.strftime('%Y%m%dT%H%M%SZ')}") + else: + new_rule_parts.append(part) + rule = ';'.join(new_rule_parts) + f.write(f"{rule}\n") f.write("END:VEVENT\n") f.write("END:VCALENDAR\n")