bumblebee-status/bumblebee_status/modules/contrib/gcalendar.py

172 lines
6.5 KiB
Python
Raw Permalink Normal View History

2022-04-08 14:15:28 +02:00
"""Displays first upcoming event in google calendar.
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.
2022-04-08 14:15:28 +02:00
A refresh is done every 15 minutes.
Parameters:
* gcalendar.time_format: Format time output. Defaults to "%H:%M".
* gcalendar.date_format: Format date output. Defaults to "%d.%m.%y".
* gcalendar.credentials_path: Path to credentials.json. Defaults to "~/".
2022-08-10 17:02:35 +02:00
* gcalendar.locale: locale to use rather than the system default.
2022-04-08 14:15:28 +02:00
Requires these pip packages:
* google-api-python-client >= 1.8.0
* google-auth-httplib2
2022-04-08 14:15:28 +02:00
* google-auth-oauthlib
"""
# This import belongs to the google code
from __future__ import print_function
from dateutil.parser import parse as dtparse
import core.module
import core.widget
import core.decorators
import util.format
2022-04-08 14:15:28 +02:00
import datetime
import os.path
2022-08-10 17:02:35 +02:00
import locale
import time
2022-04-08 14:15:28 +02:00
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
# Minutes
update_every = 15
2022-04-08 14:23:22 +02:00
2022-04-08 14:15:28 +02:00
class Module(core.module.Module):
@core.decorators.every(minutes=update_every)
2022-04-08 14:15:28 +02:00
def __init__(self, config, theme):
super().__init__(config, theme, [core.widget.Widget(self.__datetime), core.widget.Widget(self.__summary)])
self.__error = False
2022-04-08 14:15:28 +02:00
self.__time_format = self.parameter("time_format", "%H:%M")
self.__date_format = self.parameter("date_format", "%d.%m.%y")
2022-04-08 14:23:22 +02:00
self.__credentials_path = os.path.expanduser(
self.parameter("credentials_path", "~/")
)
self.__credentials = os.path.join(self.__credentials_path, "credentials.json")
self.__token = os.path.join(self.__credentials_path, ".gcalendar_token.json")
2022-04-08 14:15:28 +02:00
2022-08-10 17:02:35 +02:00
l = locale.getdefaultlocale()
if not l or l == (None, None):
l = ("en_US", "UTF-8")
lcl = self.parameter("locale", ".".join(l))
try:
locale.setlocale(locale.LC_TIME, lcl.split("."))
except Exception:
locale.setlocale(locale.LC_TIME, ("en_US", "UTF-8"))
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):
2022-04-08 14:23:22 +02:00
SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
2022-04-08 14:15:28 +02:00
creds = None
2022-04-08 14:15:28 +02:00
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())
2022-04-08 14:23:22 +02:00
service = build("calendar", "v3", credentials=creds)
2022-04-08 14:15:28 +02:00
# Call the Calendar API
2022-04-08 14:23:22 +02:00
now = datetime.datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
end = (
datetime.datetime.utcnow() + datetime.timedelta(days=7)
).isoformat() + "Z" # 'Z' indicates UTC time
2022-04-08 14:15:28 +02:00
# Get all calendars
calendar_list = service.calendarList().list().execute()
event_list = []
2022-04-08 14:23:22 +02:00
for calendar_list_entry in calendar_list["items"]:
calendar_id = calendar_list_entry["id"]
events_result = (
service.events()
.list(
calendarId=calendar_id,
timeMin=now,
timeMax=end,
singleEvents=True,
orderBy="startTime",
)
.execute()
)
events = events_result.get("items", [])
2022-04-08 14:15:28 +02:00
for event in events:
2022-04-08 14:23:22 +02:00
start = dtparse(
event["start"].get("dateTime", event["start"].get("date"))
)
2022-04-08 14:15:28 +02:00
# Only add to list if not an whole day event
if start.tzinfo:
2022-04-08 14:23:22 +02:00
event_list.append(
{
"date": start,
"summary": event["summary"],
"type": event["eventType"],
}
)
sorted_list = sorted(event_list, key=lambda t: t["date"])
next_event = sorted_list[0]
2022-04-08 14:15:28 +02:00
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"])
2022-04-08 14:15:28 +02:00
return (None, "No upcoming events.")
2022-04-08 14:15:28 +02:00
except:
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()
2022-04-08 14:23:22 +02:00
2022-04-08 14:15:28 +02:00
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4