diff --git a/.gitignore b/.gitignore index 6d8e1e8..9d7ed80 100644 --- a/.gitignore +++ b/.gitignore @@ -223,3 +223,4 @@ __marimo__/ airingdata.json listdata.json config.toml +anime.ics diff --git a/example.config.toml b/example.config.toml index c55829e..409f9ab 100644 --- a/example.config.toml +++ b/example.config.toml @@ -4,6 +4,3 @@ mediaType = "ANIME" [anical] listNames = ["Completed", "Planning", "Watching", "Rewatching", "Paused"] - -[ical] -# reserved for future use \ No newline at end of file diff --git a/main.py b/main.py index e90b28d..1054ed9 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,8 @@ import time import tomllib from os import path import json +from icalendar import Calendar, Event +from datetime import datetime, timezone if path.exists("config.toml"): with open("config.toml", "rb") as f: @@ -305,6 +307,47 @@ def getAiringScheduleBatch( return (form, False) +def createIcal( + schedules, + *, + name="Anime Airing Schedule", + duration=24 * 60 +): + """ + Returns the .ics file contents (bytes) for every upcoming episode in schedules. + + Takes the following arguments: + - schedules: the "response" dict from getAiringScheduleBatch (mediaId -> {"title", "schedule"}) + - name: the calendar's display name (defaults to "Anime Airing Schedule") + - duration: event length in seconds (defaults to 24 minutes) + """ + + cal = Calendar() + cal.add("prodid", "-//anical//AniList airing schedule//EN") + cal.add("version", "2.0") + cal.add("x-wr-calname", name) # display name in Google/Apple Calendar + + now = time.time() + stamp = datetime.now(timezone.utc) + + for mediaId, anime in schedules.items(): + title = anime["title"].get("english") or anime["title"].get("romaji") or f"Anime {mediaId}" + + for item in anime["schedule"]: + # the cache can be older than some of its episodes, so skip anything already aired + if item["airingAt"] <= now: + continue + + ev = Event() + ev.add("uid", f"anilist-{item['id']}@anilist.co") + ev.add("summary", f"{title} - Episode {item['episode']}") + ev.add("dtstart", datetime.fromtimestamp(item["airingAt"], timezone.utc)) + ev.add("dtend", datetime.fromtimestamp(item["airingAt"] + duration, timezone.utc)) + ev.add("dtstamp", stamp) + cal.add_component(ev) + + return cal.to_ical() # bytes + if __name__ == "__main__": entries, cached = getMediaListCollection() @@ -336,4 +379,7 @@ if __name__ == "__main__": f.write(f"{json.dumps(airing, indent=2)}") schedules = airing["response"] - print(f"\nGot upcoming episodes for {len(schedules)} anime") \ No newline at end of file + print(f"\nGot upcoming episodes for {len(schedules)} anime") + + with open("anime.ics", "wb") as f: + f.write(createIcal(schedules)) \ No newline at end of file