Open-sourced at:
Availabilities
13 Feb 2023
Goal:
- insert my availabilities as text in an email.
- ability to select timezone of the recipient (method TBC)
- combines availabilities across my 2 pro calendars (both using Google Calendar)
Output should be like:
Germany
- Wed 15 Feb 10:00
- Wed 15 Feb 11:00
- Fri 17 Feb 10:00
with date/time format adapted based on recipient's timezone:
PT
- Wed Feb 15 10am
- Wed Feb 15 11am
- Fri 17 Feb 3pm
so:
{recipient_timezone}
- {date} {time}
- {date} {time}
- {date} {time}
Timezone selection passed as a parameter to the script.
v2:
- "tomorrow" when applicable (instead of full date)
Process
Authorisation works by creating a token file using the Credentials JSON file downloaded from Google.
Steps:
- Access
https://console.cloud.google.com/apis
- Create a new project
- Enable the Google Calendar API
- Create a new OAuth client ID
- Download the credentials JSON file
- save it as
credentials.json
in the same folder asquickstart.py
(next step) -
Create a
quickstart.py
with below code, run, authenticate in browser, create the token file. -
Create a
service_key.json
athttps://console.cloud.google.com/iam-admin/serviceaccounts/create
In the menu, click on "APIs & Services" > "Credentials."
Click on "Create credentials" and choose "Service account."
Enter a name for the service account, and optionally, add a description. Click "Create."
Optionally, you can grant the service account a role with specific permissions. Click "Continue."
Click "Done" to create the service account.
In the "Service Accounts" section, find the service account you just created and click on its name.
In the "Keys" tab, click on "Add Key" and choose "JSON."
The JSON key file will be generated and downloaded to your computer. This is your service_key.json file.
# quickstart.py
from __future__ import print_function
import datetime
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
def main():
"""Shows basic usage of the Google Calendar API.
Prints the start and name of the next 10 events on the user's calendar.
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
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(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
try:
service = build('calendar', 'v3', credentials=creds)
# Call the Calendar API
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
print('Getting the upcoming 10 events')
events_result = service.events().list(calendarId='primary', timeMin=now,
maxResults=10, singleEvents=True,
orderBy='startTime').execute()
events = events_result.get('items', [])
if not events:
print('No upcoming events found.')
return
# Prints the start and name of the next 10 events
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
print(start, event['summary'])
except HttpError as error:
print('An error occurred: %s' % error)
if __name__ == '__main__':
main()
Initial basic code works as follows:
from datetime import datetime, time, timezone
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
def get_future_events(calendar_id, token):
# Set up Google API credentials
creds = Credentials.from_authorized_user_file(token, ['https://www.googleapis.com/auth/calendar'])
# Build the Google Calendar API client
service = build('calendar', 'v3', credentials=creds)
# Get the current time in UTC
now = datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
# Call the Google Calendar API to get events
events_result = service.events().list(
calendarId=calendar_id,
timeMin=now,
singleEvents=True,
orderBy='startTime'
).execute()
# Get the events from the response
events = events_result.get('items', [])
# Filter out events that have already ended
future_events = [event for event in events if datetime.fromisoformat(event['end'].get('dateTime', event['end'].get('date'))) > datetime.now(timezone.utc)]
# Return the list of future events
return future_events
Note: token
is the path to the token file created by the quickstart.py
script.
16 Feb 2023
Basic logic is working.
Some troubleshooting left re removing available time when events span more than 1 hour + formatting of output, currently coming out as:
- tomorrow 12:00
- tomorrow 17:00
- Mon 20th 12:00
- Tue 21st 17:00
- Wed 22nd 12:00
- Thu 23rd 12:00
working code
See:
variables
are available_days
, available_hours
, timezones
, slot
& weekdays_forward
:
slot = 30 # minutes
# How many weekdays forward to check for availability
weekdays_forward = 3
available_days = [ # comment lines below to make unavailable
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
]
available_hours = [ # comment lines below to make unavailable
# "08:00",
# "09:00",
# "10:00",
"11:00",
# "11:30",
"12:00",
# "13:00",
# "14:00",
# "15:00",
"16:00",
# "16:30",
"17:00",
# "18:00",
# "19:00",
]
timezones = { # This will define the return time & format
"CET": 0, # default
"UK": 1, # timezone offset in hours
"ET": 6,
"MT": 7,
"PT": 9,
}
outputs
Standard:
(CET / Germany time)
- Mon 6th 12:00
- Tue 7th 11:00, 12:00 or 17:00
- Wed 8th 11:00, 12:00, 16:00 or 17:00
or see all at https://cal.com/ndeville
UK:
(UK time)
- Mon 6th 11am
- Tue 7th 10am, 11am or 4pm
- Wed 8th 10am, 11am, 3pm or 4pm
or see all at https://cal.com/ndeville
PT:
(PT)
- Mon 6th 3am
- Tue 7th 2am, 3am or 8am
- Wed 8th 2am, 3am, 7am or 8am
or see all at https://cal.com/ndeville
Meetings
Generates list of past meetings objects when external attendees present, for ingestion anywhere (eg. CRM).
Each meeting returned as:
class Meeting:
def __init__(self):
self.id = '' # from Google
self.summary = ''
self._date = '' # from Google 'start', in YYYY-MM-DD format
self.htmlLink = ''
self.attendees = set() # unique emails from both 'attendees' and 'organiser'
self.description = ''
self.domain = '' # WARNING: this caters only for 1 domain per meeting
See:
Issues
Token (RESOLVED)
Token needs to be refreshed every day?
Need to find how to configure for perpetual use, or automate.
Seems like using a "Service Account" with "Domain Wide Delegation" is the way to go: https://medium.com/@ArchTaqi/google-calendar-api-in-your-application-without-oauth-consent-screen-4fcc1f8eb380
Solved with:
- create Service account & download Service key as '.json' file (see Medium article above)
- apply Domain Wide Delegation to the Service account (see Google Admin article above)
- use following code:
from google.oauth2 import service_account
SCOPES = ['https://www.googleapis.com/auth/calendar']
credentials = service_account.Credentials.from_service_account_file(token, scopes=SCOPES)
creds = credentials.with_subject(calendar_id)
service = build('calendar', 'v3', credentials=creds)
where token
is the path to the Service key file, and calendar_id
is the email address of the Google Calendar.