Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add to google calendar rewrite #148

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ data.txt
.idea/
.vscode
venv
.env
.env

ACADEMIC_CALENDAR_*.pdf
Academic_Cal-j/**
final.json
erpcreds.py
8 changes: 7 additions & 1 deletion gyft.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def parse_args():
action="store_true",
help="Delete events automatically added by the script before adding new events",
)

parser.add_argument('-c', "--cal", dest='cal', default="primary",
help="Name of the calendar in which the events will be added to, you will have to manually create a calendar on the google calendar app/website",)

args = parser.parse_args()
return args

Expand Down Expand Up @@ -53,8 +57,10 @@ def main():
print("3. Exit")
choice = int(input("Enter your choice: "))

print(args.cal)

if choice == 1:
create_calendar(courses)
create_calendar(courses,args.cal)
elif choice == 2:
generate_ics(courses, output_filename)
else:
Expand Down
232 changes: 153 additions & 79 deletions timetable/google_calendar.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,129 @@
from __future__ import print_function
from datetime import datetime
import os
from bs4 import BeautifulSoup
from googleapiclient.discovery import re
import httplib2
from apiclient import discovery
import icalendar
from oauth2client import client, file, tools
from oauth2client import file
from oauth2client import tools
from datetime import datetime, timedelta, date
from collections import defaultdict
from icalendar import Event
from icalendar import Calendar

from timetable.generate_ics import generate_ics
from utils import (
END_TERM_BEGIN,
SEM_BEGIN,
GYFT_RECUR_STRS,
get_rfc_time,
hdays,
holidays,
)
from timetable import Course
from utils.dates import MID_TERM_END

DEBUG = False

SCOPES = "https://www.googleapis.com/auth/calendar"
CLIENT_SECRET_FILE = "client_secret.json"
APPLICATION_NAME = "gyft"

def is_in_correct_sem(dt: datetime) -> bool:
if(datetime.now().replace(tzinfo=None) <= MID_TERM_END.replace(tzinfo=None)):
return dt.replace(tzinfo=None) < MID_TERM_END.replace(tzinfo=None)
elif( datetime.now().replace(tzinfo=None) >= MID_TERM_END.replace(tzinfo=None)):
return dt.replace(tzinfo=None) < END_TERM_BEGIN.replace(tzinfo=None) and dt.replace(tzinfo=None) > MID_TERM_END.replace(tzinfo=None)
else:
return True

def parse_ics(ics,length):
events = []
with open(ics, 'r') as rf:
ical = Calendar().from_ical(rf.read())
for i, comp in enumerate(ical.walk()):
if ((comp.name == "VEVENT") and ( length == "c" and is_in_correct_sem(comp.get('dtstart').dt)) ) :
event = {}
for name, prop in comp.property_items():

if name in ["SUMMARY", "LOCATION"]:
event[name.lower()] = prop.to_ical().decode('utf-8')

elif name == "DTSTART":
event["start"] = {
"dateTime": prop.dt.isoformat(),
"timeZone": ( str(prop.dt.tzinfo) if prop.dt.tzinfo else "Asia/Kolkata" )
}

elif name == "DTEND":
event["end"] = {
"dateTime": prop.dt.isoformat(),
"timeZone": ( str(prop.dt.tzinfo) if prop.dt.tzinfo else "Asia/Kolkata" )
}
elif name == "RRULE":
freq = str(prop.get("FREQ")[0]).strip()
duration = comp.get('duration').dt
end_time = (comp.get('dtstart').dt + duration)
until = prop.get('UNTIL')[0]
event["recurrence"] = [
("RRULE:FREQ="+freq+";UNTIL={}").format(
until.strftime("%Y%m%dT000000Z")
)
]
event["end"]= {
'dateTime': end_time.isoformat(),
"timeZone": "Asia/Kolkata"
}
elif name == "SEQUENCE":
event[name.lower()] = prop

elif name == "TRANSP":
event["transparency"] = prop.lower()

elif name == "CLASS":
event["visibility"] = prop.lower()

elif name == "ORGANIZER":
event["organizer"] = {
"displayName": prop.params.get("CN") or '',
"email": re.match('mailto:(.*)', prop).group(1) or ''
}

elif name == "DESCRIPTION":
desc = prop.to_ical().decode('utf-8')
desc = desc.replace(u'\xa0', u' ')
if name.lower() in event:
event[name.lower()] = desc + '\r\n' + event[name.lower()]
else:
event[name.lower()] = desc

elif name == 'X-ALT-DESC' and "description" not in event:
soup = BeautifulSoup(prop, 'lxml')
desc = soup.body.text.replace(u'\xa0', u' ')
if 'description' in event:
event['description'] += '\r\n' + desc
else:
event['description'] = desc

elif name == 'ATTENDEE':
if 'attendees' not in event:
event["attendees"] = []
RSVP = prop.params.get('RSVP') or ''
RSVP = 'RSVP={}'.format('TRUE:{}'.format(prop) if RSVP == 'TRUE' else RSVP)
ROLE = prop.params.get('ROLE') or ''
event['attendees'].append({
'displayName': prop.params.get('CN') or '',
'email': re.match('mailto:(.*)', prop).group(1) or '',
'comment': ROLE
# 'comment': '{};{}'.format(RSVP, ROLE)
})

# VALARM: only remind by UI popup
elif name == 'ACTION':
event['reminders'] = {'useDefault': True}

else:
# print(name)
pass

events.append(event)

return events

def get_credentials() -> client.Credentials:
"""Gets valid user credentials from storage.
Expand Down Expand Up @@ -53,7 +152,28 @@ def get_credentials() -> client.Credentials:
return credentials


def create_calendar(courses: list[Course]) -> None:
def get_calendarId(service, summary):
page_token = None
while True:
calendar_list = service.calendarList().list(pageToken=page_token).execute()
for calendar_list_entry in calendar_list['items']:
if calendar_list_entry['summary'] == summary:
return calendar_list_entry['id']
page_token = calendar_list.get('nextPageToken')
if not page_token:
return "primary"



def cb_insert_event(request_id, response, e):
summary = response['summary'] if response and 'summary' in response else '?'
if not e:
print('({}) - Insert event {}'.format(request_id, summary))
else:
print('({}) - Exception {}'.format(request_id, e))


def create_calendar(courses: list[Course], cal_name:str) -> None:
r"""
Adds courses to Google Calendar
Args:
Expand All @@ -62,77 +182,31 @@ def create_calendar(courses: list[Course]) -> None:
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build("calendar", "v3", http=http)
batch = service.new_batch_http_request() # To add events in a batch
for course in courses:
event = {
"summary": course.title,
"location": course.get_location(),
"start": {
"dateTime": get_rfc_time(course.start_time, course.day)[:-7],
"timeZone": "Asia/Kolkata",
},
"end": {
"dateTime": get_rfc_time(course.end_time, course.day)[:-7],
"timeZone": "Asia/Kolkata",
},
}

### making a string to pass in exdate. Changed the time of the string to class start time
exdate_str_dict = defaultdict(str)

for day in hdays[course.day]:
exdate_str_dict[course.day] += (
day.replace(hour=course.start_time).strftime("%Y%m%dT%H%M%S") + ","
)
if exdate_str_dict[course.day] != None:
exdate_str_dict[course.day] = exdate_str_dict[course.day][:-1]

if (
exdate_str_dict[course.day] != None
): ## if holiday exists on this recurrence, skip it with exdate
event["recurrence"] = [
"EXDATE;TZID=Asia/Kolkata:{}".format(exdate_str_dict[course.day]),
"RRULE:FREQ=WEEKLY;UNTIL={}".format(
END_TERM_BEGIN.strftime("%Y%m%dT000000Z")
),
]

batch = service.new_batch_http_request(callback=cb_insert_event) # To add events in a batch
filename = "timetable.ics" if os.path.exists("timetable.ics") else "temp.ics"
if(filename == "timetable.ics"):
print("Using existing timetable.ics file, press 'n' to generate and use a temporary one or 'y' to continue : (Y/n)")
if(input().lower() == "n" and True):
generate_ics(courses, "temp.ics")
else:
event["recurrence"] = [
"RRULE:FREQ=WEEKLY;UNTIL={}".format(
END_TERM_BEGIN.strftime("%Y%m%dT000000Z")
)
]

batch.add(service.events().insert(calendarId="primary", body=event))
print("Added " + event["summary"])

batch.execute() ## execute batch of timetable

# add holidays to calender as events
for holiday in holidays:
if (
holiday[1].date() >= date.today()
and holiday[1].date() <= END_TERM_BEGIN.date()
):
holiday_event = {
"summary": "INSTITUTE HOLIDAY : " + holiday[0],
"start": {
"dateTime": holiday[1].strftime("%Y-%m-%dT00:00:00"),
"timeZone": "Asia/Kolkata",
},
"end": {
"dateTime": (holiday[1] + timedelta(days=1)).strftime(
"%Y-%m-%dT00:00:00"
),
"timeZone": "Asia/Kolkata",
},
}
insert = (
service.events()
.insert(calendarId="primary", body=holiday_event)
.execute()
)
print("Invalid input")
exit(1)

calendar_id = get_calendarId(service,cal_name)
length = input("Do you want Events from (C)urrent or (B)oth Semesters (C/b) _default is current_ : ") or 'c'
if(length.lower() == 'b'):
print("WARNING: Events from both semesters will be added.\n This may result in duplicate events if tool is used in both semesters")
events = parse_ics(filename, length.lower())

for i, event in enumerate(events):
try:
print("[ADDING EVENT]: ",event,"\n")
batch.add(service.events().insert(calendarId=calendar_id, body=event))
except Exception as e:
print(e)
batch.execute(http=http)
if(os.path.exists("temp.ics")):
os.remove("temp.ics")

print("\nAll events added successfully!\n")

Expand Down