223 lines
8.3 KiB
Nix
223 lines
8.3 KiB
Nix
{ config, pkgs, secrets, ... }:
|
|
let in
|
|
{
|
|
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" {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():
|
|
os.remove(PICKLE_PATH)
|
|
os.remove(TOKEN_PATH)
|
|
os.system('notify-send --app-name="nx_gcal_event" "Deleted Token"')
|
|
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()
|
|
'')
|
|
];
|
|
};
|
|
}
|