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
+
+
+
+
+
+
+
+