commit 13cab84fb2e918c8dbdee409f8f0661f92345512 Author: Harvey Tindall Date: Wed Mar 4 22:23:06 2020 +0000 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc9440e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +delete.py +*.timetable +token.pickle +credentials.json diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..dc91e2a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Harvey Tindall + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f67ad9 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# classtime-calendar + +Script to import a timetable from [ClassTime](https://play.google.com/store/apps/details?id=eu.nohus.classtime) into Google Calendar. +## Usage + +``` +typeprint.py [-tz|--timezone] [-c|--calendar] +``` +* `--timezone`: Timezone in TZ database. Defaults to `Europe/London`. +* `--calendar`: ID of calendar to apply to. To find: [calendar settings](https://calendar.google.com/calendar/r/settings) > click your calendar under "Settings for my calendar", "Calendar ID" under Integration at bottom of page. +* ``: .timetable file can be exported through the share menu in the ClassTime app. + +On the first run, a browser window will open where you can login. Credentials are saved to `token.pickle`/`credentials.json`. +## Known problems +* Currently only works with A/B week timetables. Each should be imported separately, at which time the script will ask whether it is for the current week. If you have a single timetable, either import twice as different weeks or change `self.recurrence`'s interval. +* The delete from calendar script was too rough to include, so there's no way to quickly remove classes from the calendar. + +## Licenses +Check LICENSE.txt. Code taken from quickstart.py in import.py includes Copyright notice diff --git a/import.py b/import.py new file mode 100755 index 0000000..a559a40 --- /dev/null +++ b/import.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# Script to import a timetable from the app ClassTime into a google calendar. +# Calendar ID is found in the calendar's settings, DB file can be exported through the share button in the app. +# Only works with A/B timetables, which are exported separately. + +import json, datetime, pickle, os.path, sys, argparse +from googleapiclient.discovery import build +from google_auth_oauthlib.flow import InstalledAppFlow +from google.auth.transport.requests import Request + +parser = argparse.ArgumentParser() +parser.add_argument("timetable", help="filename of .timetable db.") +parser.add_argument("-tz", "--timezone", help="Timezone in TZ database format. Defaults to Europe/London.") +parser.add_argument("-c", "--calendar", help="ID of the calendar you are adding to. Found under the settings on calendar.google.com.") +try: + args = parser.parse_args() + calendar_id = args.calendar + dbfile = args.timetable + if not args.timezone: + timeZone = "Europe/London" + else: + timeZone = args.timezone +except FileNotFoundError: + print("No filename supplied.") + sys.exit() + +print("This script only works for A/B week timetables, each being loaded seperately.") +choice = input("Is this timetable made for this week? [y/n]: ").lower() +if choice == "y": + today = datetime.datetime.today() +elif choice == "n": + today = datetime.datetime.today() + datetime.timedelta(days=7) +else: + sys.exit() + +days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + +class Lesson: + def __init__(self, name, startDelta, endDelta, teacher, room, day): + self.summary = name + if teacher != "": + self.description = "Teacher: {}".format(teacher) + self.location = "Room: {}".format(room) + currentDay = days.index(today.strftime("%A")) + lessonDay = days.index(day) + dayDelta = lessonDay - currentDay + applyDate = (today + datetime.timedelta(days=dayDelta)).day + # the .timetable file stores lesson start & end times as minutes from 00:00, so they are converted here + self.start = { + "dateTime": datetime.datetime(today.year, today.month, applyDate, int(startDelta/60), int(startDelta%60)).isoformat(), + "timeZone": timeZone + } + self.end = { + "dateTime": datetime.datetime(today.year, today.month, applyDate, int(endDelta/60), int(endDelta%60)).isoformat(), + "timeZone": timeZone + } + # Change this if you have the same timetable each week + self.recurrence = ["RRULE:FREQ=WEEKLY;INTERVAL=2"] + + + +lessons = [] +try: + with open(dbfile, "r") as raw: + db = json.load(raw) + for d in range(1,6): + try: + for l in range(1,6): + name = db["LESSON_{}_{}".format(d,l)] + startDelta = db["LESSON_START_TIME_{}_{}".format(d,l)] + endDelta = db["LESSON_END_TIME_{}_{}".format(d,l)] + teacher = db["TEACHER_{}_{}".format(d,l)] + room = db["ROOM_{}_{}".format(d,l)] + day = days[d-1] + if name != "": + lessons.append(Lesson(name, startDelta, endDelta, teacher, room, day)) + except: + pass +except FileNotFoundError: + print("File not found.") + sys.exit() + +print("Lessons imported. Do these look right?") +for l in lessons: + try: + d = l.description+", " + except: + d = "" + print("{}: {}, {}{}".format(l.start["dateTime"], l.summary, d, l.location)) +if input(":) ? [y/n]: ").lower() != "y": + sys.exit() + +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# borrowed from https://github.com/gsuitedevs/python-samples/blob/master/drive/quickstart/quickstart.py +# Removed unnecessary libraries, comments, and changed scope. +SCOPES = ['https://www.googleapis.com/auth/calendar'] + +creds = None +if os.path.exists('token.pickle'): + with open('token.pickle', 'rb') as token: + creds = pickle.load(token) +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.json', SCOPES) + creds = flow.run_local_server(port=0) + # Save the credentials for the next run + with open('token.pickle', 'wb') as token: + pickle.dump(creds, token) + +service = build('calendar', 'v3', credentials=creds) + +for l in lessons: + event = json.loads(json.dumps(l.__dict__)) + event = service.events().insert(calendarId=calendar_id, body=event).execute() +print("Events Added. Refresh your calendar to see classes.")