forked from Ugric/cov-to-ics
change to create an ics file instead of using google calender
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
credentials.json
|
||||
token.json
|
||||
timetableurl
|
||||
response.txt
|
||||
calendar.ics
|
||||
old_calendar.ics
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
||||
18
Pipfile
18
Pipfile
@@ -1,18 +0,0 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
verify_ssl = true
|
||||
url = "https://pypi.org/simple"
|
||||
|
||||
[packages]
|
||||
google-api-python-client = "*"
|
||||
"oauth2client" = "*"
|
||||
requests = "*"
|
||||
"bs4" = "*"
|
||||
pyjsparser = "*"
|
||||
pytz = "*"
|
||||
|
||||
[dev-packages]
|
||||
pylint = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
275
Pipfile.lock
generated
275
Pipfile.lock
generated
@@ -1,275 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "ffdcc7d81db0f5c5be8a6c0e7f3d57a38eff7d66c904d23adfc53ba06e7af315"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"beautifulsoup4": {
|
||||
"hashes": [
|
||||
"sha256:194ec62a25438adcb3fdb06378b26559eda1ea8a747367d34c33cef9c7f48d57",
|
||||
"sha256:90f8e61121d6ae58362ce3bed8cd997efb00c914eae0ff3d363c32f9a9822d10",
|
||||
"sha256:f0abd31228055d698bb392a826528ea08ebb9959e6bea17c606fd9c9009db938"
|
||||
],
|
||||
"version": "==4.6.3"
|
||||
},
|
||||
"bs4": {
|
||||
"hashes": [
|
||||
"sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.0.1"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
"sha256:90f1d559512fc073483fe573ef5ceb39bf6ad3d39edc98dc55178a2b2b176fa3",
|
||||
"sha256:d1c398969c478d336f767ba02040fa22617333293fb0b8968e79b16028dfee35"
|
||||
],
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
|
||||
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
|
||||
],
|
||||
"version": "==2018.8.24"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"google-api-python-client": {
|
||||
"hashes": [
|
||||
"sha256:5d5cb02c6f3112c68eed51b74891a49c0e35263380672d662f8bfe85b8114d7c",
|
||||
"sha256:7cc47cf80b25ecd7f3d917ea247bb6c62587514e40604ae29c47c0e4ebd1174b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.7.4"
|
||||
},
|
||||
"google-auth": {
|
||||
"hashes": [
|
||||
"sha256:9ca363facbf2622d9ba828017536ccca2e0f58bd15e659b52f312172f8815530",
|
||||
"sha256:a4cf9e803f2176b5de442763bd339b313d3f1ed3002e3e1eb6eec1d7c9bbc9b4"
|
||||
],
|
||||
"version": "==1.5.1"
|
||||
},
|
||||
"google-auth-httplib2": {
|
||||
"hashes": [
|
||||
"sha256:098fade613c25b4527b2c08fa42d11f3c2037dda8995d86de0745228e965d445",
|
||||
"sha256:f1c437842155680cf9918df9bc51c1182fda41feef88c34004bd1978c8157e08"
|
||||
],
|
||||
"version": "==0.0.3"
|
||||
},
|
||||
"httplib2": {
|
||||
"hashes": [
|
||||
"sha256:e71daed9a0e6373642db61166fa70beecc9bf04383477f84671348c02a04cbdf"
|
||||
],
|
||||
"version": "==0.11.3"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||
],
|
||||
"version": "==2.7"
|
||||
},
|
||||
"oauth2client": {
|
||||
"hashes": [
|
||||
"sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac",
|
||||
"sha256:d486741e451287f69568a4d26d70d9acd73a2bbfa275746c535b4209891cccc6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.1.3"
|
||||
},
|
||||
"pyasn1": {
|
||||
"hashes": [
|
||||
"sha256:b9d3abc5031e61927c82d4d96c1cec1e55676c1a991623cfed28faea73cdd7ca",
|
||||
"sha256:f58f2a3d12fd754aa123e9fa74fb7345333000a035f3921dbdaa08597aa53137"
|
||||
],
|
||||
"version": "==0.4.4"
|
||||
},
|
||||
"pyasn1-modules": {
|
||||
"hashes": [
|
||||
"sha256:a0cf3e1842e7c60fde97cb22d275eb6f9524f5c5250489e292529de841417547",
|
||||
"sha256:a38a8811ea784c0136abfdba73963876328f66172db21a05a82f9515909bfb4e"
|
||||
],
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"pyjsparser": {
|
||||
"hashes": [
|
||||
"sha256:e4a659df3db42a2ff9fbc961eb6d4076a0b945e1aadfc20d48f913ad5dca011d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
|
||||
"sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2018.5"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.19.1"
|
||||
},
|
||||
"rsa": {
|
||||
"hashes": [
|
||||
"sha256:14ba45700ff1ec9eeb206a2ce76b32814958a98e372006c8fb76ba820211be66",
|
||||
"sha256:1a836406405730121ae9823e19c6e806c62bbad73f890574fff50efa4122c487"
|
||||
],
|
||||
"version": "==4.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"uritemplate": {
|
||||
"hashes": [
|
||||
"sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd",
|
||||
"sha256:1b9c467a940ce9fb9f50df819e8ddd14696f89b9a8cc87ac77952ba416e0a8fd",
|
||||
"sha256:c02643cebe23fc8adb5e6becffe201185bf06c40bda5c0b4028a93f1527d011d"
|
||||
],
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version < '4'",
|
||||
"version": "==1.23"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:292fa429e69d60e4161e7612cb7cc8fa3609e2e309f80c224d93a76d5e7b58be",
|
||||
"sha256:c7013d119ec95eb626f7a2011f0b63d0c9a095df9ad06d8507b37084eada1a8d"
|
||||
],
|
||||
"version": "==2.0.4"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
|
||||
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
|
||||
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
|
||||
],
|
||||
"markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||
"version": "==4.3.4"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
"hashes": [
|
||||
"sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
|
||||
"sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
|
||||
"sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
|
||||
"sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
|
||||
"sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
|
||||
"sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
|
||||
"sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
|
||||
"sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
|
||||
"sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
|
||||
"sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
|
||||
"sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
|
||||
"sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
|
||||
"sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
|
||||
"sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
|
||||
"sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
|
||||
"sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
|
||||
"sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
|
||||
"sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
|
||||
"sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
|
||||
"sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
|
||||
"sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
|
||||
"sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
|
||||
"sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
|
||||
"sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
|
||||
"sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
|
||||
"sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
|
||||
"sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
|
||||
"sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
|
||||
"sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
|
||||
],
|
||||
"version": "==1.3.1"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"pylint": {
|
||||
"hashes": [
|
||||
"sha256:1d6d3622c94b4887115fe5204982eee66fdd8a951cf98635ee5caee6ec98c3ec",
|
||||
"sha256:31142f764d2a7cd41df5196f9933b12b7ee55e73ef12204b648ad7e556c119fb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:0948004fa228ae071054f5208840a1e88747a357ec1101c17217bfe99b299d58",
|
||||
"sha256:10703d3cec8dcd9eef5a630a04056bbc898abc19bac5691612acba7d1325b66d",
|
||||
"sha256:1f6c4bd0bdc0f14246fd41262df7dfc018d65bb05f6e16390b7ea26ca454a291",
|
||||
"sha256:25d8feefe27eb0303b73545416b13d108c6067b846b543738a25ff304824ed9a",
|
||||
"sha256:29464a177d56e4e055b5f7b629935af7f49c196be47528cc94e0a7bf83fbc2b9",
|
||||
"sha256:2e214b72168ea0275efd6c884b114ab42e316de3ffa125b267e732ed2abda892",
|
||||
"sha256:3e0d5e48e3a23e9a4d1a9f698e32a542a4a288c871d33ed8df1b092a40f3a0f9",
|
||||
"sha256:519425deca5c2b2bdac49f77b2c5625781abbaf9a809d727d3a5596b30bb4ded",
|
||||
"sha256:57fe287f0cdd9ceaf69e7b71a2e94a24b5d268b35df251a88fef5cc241bf73aa",
|
||||
"sha256:668d0cec391d9aed1c6a388b0d5b97cd22e6073eaa5fbaa6d2946603b4871efe",
|
||||
"sha256:68ba70684990f59497680ff90d18e756a47bf4863c604098f10de9716b2c0bdd",
|
||||
"sha256:6de012d2b166fe7a4cdf505eee3aaa12192f7ba365beeefaca4ec10e31241a85",
|
||||
"sha256:79b91ebe5a28d349b6d0d323023350133e927b4de5b651a8aa2db69c761420c6",
|
||||
"sha256:8550177fa5d4c1f09b5e5f524411c44633c80ec69b24e0e98906dd761941ca46",
|
||||
"sha256:898f818399cafcdb93cbbe15fc83a33d05f18e29fb498ddc09b0214cdfc7cd51",
|
||||
"sha256:94b091dc0f19291adcb279a108f5d38de2430411068b219f41b343c03b28fb1f",
|
||||
"sha256:a26863198902cda15ab4503991e8cf1ca874219e0118cbf07c126bce7c4db129",
|
||||
"sha256:a8034021801bc0440f2e027c354b4eafd95891b573e12ff0418dec385c76785c",
|
||||
"sha256:bc978ac17468fe868ee589c795d06777f75496b1ed576d308002c8a5756fb9ea",
|
||||
"sha256:c05b41bc1deade9f90ddc5d988fe506208019ebba9f2578c622516fd201f5863",
|
||||
"sha256:c9b060bd1e5a26ab6e8267fd46fc9e02b54eb15fffb16d112d4c7b1c12987559",
|
||||
"sha256:edb04bdd45bfd76c8292c4d9654568efaedf76fe78eb246dde69bdb13b2dad87",
|
||||
"sha256:f19f2a4f547505fe9072e15f6f4ae714af51b5a681a97f187971f50c283193b6"
|
||||
],
|
||||
"markers": "python_version < '3.7' and implementation_name == 'cpython'",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"wrapt": {
|
||||
"hashes": [
|
||||
"sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6"
|
||||
],
|
||||
"version": "==1.10.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
239
main.py
239
main.py
@@ -1,188 +1,185 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime, timezone
|
||||
from googleapiclient.discovery import build
|
||||
from httplib2 import Http
|
||||
from oauth2client import file, client, tools
|
||||
import pytz
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
# We don't store this url in the source, as it is sensitive
|
||||
URL = open("timetableurl").read().strip()
|
||||
# ------------------------------------------------------------
|
||||
# Config
|
||||
# ------------------------------------------------------------
|
||||
|
||||
RESPONSE_FILE = "response.txt"
|
||||
OLD_CALENDAR = "old_calendar.ics"
|
||||
NEW_CALENDAR = "calendar.ics"
|
||||
LOCAL_TZ = pytz.timezone("Europe/London")
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Parsing timetable JS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def parse_events(events_data):
|
||||
# Replace date objects with tuples, easier to parse
|
||||
# Replace JS date objects
|
||||
events_data = events_data.replace("new Date", "")
|
||||
|
||||
cleaned_data = ""
|
||||
|
||||
# Remove comments, properties to keys
|
||||
for line in events_data.split("\n"):
|
||||
comment_pos = line.find("//")
|
||||
if comment_pos != -1:
|
||||
line = line[:comment_pos]
|
||||
|
||||
|
||||
if ":" in line:
|
||||
line_values = line.split(":")
|
||||
line = "'" + line_values[0] + "': " + line_values[1]
|
||||
key, val = line.split(":", 1)
|
||||
line = f"'{key}': {val}"
|
||||
|
||||
cleaned_data += line + "\n"
|
||||
|
||||
# Parse the event, as if it were a dict
|
||||
parsed_data = eval(cleaned_data)
|
||||
|
||||
# Parse the datetime info
|
||||
for event in parsed_data:
|
||||
if "start" in event:
|
||||
event["start"] = list(event["start"])
|
||||
event["start"][1] += 1
|
||||
event["start"].append(0)
|
||||
event["start"] = datetime(*event["start"])
|
||||
event["start"] = pytz.timezone("Europe/London").localize(event["start"])
|
||||
s = list(event["start"])
|
||||
s[1] += 1
|
||||
s.append(0)
|
||||
event["start"] = LOCAL_TZ.localize(datetime(*s))
|
||||
|
||||
if "end" in event:
|
||||
event["end"] = list(event["end"])
|
||||
event["end"][1] += 1
|
||||
event["end"].append(0)
|
||||
event["end"] = datetime(*event["end"])
|
||||
event["end"] = pytz.timezone("Europe/London").localize(event["end"])
|
||||
e = list(event["end"])
|
||||
e[1] += 1
|
||||
e.append(0)
|
||||
event["end"] = LOCAL_TZ.localize(datetime(*e))
|
||||
|
||||
return parsed_data
|
||||
|
||||
|
||||
def get_events_data(url):
|
||||
page_data = requests.get(url).text
|
||||
def get_events_data_from_file(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
page_data = f.read()
|
||||
|
||||
soup = BeautifulSoup(page_data, features="html.parser")
|
||||
|
||||
source = ""
|
||||
for script in soup.head.findAll("script", {"type": "text/javascript"}):
|
||||
for script in soup.head.find_all("script", {"type": "text/javascript"}):
|
||||
if not script.has_attr("src"):
|
||||
source = script.text
|
||||
break
|
||||
else:
|
||||
raise RuntimeError("Could not find inline timetable script")
|
||||
|
||||
events_data = source.split("events:")[1].split("]")[0] +"]"
|
||||
return source.split("events:")[1].split("]")[0] + "]"
|
||||
|
||||
return events_data
|
||||
# ------------------------------------------------------------
|
||||
# ICS helpers
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def ics_time(dt):
|
||||
return dt.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||
|
||||
|
||||
def create_google_event(event):
|
||||
new_event = event.copy()
|
||||
|
||||
if not new_event:
|
||||
return new_event
|
||||
|
||||
# Make the event the correct format
|
||||
new_event["summary"] = event["moduleDesc"] + " - " + event["title"]
|
||||
new_event["description"] = event["lecturer"] + " - " + event["room"]
|
||||
|
||||
new_event["end"] = {"dateTime": str(event["end"].isoformat()), "timeZone": "Europe/London"}
|
||||
new_event["start"] = {"dateTime": str(event["start"].isoformat()), "timeZone": "Europe/London"}
|
||||
|
||||
new_event["reminders"] = {'useDefault': False,
|
||||
'overrides': [{'method': 'popup', 'minutes': 30}]}
|
||||
return new_event
|
||||
def make_uid(event):
|
||||
key = f"{event['moduleDesc']}|{event['title']}|{event['start'].isoformat()}"
|
||||
return hashlib.sha1(key.encode()).hexdigest() + "@timetable"
|
||||
|
||||
|
||||
# Read and write access
|
||||
SCOPES = "https://www.googleapis.com/auth/calendar"
|
||||
def create_ics_event(event):
|
||||
return {
|
||||
"uid": make_uid(event),
|
||||
"summary": f"{event['moduleDesc']} - {event['title']}",
|
||||
"description": f"{event['lecturer']} - {event['room']}",
|
||||
"start": event["start"],
|
||||
"end": event["end"],
|
||||
}
|
||||
|
||||
def get_calendar_service():
|
||||
'''
|
||||
Connect to the google calendar service, and return the
|
||||
service object
|
||||
'''
|
||||
# ------------------------------------------------------------
|
||||
# Load existing calendar (UIDs only)
|
||||
# ------------------------------------------------------------
|
||||
|
||||
store = file.Storage("token.json")
|
||||
creds = store.get()
|
||||
def load_existing_uids(path):
|
||||
uids = set()
|
||||
|
||||
# Run prompt to get the google credentials
|
||||
if not creds or creds.invalid:
|
||||
flow = client.flow_from_clientsecrets("redentials.json", SCOPES)
|
||||
creds = tools.run_flow(flow, store)
|
||||
if not os.path.exists(path):
|
||||
return uids
|
||||
|
||||
return build("calendar", "v3", http=creds.authorize(Http()))
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
current_uid = None
|
||||
cancelled = False
|
||||
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
|
||||
def execute_batch(service, commands):
|
||||
batch = service.new_batch_http_request()
|
||||
batch_count = 0
|
||||
if line == "BEGIN:VEVENT":
|
||||
current_uid = None
|
||||
cancelled = False
|
||||
|
||||
for command in commands:
|
||||
batch.add(command)
|
||||
batch_count += 1
|
||||
elif line.startswith("UID:"):
|
||||
current_uid = line[4:]
|
||||
|
||||
if batch_count > 999:
|
||||
batch.execute()
|
||||
elif line == "STATUS:CANCELLED":
|
||||
cancelled = True
|
||||
|
||||
batch = service.new_batch_http_request()
|
||||
batch_count = 0
|
||||
elif line == "END:VEVENT" and current_uid and not cancelled:
|
||||
uids.add(current_uid)
|
||||
|
||||
if batch_count > 0:
|
||||
batch.execute()
|
||||
return uids
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Main
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
type_to_color = {}
|
||||
# A queue of colors, where a color is removed when
|
||||
# when an event we have not seen before exists
|
||||
color_queue = list(range(0, 12, 3))
|
||||
events_js = get_events_data_from_file(RESPONSE_FILE)
|
||||
parsed_events = parse_events(events_js)
|
||||
|
||||
service = get_calendar_service()
|
||||
new_events = [
|
||||
create_ics_event(e)
|
||||
for e in parsed_events
|
||||
if e
|
||||
]
|
||||
|
||||
# Get a list of all events in the future
|
||||
results = service.events().list(timeMin=datetime.now().isoformat() + 'Z', calendarId='primary').execute()
|
||||
future_events = results.get("items", [])
|
||||
old_uids = load_existing_uids(OLD_CALENDAR)
|
||||
new_uids = {e["uid"] for e in new_events}
|
||||
|
||||
cov_events = parse_events(get_events_data(URL))
|
||||
new_events = []
|
||||
now = ics_time(datetime.now(timezone.utc))
|
||||
|
||||
new_summaries = set()
|
||||
lines = [
|
||||
"BEGIN:VCALENDAR",
|
||||
"VERSION:2.0",
|
||||
"PRODID:-//Timetable Sync//EN",
|
||||
"CALSCALE:GREGORIAN",
|
||||
]
|
||||
|
||||
for event in cov_events:
|
||||
if not event:
|
||||
continue
|
||||
# Add / update events
|
||||
for ev in new_events:
|
||||
lines.extend([
|
||||
"BEGIN:VEVENT",
|
||||
f"UID:{ev['uid']}",
|
||||
f"DTSTAMP:{now}",
|
||||
f"DTSTART:{ics_time(ev['start'])}",
|
||||
f"DTEND:{ics_time(ev['end'])}",
|
||||
f"SUMMARY:{ev['summary']}",
|
||||
f"DESCRIPTION:{ev['description']}",
|
||||
"END:VEVENT",
|
||||
])
|
||||
|
||||
new_event = create_google_event(event)
|
||||
# Cancel removed events
|
||||
for uid in old_uids - new_uids:
|
||||
lines.extend([
|
||||
"BEGIN:VEVENT",
|
||||
f"UID:{uid}",
|
||||
f"DTSTAMP:{now}",
|
||||
"STATUS:CANCELLED",
|
||||
"END:VEVENT",
|
||||
])
|
||||
|
||||
color_type = new_event["mainColor"]
|
||||
lines.append("END:VCALENDAR")
|
||||
|
||||
if color_type in type_to_color:
|
||||
colorId = type_to_color[color_type]
|
||||
else:
|
||||
colorId = color_queue.pop(0)
|
||||
color_queue.append(colorId)
|
||||
type_to_color[color_type] = colorId
|
||||
|
||||
new_event["colorId"] = colorId
|
||||
|
||||
new_events.append(new_event)
|
||||
new_summaries.add(new_event["summary"])
|
||||
|
||||
# Make sure we remove old events so as not to create duplicates
|
||||
if not future_events:
|
||||
print('No existing events found')
|
||||
else:
|
||||
deletes = []
|
||||
for existing_event in future_events:
|
||||
if "summary" in existing_event and existing_event["summary"] in new_summaries:
|
||||
deletes.append(service.events()
|
||||
.delete(calendarId='primary',
|
||||
eventId=existing_event['id']))
|
||||
|
||||
|
||||
print(f'Removing {len(deletes)} existing events')
|
||||
execute_batch(service, deletes)
|
||||
|
||||
inserts = []
|
||||
for new_event in new_events:
|
||||
inserts.append(service.events()
|
||||
.insert(body=new_event,
|
||||
calendarId='primary'))
|
||||
print(f"Inserting {len(inserts)} new events")
|
||||
execute_batch(service, inserts)
|
||||
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}")
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user