From b327162f3b4c8aecb184a270d0455fecc156ed3c Mon Sep 17 00:00:00 2001 From: arivarton Date: Wed, 4 Jan 2023 21:34:21 +0100 Subject: [PATCH 1/2] Added a max_chars parameter to be able to control the widget width. Also moved the try block a bit further up to catch network errors. --- bumblebee_status/modules/contrib/gcalendar.py | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/bumblebee_status/modules/contrib/gcalendar.py b/bumblebee_status/modules/contrib/gcalendar.py index efaabf7..49805e3 100644 --- a/bumblebee_status/modules/contrib/gcalendar.py +++ b/bumblebee_status/modules/contrib/gcalendar.py @@ -3,7 +3,9 @@ Events that are set as 'all-day' will not be shown. Requires credentials.json from a google api application where the google calendar api is installed. -On first time run the browser will open and google will ask for permission for this app to access the google calendar and then save a .gcalendar_token.json file to the credentials_path directory which stores this permission. +On first time run the browser will open and google will ask for permission for this app to access +the google calendar and then save a .gcalendar_token.json file to the credentials_path directory +which stores this permission. A refresh is done every 15 minutes. @@ -12,10 +14,11 @@ Parameters: * gcalendar.date_format: Format date output. Defaults to "%d.%m.%y". * gcalendar.credentials_path: Path to credentials.json. Defaults to "~/". * gcalendar.locale: locale to use rather than the system default. + * gcalendar.max_chars: Maximum of characters to display. Requires these pip packages: * google-api-python-client >= 1.8.0 - * google-auth-httplib2 + * google-auth-httplib2 * google-auth-oauthlib """ @@ -51,6 +54,13 @@ class Module(core.module.Module): self.__credentials = os.path.join(self.__credentials_path, "credentials.json") self.__token = os.path.join(self.__credentials_path, ".gcalendar_token.json") + self.__max_chars = self.parameter("max_chars", None) + try: + if self.__max_chars: + self.__max_chars = int(self.__max_chars) + except ValueError: + self.__max_chars = None + l = locale.getdefaultlocale() if not l or l == (None, None): l = ("en_US", "UTF-8") @@ -63,29 +73,26 @@ class Module(core.module.Module): def first_event(self, widget): SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] - """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(self.__token): - creds = Credentials.from_authorized_user_file(self.__token, 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( - self.__credentials, SCOPES - ) - creds = flow.run_local_server(port=0) - # Save the credentials for the next run - with open(self.__token, "w") as token: - token.write(creds.to_json()) - try: + # 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(self.__token): + creds = Credentials.from_authorized_user_file(self.__token, 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( + self.__credentials, SCOPES + ) + creds = flow.run_local_server(port=0) + # Save the credentials for the next run + with open(self.__token, "w") as token: + token.write(creds.to_json()) + service = build("calendar", "v3", credentials=creds) # Call the Calendar API @@ -137,7 +144,7 @@ class Module(core.module.Module): .strftime(f"{self.__time_format}"), gevent["summary"], ) - ) + )[: self.__max_chars] else: return str( "%s %s" @@ -147,7 +154,7 @@ class Module(core.module.Module): .strftime(f"{self.__date_format} {self.__time_format}"), gevent["summary"], ) - ) + )[: self.__max_chars] return "No upcoming events found." except: From ae29c1b79f142eafb90d8cf7dfaea381e7cc7cfd Mon Sep 17 00:00:00 2001 From: arivarton Date: Sun, 29 Jan 2023 13:05:13 +0100 Subject: [PATCH 2/2] Divided date/time and summary into two widgets and made the summary widget scrollable. --- bumblebee_status/modules/contrib/gcalendar.py | 77 ++++++++++--------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/bumblebee_status/modules/contrib/gcalendar.py b/bumblebee_status/modules/contrib/gcalendar.py index 49805e3..6848ba8 100644 --- a/bumblebee_status/modules/contrib/gcalendar.py +++ b/bumblebee_status/modules/contrib/gcalendar.py @@ -14,7 +14,6 @@ Parameters: * gcalendar.date_format: Format date output. Defaults to "%d.%m.%y". * gcalendar.credentials_path: Path to credentials.json. Defaults to "~/". * gcalendar.locale: locale to use rather than the system default. - * gcalendar.max_chars: Maximum of characters to display. Requires these pip packages: * google-api-python-client >= 1.8.0 @@ -30,10 +29,12 @@ from dateutil.parser import parse as dtparse import core.module import core.widget import core.decorators +import util.format import datetime import os.path import locale +import time from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials @@ -41,11 +42,15 @@ from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError +# Minutes +update_every = 15 + class Module(core.module.Module): - @core.decorators.every(minutes=15) + @core.decorators.every(minutes=update_every) def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget(self.first_event)) + super().__init__(config, theme, [core.widget.Widget(self.__datetime), core.widget.Widget(self.__summary)]) + self.__error = False self.__time_format = self.parameter("time_format", "%H:%M") self.__date_format = self.parameter("date_format", "%d.%m.%y") self.__credentials_path = os.path.expanduser( @@ -54,13 +59,6 @@ class Module(core.module.Module): self.__credentials = os.path.join(self.__credentials_path, "credentials.json") self.__token = os.path.join(self.__credentials_path, ".gcalendar_token.json") - self.__max_chars = self.parameter("max_chars", None) - try: - if self.__max_chars: - self.__max_chars = int(self.__max_chars) - except ValueError: - self.__max_chars = None - l = locale.getdefaultlocale() if not l or l == (None, None): l = ("en_US", "UTF-8") @@ -70,10 +68,25 @@ class Module(core.module.Module): except Exception: locale.setlocale(locale.LC_TIME, ("en_US", "UTF-8")) - def first_event(self, widget): + self.__last_update = time.time() + self.__gcalendar_date, self.__gcalendar_summary = self.__fetch_from_calendar() + + def hidden(self): + return self.__error + + def __datetime(self, _): + return self.__gcalendar_date + + @core.decorators.scrollable + def __summary(self, _): + return self.__gcalendar_summary + + + def __fetch_from_calendar(self): SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] creds = None + try: # The file token.json stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first @@ -132,33 +145,27 @@ class Module(core.module.Module): } ) sorted_list = sorted(event_list, key=lambda t: t["date"]) + next_event = sorted_list[0] - for gevent in sorted_list: - if gevent["date"] >= datetime.datetime.now(datetime.timezone.utc): - if gevent["date"].date() == datetime.datetime.utcnow().date(): - return str( - "%s %s" - % ( - gevent["date"] - .astimezone() - .strftime(f"{self.__time_format}"), - gevent["summary"], - ) - )[: self.__max_chars] - else: - return str( - "%s %s" - % ( - gevent["date"] - .astimezone() - .strftime(f"{self.__date_format} {self.__time_format}"), - gevent["summary"], - ) - )[: self.__max_chars] - return "No upcoming events found." + if next_event["date"] >= datetime.datetime.now(datetime.timezone.utc): + if next_event["date"].date() == datetime.datetime.utcnow().date(): + dt = next_event["date"].astimezone()\ + .strftime(f"{self.__time_format}") + else: + dt = next_event["date"].astimezone()\ + .strftime(f"{self.__date_format} {self.__time_format}") + return (dt, next_event["summary"]) + return (None, "No upcoming events.") except: - return None + self.__error = True + def update(self): + # Since scrolling runs the update command and therefore negates the + # every decorator, this need to be stopped + # to not break the API rules of google. + if self.__last_update+(update_every*60) < time.time(): + self.__last_update = time.time() + self.__gcalendar_date, self.__gcalendar_summary = self.__fetch_from_calendar() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4