Initial commit.
This commit is contained in:
commit
13cab84fb2
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
delete.py
|
||||||
|
*.timetable
|
||||||
|
token.pickle
|
||||||
|
credentials.json
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@ -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.
|
19
README.md
Normal file
19
README.md
Normal file
@ -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] <timezone> [-c|--calendar] <calendar id> <timetable>
|
||||||
|
```
|
||||||
|
* `--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>`: .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
|
131
import.py
Executable file
131
import.py
Executable file
@ -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.")
|
Loading…
Reference in New Issue
Block a user