diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..4b2df06 Binary files /dev/null and b/.DS_Store differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/Timtabla.iml b/.idea/Timtabla.iml new file mode 100644 index 0000000..c03f621 --- /dev/null +++ b/.idea/Timtabla.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..590a59e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a0b31cb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/db/init.py b/db/init.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py index b096ea7..5a57cae 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,9 @@ from datetime import datetime, timezone import pytz import hashlib import os +from flask import Flask, request, send_file, render_template +import requests +from requests_ntlm import HttpNtlmAuth # ------------------------------------------------------------ # Config @@ -12,15 +15,12 @@ RESPONSE_FILE = "response.txt" OLD_CALENDAR = "old_calendar.ics" NEW_CALENDAR = "calendar.ics" LOCAL_TZ = pytz.timezone("Europe/London") +URL = "https://webapp.coventry.ac.uk/Timetable-main/Timetable/Current" -# ------------------------------------------------------------ -# Parsing timetable JS -# ------------------------------------------------------------ +app = Flask(__name__) def parse_events(events_data): - # Replace JS date objects events_data = events_data.replace("new Date", "") - cleaned_data = "" for line in events_data.split("\n"): @@ -67,9 +67,6 @@ def get_events_data_from_file(path): return source.split("events:")[1].split("]")[0] + "]" -# ------------------------------------------------------------ -# ICS helpers -# ------------------------------------------------------------ def ics_time(dt): return dt.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ") @@ -89,13 +86,9 @@ def create_ics_event(event): "end": event["end"], } -# ------------------------------------------------------------ -# Load existing calendar (UIDs only) -# ------------------------------------------------------------ def load_existing_uids(path): uids = set() - if not os.path.exists(path): return uids @@ -109,31 +102,30 @@ def load_existing_uids(path): if line == "BEGIN:VEVENT": current_uid = None cancelled = False - elif line.startswith("UID:"): current_uid = line[4:] - elif line == "STATUS:CANCELLED": cancelled = True - elif line == "END:VEVENT" and current_uid and not cancelled: uids.add(current_uid) return uids -# ------------------------------------------------------------ -# Main -# ------------------------------------------------------------ +def fetch_timetable(username, password): + session = requests.Session() + session.auth = HttpNtlmAuth(username, password) -def main(): + r = session.get(URL) + r.raise_for_status() + + with open("response.txt", "w", encoding="utf-8") as f: + f.write(r.text) + +def build_calendar(): events_js = get_events_data_from_file(RESPONSE_FILE) parsed_events = parse_events(events_js) - new_events = [ - create_ics_event(e) - for e in parsed_events - if e - ] + new_events = [create_ics_event(e) for e in parsed_events if e] old_uids = load_existing_uids(OLD_CALENDAR) new_uids = {e["uid"] for e in new_events} @@ -147,7 +139,6 @@ def main(): "CALSCALE:GREGORIAN", ] - # Add / update events for ev in new_events: lines.extend([ "BEGIN:VEVENT", @@ -160,7 +151,6 @@ def main(): "END:VEVENT", ]) - # Cancel removed events for uid in old_uids - new_uids: lines.extend([ "BEGIN:VEVENT", @@ -174,12 +164,31 @@ def main(): with open(NEW_CALENDAR, "w", encoding="utf-8") as f: f.write("\n".join(lines)) - - print(f"Added / updated: {len(new_events)}") - print(f"Removed: {len(old_uids - new_uids)}") - print(f"Wrote {NEW_CALENDAR}") - # ------------------------------------------------------------ +# Flask +# ------------------------------------------------------------ +@app.route("/") +def index(): + return render_template("index.html") + +@app.route("/login-and-download", methods=["POST"]) +def login_and_download(): + data = request.get_json() + username = data["username"] + password = data["password"] + username = f'COVENTRY\\{username}' + try: + fetch_timetable(username, password) #request + build_calendar() + return send_file( + NEW_CALENDAR, + as_attachment=True, + download_name="timetable.ics", + mimetype="text/calendar" + ) + except Exception as e: + return str(e), 400 + if __name__ == "__main__": - main() + app.run(debug=True) \ No newline at end of file diff --git a/static/apple-touch-icon.png b/static/apple-touch-icon.png new file mode 100644 index 0000000..bafe5e8 Binary files /dev/null and b/static/apple-touch-icon.png differ diff --git a/static/birdforfavicon.avif b/static/birdforfavicon.avif new file mode 100644 index 0000000..23a160b Binary files /dev/null and b/static/birdforfavicon.avif differ diff --git a/static/favicon-96x96.png b/static/favicon-96x96.png new file mode 100644 index 0000000..5314c4e Binary files /dev/null and b/static/favicon-96x96.png differ diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..99f9263 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..42b6230 --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/site.webmanifest b/static/site.webmanifest new file mode 100644 index 0000000..10576b6 --- /dev/null +++ b/static/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Timtabla (Cov Uni Timetable Converter)", + "short_name": "Timtabla", + "icons": [ + { + "src": "/images/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/images/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/static/web-app-manifest-192x192.png b/static/web-app-manifest-192x192.png new file mode 100644 index 0000000..5604a96 Binary files /dev/null and b/static/web-app-manifest-192x192.png differ diff --git a/static/web-app-manifest-512x512.png b/static/web-app-manifest-512x512.png new file mode 100644 index 0000000..eb3539f Binary files /dev/null and b/static/web-app-manifest-512x512.png differ diff --git a/templates/.DS_Store b/templates/.DS_Store new file mode 100644 index 0000000..240aa28 Binary files /dev/null and b/templates/.DS_Store differ diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c5e2f7f --- /dev/null +++ b/templates/index.html @@ -0,0 +1,96 @@ + + + + + + + + + + + Timtabla + + + +
+

Login to Download

+ + + +
+
+ + +