calendar-booking/calendar_booking_api.py

117 lines
3.0 KiB
Python

import bisect
from datetime import datetime, timedelta
from flask import Flask, jsonify, redirect, request
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
import pytz
# Monday to Friday
WEEK_DAYS = {1, 2, 3, 4, 5}
# 9 to 5
FROM = 9
TO = 17
TIMEZONE = pytz.timezone('America/New_York')
AVAILABILITY_LIFETIME = timedelta(minutes=2)
SCOPES = ['https://www.googleapis.com/auth/calendar']
UTC = pytz.timezone('UTC')
creds = Credentials.from_authorized_user_file('creds.json', SCOPES)
def read_datetime(dct):
dt = datetime.fromisoformat(dct['dateTime']).replace(tzinfo=None)
dt = pytz.timezone(dct['timeZone']).localize(dt)
return dt
def asutciso(dt):
return dt.astimezone(UTC).isoformat()[:19] + 'Z'
def aslocal(dt):
return dt.astimezone(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')
def get_availability():
if not creds.valid:
creds.refresh(Request())
service = build('calendar', 'v3', credentials=creds)
# Get events for the next 2 weeks
now = datetime.utcnow()
start = datetime(now.year, now.month, now.day)
end = start + timedelta(days=14)
events_result = service.events().list(
calendarId='primary',
timeMin=asutciso(now),
timeMax=asutciso(end),
maxResults=200,
singleEvents=True,
orderBy='startTime',
).execute()
# Build a list of possible time slots for the next 2 weeks
availability = []
for day in range(14):
day = start + timedelta(days=day)
if day.weekday() not in WEEK_DAYS:
continue
for hour in range(FROM, TO):
for minute in (0, 30):
availability.append(TIMEZONE.localize(
day.replace(hour=hour, minute=minute),
))
for event in events_result.get('items', []):
event_start = read_datetime(event['start'])
event_end = read_datetime(event['end'])
# Find the first slot that starts less than 30min before this event
first_idx = bisect.bisect_right(
availability,
event_start - timedelta(minutes=30),
)
# Find the first slot that starts after this event ends
last_idx = bisect.bisect_left(
availability,
event_end,
)
# Remove the slots from the availability
del availability[first_idx:last_idx]
return availability
availability = get_availability()
availability_as_of_date = datetime.utcnow()
app = Flask('calendar_booking_api')
@app.route('/availability')
def api_availability():
global availability, availability_as_of_date
if (
availability_as_of_date is None
or availability_as_of_date + AVAILABILITY_LIFETIME < datetime.utcnow()
):
availability = get_availability()
availability_as_of_date = datetime.utcnow()
return jsonify({'availability': [asutciso(time) for time in availability]})
@app.route('/book', methods=['POST'])
def api_book():
TODO