Skip to content

Instantly share code, notes, and snippets.

@lonniev
Created January 6, 2025 21:23
Show Gist options
  • Save lonniev/bc56e1158223ad612b693da189b95b9a to your computer and use it in GitHub Desktop.
Save lonniev/bc56e1158223ad612b693da189b95b9a to your computer and use it in GitHub Desktop.
Use python with Google Calendar APIs to find out when you met or are meeting with particular people over time.
import argparse
import os
import sys
import re
import json
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from dateutil import parser
from datetime import datetime
import pytz
from tzlocal import get_localzone
import itertools
from typing import Any, Union
def load_credentials():
creds = {}
with open(os.path.expanduser('~/.gcalclirc'), 'r') as file:
for line in file:
key, value = line.strip().split('=')
creds[key.lstrip('--')] = value
return creds
def oAuthenticate( client_id, client_secret ):
SCOPES = [ 'https://www.googleapis.com/auth/calendar.readonly' ]
credentials = None
savedCredsFile = os.path.expanduser("~/.gcal_token.json")
if savedCredsFile:
credentials = Credentials.from_authorized_user_file( savedCredsFile, SCOPES )
if not credentials or not credentials.valid:
if credentials and credentials.expired and credentials.refresh_token:
credentials.refresh( Request() )
else:
original_stdout = sys.stdout
sys.stdout = open( os.devnull, 'w' )
try:
flow = InstalledAppFlow.from_client_config(
{
"installed": {
"client_id": client_id,
"client_secret": client_secret,
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"]
}
},
SCOPES
)
credentials = flow.run_local_server(port=0)
finally:
sys.stdout = original_stdout
with open( savedCredsFile, "w" ) as tokenfile:
tokenfile.write( credentials.to_json() )
refresh_token = credentials.refresh_token
token_uri = credentials.token_uri
return {
'client_id': client_id,
'client_secret': client_secret,
'refresh_token': refresh_token,
'token_uri': token_uri
}
def get_calendar_events(start_date, end_date, pageSize, service, pageToken ):
return service.events().list(
calendarId='primary', timeMin=start_date, timeMax=end_date, singleEvents=True,
orderBy='startTime', maxResults= pageSize, pageToken= pageToken ).execute()
def findEvents( start_date, end_date, email_pattern ):
creds_data = load_credentials()
oauth_creds = oAuthenticate( creds_data['client-id'], creds_data['client-secret'] )
creds = Credentials.from_authorized_user_info(oauth_creds)
service = build('calendar', 'v3', credentials=creds)
def fetch_pages():
pageToken = None
while True:
response = get_calendar_events( start_date, end_date, 100, service, pageToken )
yield response['items']
pageToken = response.get('nextPageToken')
if not pageToken:
break
events = itertools.chain.from_iterable( fetch_pages() )
matched_events = [
event for event in events
if ( any(re.search(email_pattern, attendee.get('email', '')) for attendee in event.get('attendees', [])) or re.search(email_pattern, event.get('organizer', {}).get('email', 'UNORGANIZED')) )
]
return matched_events
def parseToOffsetIso8601( date_str ):
local_tz = get_localzone()
local_dt = parser.parse( date_str ).astimezone( local_tz )
return local_dt.strftime('%Y-%m-%dT%H:%M:%S%z')
def toZuluIso8601(date_str):
dt = datetime.fromisoformat(date_str)
dt_utc = dt.astimezone( pytz.utc )
return dt_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
def mapTimestampsToUtc(d):
return {
**d,
'start': {**d['start'], 'dateTime': toZuluIso8601(d['start']['dateTime'])},
'end': {**d['end'], 'dateTime': toZuluIso8601(d['end']['dateTime'])}
}
def main():
arg_parser = argparse.ArgumentParser(description='Fetch Google Calendar events by date range and email pattern.')
arg_parser.add_argument('--start-date', required=True, help='Start date in YYYY-MM-DD format')
arg_parser.add_argument('--end-date', required=True, help='End date in YYYY-MM-DD format')
arg_parser.add_argument('--email-pattern', required=True, help='Email regex pattern to match participants')
args = arg_parser.parse_args()
args.start_date = parseToOffsetIso8601( args.start_date )
args.end_date = parseToOffsetIso8601( args.end_date )
events = findEvents(args.start_date, args.end_date, args.email_pattern)
print(json.dumps( [ mapTimestampsToUtc( event ) for event in events ], indent=2))
if __name__ == '__main__':
main()
@lonniev
Copy link
Author

lonniev commented Jan 6, 2025

This uses a credentials file that the CLI gcalcli utility creates. Once it has borrowed those Oauth Application Credentials, it then uses its own saved OAuth credentials file.

@lonniev
Copy link
Author

lonniev commented Jan 6, 2025

Use this like:

printf "%.2f hours in meetings with Kenneth in 2024" $(python ~/meetingsWith.py --start-date 2024-01-01 --end-date 2024-12-31 --email-pattern naylor | jq '.[] | {summary, start: .start.dateTime, end: .end.dateTime} | (.end | fromdateiso8601) - (.start | fromdateis o8601)' | jq -s 'add / 3600' )

288.58 hours in meetings with Kenneth in 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment