add LibCal functionality
This commit is contained in:
parent
bae52e7d7e
commit
a5b5189e52
7 changed files with 341 additions and 71 deletions
|
|
@ -6,16 +6,22 @@ from dataclasses import dataclass
|
|||
class Config(object):
|
||||
discord_token: str | None = None
|
||||
discord_guild_id: int | None = None
|
||||
discord_study_room_thread: int | None = None
|
||||
git_branch: str | None = None
|
||||
git_url: str | None = None
|
||||
google_calendar_id: str | None = None
|
||||
time_zone: str | None = None
|
||||
|
||||
def config_decoder(obj):
|
||||
if '__type__' in obj and obj['__type__'] == "Config":
|
||||
return Config(
|
||||
discord_token=obj['discord_token'],
|
||||
discord_guild_id=obj['discord_guild_id'],
|
||||
discord_study_room_thread=obj['discord_study_room_thread'],
|
||||
git_branch=obj['git_branch'],
|
||||
git_url=obj['git_url']
|
||||
git_url=obj['git_url'],
|
||||
google_calendar_id=obj['google_calendar_id'],
|
||||
time_zone=obj['time_zone']
|
||||
)
|
||||
|
||||
def save_config(config: dict):
|
||||
|
|
|
|||
62
lib/gcal.py
Normal file
62
lib/gcal.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, time
|
||||
from pathlib import Path
|
||||
import json
|
||||
import pytz
|
||||
|
||||
from aiogoogle import Aiogoogle
|
||||
|
||||
from lib.config import load_config
|
||||
|
||||
config = load_config()
|
||||
|
||||
class GoogleCalendarAPI():
|
||||
def __init__(self, creds_path: Path = Path("token.json"), calendar_id = config.google_calendar_id):
|
||||
self._creds_path = creds_path
|
||||
self._calendar_id = calendar_id
|
||||
|
||||
def get_user_creds(self) -> dict:
|
||||
with Path(self._creds_path).open("r", encoding="utf-8") as fp:
|
||||
creds = json.load(fp)
|
||||
output = {"access_token": creds['token'], 'refresh_token': creds['refresh_token']}
|
||||
return output
|
||||
|
||||
def get_client_creds(self) -> dict:
|
||||
"""Taken from an Installed App credentials file"""
|
||||
with Path(self._creds_path).open("r", encoding="utf-8") as fp:
|
||||
creds = json.load(fp)
|
||||
output = {
|
||||
'client_id': creds['client_id'],
|
||||
'client_secret': creds['client_secret'],
|
||||
'scopes': creds['scopes']
|
||||
}
|
||||
return output
|
||||
|
||||
async def get_event(self, event_id) -> dict:
|
||||
async with Aiogoogle(user_creds=self.get_user_creds(), client_creds=self.get_client_creds()) as aiogoogle:
|
||||
api = await aiogoogle.discover("calendar", "v3")
|
||||
result = await aiogoogle.as_user(api.events.get(calendarId=self._calendar_id, eventId=event_id))
|
||||
return result
|
||||
|
||||
async def get_events(self,
|
||||
start_time = datetime.combine(datetime.now(), time.min, tzinfo=pytz.timezone(config.time_zone)).isoformat(),
|
||||
end_time = datetime.combine(datetime.now(), time.max, tzinfo=pytz.timezone(config.time_zone)).isoformat()
|
||||
):
|
||||
async with Aiogoogle(user_creds=self.get_user_creds(), client_creds=self.get_client_creds()) as aiogoogle:
|
||||
api = await aiogoogle.discover("calendar", "v3")
|
||||
result = await aiogoogle.as_user(api.events.list(calendarId = self._calendar_id, timeMin = start_time, timeMax = end_time))
|
||||
return result['items']
|
||||
|
||||
async def create_event(self, name: str, start_time: datetime, end_time: datetime):
|
||||
async with Aiogoogle(user_creds=self.get_user_creds(), client_creds=self.get_client_creds()) as aiogoogle:
|
||||
api = await aiogoogle.discover("calendar", "v3")
|
||||
event = {
|
||||
"summary": "",
|
||||
"start": {},
|
||||
"end": {}
|
||||
}
|
||||
event['summary'] = name
|
||||
event['start']['dateTime'] = start_time.isoformat().strip()
|
||||
event['end']['dateTime'] = end_time.isoformat().strip()
|
||||
await aiogoogle.as_user(api.events.insert(calendarId=self._calendar_id, json=event))
|
||||
87
lib/ocr.py
Normal file
87
lib/ocr.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
import pytz
|
||||
|
||||
from pytesseract import image_to_string
|
||||
from PIL import Image
|
||||
|
||||
from lib.config import load_config
|
||||
from lib.room import Room
|
||||
|
||||
__all__ = ["get_room_data"]
|
||||
|
||||
config = load_config()
|
||||
|
||||
RE_STRING = re.compile( # https://regex101.com/r/ELsqrO/1
|
||||
r"(L-[0-9]{4}): " # room number (group 1)
|
||||
# time-slot (group 2,3,4 - group 5,6,7)
|
||||
r"([1-12]{1,2}):([0,3]{2})(am|pm) - ([1-12]{1,2}):([0,3]{2})(am|pm), "
|
||||
# weekday (group 8)
|
||||
r"(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), "
|
||||
# month (group 9)
|
||||
r"(January|February|March|April|May|June|July|August|September|October|November|December) "
|
||||
r"([1-9][0-9]|[1-9]), ([0-9]{4})", # day & year (group 10 & 11)
|
||||
flags=re.M
|
||||
)
|
||||
|
||||
|
||||
class NotAMatchException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoMatchException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def add_zero_padding(value: str):
|
||||
"""Adds zero-padding to a single digit value."""
|
||||
if int(value) < 10:
|
||||
return "0"+str(int(value)) # this removes leading 0 if already present
|
||||
return value
|
||||
|
||||
|
||||
def libcal_to_datetime(year: str, month: str, day: str, hour: str, minute: str, am_pm: str) -> datetime:
|
||||
"""Takes date information as displayed by LibCal and turns it into a datetime object.
|
||||
All values should be given in an unmodified string format."""
|
||||
date = datetime.strptime(f"{year}-{month}-{add_zero_padding(day)}-"
|
||||
f"{add_zero_padding(hour)}-{add_zero_padding(minute)}-{am_pm}",
|
||||
"%Y-%B-%d-%I-%M-%p")
|
||||
tz = pytz.timezone(config.time_zone)
|
||||
return tz.localize(date) # adds timezone info to object
|
||||
|
||||
|
||||
def correct_commas(string: str):
|
||||
"""Ensures all commas have a space after them in the given string."""
|
||||
return re.sub(r"(,)([^ ])", r"\1 \2", string)
|
||||
|
||||
|
||||
def correct_newlines(string: str):
|
||||
"""Replaces all newlines with a space in the given string."""
|
||||
return string.replace("\n", " ")
|
||||
|
||||
|
||||
def get_room_data(img: bytes) -> list[Room]:
|
||||
"""Gets the room data from a given image"""
|
||||
rooms: list[Room] = []
|
||||
start_time: datetime | None = None
|
||||
end_time: datetime | None = None
|
||||
img_string = image_to_string(Image.open(BytesIO(img)))
|
||||
img_string = correct_newlines(img_string)
|
||||
img_string = correct_commas(img_string)
|
||||
matches = re.finditer(RE_STRING, img_string)
|
||||
for match in matches:
|
||||
if isinstance(match, re.Match):
|
||||
room_number = match.group(1)
|
||||
start_time = libcal_to_datetime(match.group(11), match.group(
|
||||
9), match.group(10), match.group(2), match.group(3), match.group(4))
|
||||
end_time = libcal_to_datetime(match.group(11), match.group(
|
||||
9), match.group(10), match.group(5), match.group(6), match.group(7))
|
||||
room = Room(room_number, start_time, end_time)
|
||||
rooms.append(room)
|
||||
else:
|
||||
raise NotAMatchException(match)
|
||||
if len(rooms) == 0:
|
||||
raise NoMatchException
|
||||
return rooms
|
||||
21
lib/room.py
Normal file
21
lib/room.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from datetime import datetime
|
||||
import dateutil.parser as parser
|
||||
|
||||
|
||||
class Room:
|
||||
def __init__(self, number: str, start_time: datetime, end_time: datetime):
|
||||
self.number: str = number
|
||||
self.start_time: datetime = start_time
|
||||
self.end_time: datetime = end_time
|
||||
|
||||
@classmethod
|
||||
def from_event_dict(cls, body: dict):
|
||||
start_time = parser.parse(body['start']['dateTime'])
|
||||
end_time = parser.parse(body['end']['dateTime'])
|
||||
return cls(body['summary'], start_time, end_time)
|
||||
|
||||
def get_time_str(self) -> str:
|
||||
date = self.start_time.strftime("%B %d")
|
||||
start = self.start_time.strftime("%H:%M")
|
||||
end = self.end_time.strftime("%H:%M")
|
||||
return f"{date}, {start} - {end}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue