From b40d0a54859efba46b82d416f215784d263c55a7 Mon Sep 17 00:00:00 2001 From: Ugric Date: Tue, 20 Jan 2026 02:34:25 +0000 Subject: [PATCH] change to create an ics file instead of using google calender --- .gitignore | 6 +- Pipfile | 18 ---- Pipfile.lock | 275 --------------------------------------------------- main.py | 251 +++++++++++++++++++++++----------------------- 4 files changed, 127 insertions(+), 423 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock diff --git a/.gitignore b/.gitignore index ba17f27..c8a2a55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -credentials.json -token.json -timetableurl +response.txt +calendar.ics +old_calendar.ics # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 33346a0..0000000 --- a/Pipfile +++ /dev/null @@ -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" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index e973df1..0000000 --- a/Pipfile.lock +++ /dev/null @@ -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" - } - } -} diff --git a/main.py b/main.py index a3b4a53..b096ea7 100644 --- a/main.py +++ b/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] - - cleaned_data += line + "\n" + 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 - - events_data = source.split("events:")[1].split("]")[0] +"]" + else: + raise RuntimeError("Could not find inline timetable script") - return events_data + 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") -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 - - if batch_count > 999: - batch.execute() + elif line.startswith("UID:"): + current_uid = line[4:] - batch = service.new_batch_http_request() - batch_count = 0 - - if batch_count > 0: - batch.execute() + elif line == "STATUS:CANCELLED": + cancelled = True + elif line == "END:VEVENT" and current_uid and not cancelled: + uids.add(current_uid) + + 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"] - - 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 + lines.append("END:VCALENDAR") - new_event["colorId"] = colorId + with open(NEW_CALENDAR, "w", encoding="utf-8") as f: + f.write("\n".join(lines)) - 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) - + print(f"Added / updated: {len(new_events)}") + print(f"Removed: {len(old_uids - new_uids)}") + print(f"Wrote {NEW_CALENDAR}") +# ------------------------------------------------------------ if __name__ == "__main__": main()