From 87a229f791e2017898c1bba3b0e33a693a8d58fc Mon Sep 17 00:00:00 2001 From: Raphael Maenle Date: Sat, 30 May 2020 23:03:27 +0200 Subject: [PATCH] Large restructure of google and backend separation - googleHandler now takes care of every google communication and mostly nothing else - only backend classes for Calendar and Events are used for communication - backend now takes care of every other interaction - functionality is still the same though --- Routine.py | 42 -------- __init__.py | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++- caltojson.py | 267 ------------------------------------------------- 3 files changed, 273 insertions(+), 311 deletions(-) delete mode 100644 Routine.py delete mode 100644 caltojson.py diff --git a/Routine.py b/Routine.py deleted file mode 100644 index cdc3737..0000000 --- a/Routine.py +++ /dev/null @@ -1,42 +0,0 @@ -from server import db -from database.models import User, Calendar -from backend import caltojson -import os - -import google.oauth2.credentials -import json - -class Routine: - - def updateCalendar(self, user, credentials): - # check google: - if credentials is None: - return - - visualCals = [] - for calendar in user.calendars: - if calendar.toggle == 'True': - visualCals.append(calendar.calendar_id) - googleCreds = google.oauth2.credentials.Credentials(**credentials) - calendarjson = caltojson.generateJsonFromCalendarEntries(visualCals, googleCreds) - ''' - directory = 'userinfo/' + user.id + '/' - if not os.path.exists(directory): - os.makedirs(directory) - with open(directory + 'calendarevents.json', 'w', encoding='utf-8') as f: - json.dump(calendarjson, f, ensure_ascii=False, indent=4) - ''' - return calendarjson - - -def credentials_to_dict(credentials): - return{'token': credentials.token, - 'refresh_token': credentials.refresh_token, - 'token_uri': credentials.token_uri, - 'client_id': credentials.client_id, - 'client_secret': credentials.client_secret, - 'scopes': credentials.scopes} - -if __name__ == "__main__": - routine = Routine() - routine.start() diff --git a/__init__.py b/__init__.py index aa54bf7..044cc51 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,274 @@ -from backend.Routine import Routine +from __future__ import print_function +import datetime +import dateutil.parser +import pickle +import json +import os.path +from google_auth_oauthlib.flow import InstalledAppFlow +from google.auth.transport.requests import Request -routine = Routine() +from server import db + +# If modifying these scopes, delete the file token.pickle. +SCOPES = ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/calendar.readonly", "openid"] +API_SERVICE_NAME = 'calendar' +API_VERSION = 'v3' + +class Calendar: + def __init__(self, name, calendarId, toggle='False', color="#000000"): + self.name = name + self.color = color + self.toggle=toggle + self.calendarId = calendarId + +class Event: + def __init__(self, name_, start_, end_): + self.name = name_ + self.eventColorId = 0 + self.calendarColorId = None + self.naturalColorId = 0 + self.start = start_ + self.end = end_ + self.colorHex = '#adfff5' + + if self.start == None or self.end == None : + self.allDay = True + else: + self.allDay = False + + def startDateTime(self): + if self.allDay: + return None + return self.jsonFromDT(self.start) + + def stopDateTime(self): + if self.allDay: + return None + return self.jsonFromDT(self.end) + + def jsonFromDT(self, string): + sdt = dateutil.parser.parse(string) + data = { + 'date': { + 'year': sdt.year, + 'month': sdt.month, + 'day': sdt.day + }, + 'time': { + 'hour': sdt.hour, + 'minute': sdt.minute, + 'second': sdt.second + } + } + return data + + def print(self): + if self.allDay: + print(self.name + "All Day") + else: + print(self.name + ": " + self.naturalColorId) + + + +def calendarCredentials(token = None): + creds = None + # The file token.pickle stores the user's access and refresh tokens, and is + # created automatically when the authorization flow completes for the first + # time. + + if token == None: + if os.path.exists('token.pickle'): + with open('token.pickle', 'rb') as token: + token.seek(0) + creds = pickle.load(token) + + else: + creds = pickle.load(token) + + # 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=1234) + # Save the credentials for the next run + with open('token.pickle', 'wb') as token: + pickle.dump(creds, token) + + service = build('calendar', 'v3', credentials=creds) + + return service + +def updateCalendars(user, calendars, colors): + + COLORS_FILE = os.path.join(os.path.dirname(__file__), 'colors.json') + with open(COLORS_FILE) as f: + colormap = json.load(f) + + # add all new calendars into the database + # TODO update existing Name and Calendar ( do this in User class at db? ) + # Take care, that you don't overwrite the custom color selection of the user + for calendar in calendars: + if not user.hasCalendar(calendar.calendarId): + color = fromColorIdGetColor(calendar.color, colormap, colors) + c = dbCalendar(calendar_id=calendar.calendarId, + name = calendar.summary, + toggle = "False", + color = color) + db.session.add(c) + current_user.calendars.append(c) + + db.session.commit() + +def calendarsFromDb(user): + pyCalendars = [] + for calendar in user.calendars: + name = (calendar.name[:16] + '..') if len(calendar.name)> 18 else calendar.name + calendarId = calendar.calendar_id + toggle = calendar.toggle + color = calendar.color + + pyCalendars.append(Calendar(name, calendarId, toggle, color)) + + return pyCalendars + + +def purgeCalendars(calendars, visibleCalendars): + purged = [] + for calendar in calendars: + if calendar.calendarId in visibleCalendars: + purged.append(calendar) + return purged + +def getCalendarEvents(service, userCalendars, startDate, endDate): + + + visualCals = [] + for calendar in userCalendars: + if calendar.toggle == 'True': + visualCals.append(calendar.calendar_id) + + calendars = getCalendars(service) + calendars = purgeCalendars(calendars, visualCals) + + all_events = [] + + + for calendar in calendars: + events_result = service.events().list(calendarId=calendar.calendarId, timeMin=startDate, + timeMax=endDate, + maxResults=10, singleEvents=True, + orderBy='startTime').execute() + for event in events_result.get('items', []): + + # create simple event + name = event.get('summary', '(no Title)') + start = event['start'].get('dateTime') + end = event['end'].get('dateTime') + newEvent = Event(name, start, end) + + # handle weird colors from google + color = event.get('colorId') + if color == None: + for userCalendar in userCalendars: + if userCalendar.calendar_id == calendar.calendarId: + newEvent.calendarHex = userCalendar.color + newEvent.calendarColorId = calendar.color + else: + newEvent.eventColorId = color + + + all_events.append(newEvent) + + return all_events + +def toCalendarColorId(colormap, colorId): + for remap in colormap['eventRemap']: + if remap['from'] == colorId: + return remap['to'] + + print(f"failed with {colorId}") + return colorId + +def toNaturalColor(colormap, orgColor): + for remap in colormap['colors']: + if remap['api'] == orgColor: + return remap['natural'] + + print(f"failed with {orgColor}") + return orgColor + +def colorizeEvents(events, colors): + COLORS_FILE = os.path.join(os.path.dirname(__file__), 'colors.json') + with open(COLORS_FILE) as f: + colormap = json.load(f) + + + for event in events: + if event.calendarColorId != None: + event.colorHex = forEventGetColor(event, colormap, colors) + +# this function is only used once for calendar generation stuff +def fromColorIdGetColor(color, colormap, colors): + return toNaturalColor(colormap, colors['calendar'][color]['background']) + +def forEventGetColor(event, colormap, colors): + if event.calendarColorId != 0: + bg = colors['calendar'][event.calendarColorId]['background'] + else: + calColorId = toCalendarColorId(colormap, event.eventColorId) + bg = colors['calendar'][calColorId]['background'] + return toNaturalColor(colormap, bg) + +def toJson(events): + data = {} + data['kind'] = 'calendar#events' + data['events'] = [] + + for event in events: + if event.allDay: + continue + + data['events'].append({ + 'name': event.name, + 'isAllDay': event.allDay, + 'color': event.colorHex, + 'startDateTime': event.startDateTime(), + 'stopDateTime': event.stopDateTime() + }) + + return data + +def printColors(colors): + for i in range(1, 25): + col = colors['event'][str(i)]['background'] + print(f"{i}: {col}") + +def main(): + generateJsonFromCalendarEntries() + + +def getTimeStamps(): + + # define today and tomorrow + now = datetime.datetime.now(datetime.timezone.utc).astimezone() + today = now.replace(hour=0, minute=0, second = 0).isoformat() + tomorrow = now.replace(hour=23, minute=59, second=59).isoformat() # + '+01:00' + twodaysfromnow = datetime.datetime.today() + datetime.timedelta(days=2) + twodaysfromnow = twodaysfromnow.replace(hour=23, minute=59, second=59).astimezone().isoformat() + + return today, twodaysfromnow + +def generateJsonFromCalendarEntries(events, colors): + + # fix all colors in events that have a different event color + colorizeEvents(events, colors) + # if not events: + # print('No upcoming events found.') + + return toJson(events) + +if __name__ == '__main__': + main() diff --git a/caltojson.py b/caltojson.py deleted file mode 100644 index c1dfb41..0000000 --- a/caltojson.py +++ /dev/null @@ -1,267 +0,0 @@ -from __future__ import print_function -import datetime -import dateutil.parser -import pickle -import json -import os.path -from googleapiclient.discovery import build -from google_auth_oauthlib.flow import InstalledAppFlow -from google.auth.transport.requests import Request - -# If modifying these scopes, delete the file token.pickle. -SCOPES = ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/calendar.readonly", "openid"] -API_SERVICE_NAME = 'calendar' -API_VERSION = 'v3' - -class Event: - def __init__(self, name_, start_, end_): - self.name = name_ - self.eventColorId = 0 - self.calendarColorId = 0 - self.naturalColorId = 0 - self.start = start_ - self.end = end_ - self.colorHex = '#adfff5' - - if self.start == None or self.end == None : - self.allDay = True - else: - self.allDay = False - - def startDateTime(self): - if self.allDay: - return None - return self.jsonFromDT(self.start) - - def stopDateTime(self): - if self.allDay: - return None - return self.jsonFromDT(self.end) - - def jsonFromDT(self, string): - sdt = dateutil.parser.parse(string) - data = { - 'date': { - 'year': sdt.year, - 'month': sdt.month, - 'day': sdt.day - }, - 'time': { - 'hour': sdt.hour, - 'minute': sdt.minute, - 'second': sdt.second - } - } - return data - - def print(self): - if self.allDay: - print(self.name + "All Day") - else: - print(self.name + ": " + self.naturalColorId) - - -class Calendar: - def __init__(self, summary_, calendarId_, color_): - self.summary = summary_ - self.calendarId = calendarId_ - self.color = color_ - -def calendarCredentials(token = None): - creds = None - # The file token.pickle stores the user's access and refresh tokens, and is - # created automatically when the authorization flow completes for the first - # time. - - if token == None: - if os.path.exists('token.pickle'): - with open('token.pickle', 'rb') as token: - token.seek(0) - creds = pickle.load(token) - - else: - creds = pickle.load(token) - - # 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=1234) - # Save the credentials for the next run - with open('token.pickle', 'wb') as token: - pickle.dump(creds, token) - - service = build('calendar', 'v3', credentials=creds) - - return service - -def getCalendarColors(service): - colors = service.colors().get().execute() - return colors - -def getCalendars(service): - page_token = None - calendars = [] - while True: - calendar_list = service.calendarList().list(pageToken=page_token).execute() - for calendar in calendar_list['items']: - calendars.append(Calendar(calendar['summary'], calendar['id'], calendar['colorId'])) - page_token = calendar_list.get('nextPageToken') - if not page_token: - break - - return calendars - -def purgeCalendars(calendars, visibleCalendars): - purged = [] - for calendar in calendars: - if calendar.calendarId in visibleCalendars: - purged.append(calendar) - return purged - -def getCalendarEvents(service, visibleCalendars, startDate, endDate): - - calendars = getCalendars(service) - calendars = purgeCalendars(calendars, visibleCalendars) - - all_events = [] - - for calendar in calendars: - events_result = service.events().list(calendarId=calendar.calendarId, timeMin=startDate, - timeMax=endDate, - maxResults=10, singleEvents=True, - orderBy='startTime').execute() - - for event in events_result.get('items', []): - - # create simple event - name = event.get('summary', '(no Title)') - start = event['start'].get('dateTime') - end = event['end'].get('dateTime') - newEvent = Event(name, start, end) - - # handle weird colors from google - color = event.get('colorId') - if color == None: - newEvent.calendarColorId = calendar.color - else: - newEvent.eventColorId = color - - - all_events.append(newEvent) - - return all_events - -def toCalendarColorId(colormap, colorId): - for remap in colormap['eventRemap']: - if remap['from'] == colorId: - return remap['to'] - - print(f"failed with {colorId}") - return colorId - -def toNaturalColor(colormap, orgColor): - for remap in colormap['colors']: - if remap['api'] == orgColor: - return remap['natural'] - - print(f"failed with {orgColor}") - return orgColor - -def colorizeEvents(allEvents, service): - COLORS_FILE = os.path.join(os.path.dirname(__file__), 'colors.json') - with open(COLORS_FILE) as f: - colormap = json.load(f) - - colors = getCalendarColors(service) - - for event in allEvents: - event.colorHex = forEventGetColor(event, colormap, colors) - -# this function is only used once for calendar generation stuff -def fromColorIdGetColor(color, colormap, colors): - return toNaturalColor(colormap, colors['calendar'][color]['background']) - -def forEventGetColor(event, colormap, colors): - if event.calendarColorId != 0: - bg = colors['calendar'][event.calendarColorId]['background'] - else: - calColorId = toCalendarColorId(colormap, event.eventColorId) - bg = colors['calendar'][calColorId]['background'] - return toNaturalColor(colormap, bg) - -def toJson(events): - data = {} - data['kind'] = 'calendar#events' - data['events'] = [] - - for event in events: - if event.allDay: - continue - - data['events'].append({ - 'name': event.name, - 'isAllDay': event.allDay, - 'color': event.colorHex, - 'startDateTime': event.startDateTime(), - 'stopDateTime': event.stopDateTime() - }) - - return data - -def printColors(colors): - for i in range(1, 25): - col = colors['event'][str(i)]['background'] - print(f"{i}: {col}") - -def main(): - generateJsonFromCalendarEntries() - -def getCalendarList(credentials = None): - # create service - if credentials == None: - credentials = calendarCredentials() - service = build(API_SERVICE_NAME, API_VERSION, credentials=credentials) - - calendars = getCalendars(service) - - COLORS_FILE = os.path.join(os.path.dirname(__file__), 'colors.json') - with open(COLORS_FILE) as f: - colormap = json.load(f) - - colors = getCalendarColors(service) - - for calendar in calendars: - calendar.color = fromColorIdGetColor(calendar.color, colormap, colors) - return calendars - -def generateJsonFromCalendarEntries(visibleCalendars, credentials = None): - - # create service - if credentials == None: - credentials = calendarCredentials() - service = build(API_SERVICE_NAME, API_VERSION, credentials=credentials) - - # define today and tomorrow - now = datetime.datetime.now(datetime.timezone.utc).astimezone() - today = now.replace(hour=0, minute=0, second = 0).isoformat() - tomorrow = now.replace(hour=23, minute=59, second=59).isoformat() # + '+01:00' - twodaysfromnow = datetime.datetime.today() + datetime.timedelta(days=2) - twodaysfromnow = twodaysfromnow.replace(hour=23, minute=59, second=59).astimezone().isoformat() - - print(tomorrow, flush=True) - print('----') - print(twodaysfromnow, flush=True) - allEvents = getCalendarEvents(service, visibleCalendars, today, twodaysfromnow) - - colorizeEvents(allEvents, service) - # if not events: - # print('No upcoming events found.') - - return toJson(allEvents) - -if __name__ == '__main__': - main()