Files
dotfiles/home-modules/nx-gcal-event.nix
Lennart J. Kurzweg (Nx2) d97010da0c color rework, flake bump
2024-05-12 21:55:03 +02:00

228 lines
8.4 KiB
Nix
Executable File

{ config, pkgs, secrets, lib, user, ... }:
let
sep = " ";
in
lib.mkIf (user != "tv")
{
home = {
file."${config.xdg.dataHome}/nx-gcal-event-credentials.json".text = ''
{
"installed": {
"client_id": "${secrets.nx-gcal-event.client-client-id}",
"project_id": "my-own-cal",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "${secrets.nx-gcal-event.client-secret}",
"redirect_uris": [
"http://localhost"
]
}
}
'';
packages = with pkgs; [
# TODO: make into real package, currently dependencies are in home.nix
# (pkgs.python311.withPackages (python-pkgs: [
# python-pkgs.google
# ]))
(writeScriptBin "nx_gcal_event" ''
#!${pkgs.python3}/bin/python3
import datetime
import os
import pickle
import sys
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
from html import escape
CREDENTIALS_PATH = f"{os.environ['XDG_DATA_HOME']}/nx-gcal-event-credentials.json"
TOKEN_PATH = f"{os.environ['XDG_CACHE_HOME']}/nx-gcal-event-token.json"
PICKLE_PATH = "/tmp/nx-gcal-event.pickle"
def sec_to_nice_string(seconds: int):
(hours, rsec) = divmod(seconds, 3600)
minutes = rsec // 60
sep = " "
if hours == 0:
s_hours = f""
sep = ""
elif hours == 1: s_hours = f"{hours} hour"
else: s_hours = f"{hours} hours"
if minutes == 0:
s_minutes = f""
sep = ""
elif minutes == 1: s_minutes = f"{minutes} minute"
else: s_minutes = f"{minutes} minutes"
if hours + minutes == 0:
s_minutes = "~ No time"
os.remove(PICKLE_PATH)
return f"{s_hours}{sep}{s_minutes}"
def get_event_from_api():
creds = None
SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
if os.path.exists(TOKEN_PATH):
creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
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_PATH, SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(TOKEN_PATH, "w") as token:
token.write(creds.to_json())
try:
service = build("calendar", "v3", credentials=creds)
now = datetime.datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
in_24_h = (datetime.datetime.utcnow() + datetime.timedelta(days=1)).isoformat() + "Z"
calendar_list = service.calendarList().list().execute()
calendars = calendar_list.get("items", [])
# List events from all calendars
all_events = []
for calendar in calendars:
calendar_id = calendar["id"]
events_result = service.events().list(
calendarId=calendar_id,
timeMin=now,
timeMax=in_24_h,
singleEvents=True,
orderBy="startTime",
).execute()
events = events_result.get("items", [])
all_events.extend(events)
# Filter out all-day events
all_events = [event for event in all_events if "dateTime" in event["start"]]
# Find the earliest event
earliest_event = None
for event in all_events:
event_start = event["start"]["dateTime"]
if not earliest_event or event_start < earliest_event["start"]["dateTime"]:
earliest_event = event
# Now earliest_event contains the event that starts earliest
return earliest_event
except HttpError as error:
print("An error occurred: %s" % error)
exit(1)
def dump_dict_to_file(event, now):
if not event:
event = {}
event['nxWriteTime'] = now
with open(PICKLE_PATH, 'wb') as f:
pickle.dump(event, f)
def load_dict_from_file(now):
with open(PICKLE_PATH, 'rb') as f:
event = pickle.load(f)
# recheck all 15 minutes
if (now - event['nxWriteTime']).seconds > 900:
event = get_event_from_api()
os.remove(PICKLE_PATH)
return event
def lookup():
# set now (timezone CEST)
now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=2)))
no_event_string = "~ Zen ~"
if os.path.exists(PICKLE_PATH):
event = load_dict_from_file(now)
else:
event = get_event_from_api()
if not event:
print(no_event_string)
dump_dict_to_file(event, now)
exit(0)
dump_dict_to_file(event, now)
try: # when the saved even was empty (there was no event)
end = datetime.datetime.strptime(event["end"]["dateTime"], "%Y-%m-%dT%H:%M:%S%z")
start = datetime.datetime.strptime(event["start"]["dateTime"], "%Y-%m-%dT%H:%M:%S%z")
except:
if (now - event['nxWriteTime']).seconds > 900:
event = get_event_from_api()
os.remove(PICKLE_PATH)
else:
print(no_event_string)
exit(0)
# set mode, remaining
if start.day != now.day: # event start tomorrow
print(no_event_string)
exit(0)
elif (start - now).days < 0: # today, started alredy
remaining = end - now
mode = " remaining in "
else: # today, not started yet
remaining = start - now
mode = " until the start of "
name = escape(event['summary'])
print(f"󱙬${sep}{sec_to_nice_string(remaining.seconds)}{mode}\'{name}\'")
exit(0)
def print_help():
print("Usage: nx_gcal_event [lookup|force-lookup|reauthenicate|help]")
def forece_lookup():
try:
os.remove(PICKLE_PATH)
os.system('notify-send --app-name="nx_gcal_event" "Saved event deleted!"')
except:
os.system('notify-send --app-name="nx_gcal_event" "No saved event found!"')
finally:
lookup()
def reauthenicate():
try:
os.remove(PICKLE_PATH)
os.remove(TOKEN_PATH)
os.system('notify-send --app-name="nx_gcal_event" "Deleted Token"')
exept:
lookup()
def print_help():
print("Usage: nx_gcal_event [lookup|force-lookup|reauthenicate|help]")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Incorrect number of arguments.")
print_help()
else:
arg = sys.argv[1]
if arg == "lookup":
lookup()
elif arg == "force-lookup":
forece_lookup()
elif arg == "reauthenticate":
reauthenicate()
elif arg == "help":
print_help()
else:
print_help()
'')
];
};
}