Added function to get the airing schedule of a batch of animes and cache
This commit is contained in:
parent
0dbd109d8c
commit
5140730189
3
.gitignore
vendored
3
.gitignore
vendored
@ -220,5 +220,6 @@ __marimo__/
|
||||
.streamlit/secrets.toml
|
||||
|
||||
# Anical
|
||||
airingdata.json
|
||||
listdata.json
|
||||
config.toml
|
||||
data.json
|
||||
@ -2,5 +2,8 @@
|
||||
userId = 8283179
|
||||
mediaType = "ANIME"
|
||||
|
||||
[anical]
|
||||
listNames = ["Completed", "Planning", "Watching", "Rewatching", "Paused"]
|
||||
|
||||
[ical]
|
||||
# reserved for future use
|
||||
190
main.py
190
main.py
@ -16,7 +16,7 @@ def getMediaListCollection(
|
||||
mediaType=config["anilist"]["mediaType"],
|
||||
userId=config["anilist"]["userId"],
|
||||
*,
|
||||
cachePath="data.json"
|
||||
cachePath="listdata.json"
|
||||
):
|
||||
"""
|
||||
Returns a tuple of (entries, cached)
|
||||
@ -62,7 +62,7 @@ def getMediaListCollection(
|
||||
|
||||
if not cacheExpired and not parametersChanged:
|
||||
print("Cache is fresh, using cached data...")
|
||||
return (entries["response"], True)
|
||||
return (entries, True)
|
||||
|
||||
if cacheExpired:
|
||||
print("Cache has gone stale, refreshing...")
|
||||
@ -147,9 +147,193 @@ def getMediaListCollection(
|
||||
|
||||
return (form, False)
|
||||
|
||||
def getAiringScheduleBatch(
|
||||
animeIds,
|
||||
*,
|
||||
ttl=TTL,
|
||||
cachePath="airingdata.json",
|
||||
maxRetries=5
|
||||
):
|
||||
"""
|
||||
Returns a tuple of (entries, cached)
|
||||
|
||||
Entries is a dictionary of the values to be written to the cache file, such as:
|
||||
- timestamp
|
||||
- ttl
|
||||
- params supplied
|
||||
- response
|
||||
|
||||
response is a dictionary of mediaId (as a string) -> {"title": {"romaji", "english"}, "schedule": [upcoming airing schedule items]}
|
||||
|
||||
cached is a boolean indicating whether the cache file was used or not
|
||||
|
||||
Takes the following arguments:
|
||||
- animeIds: list of AniList media ids to fetch airing schedules for
|
||||
- ttl: seconds the cache stays fresh for. Only applied when the cache is refreshed;
|
||||
an existing cache keeps the ttl it was written with (defaults to value from config.toml)
|
||||
- cachePath: the path to the cache file (if not supplied, defaults to "airingdata.json")
|
||||
- maxRetries: how many times in a row to retry after a 429 before giving up (defaults to 5)
|
||||
"""
|
||||
|
||||
if not animeIds:
|
||||
raise RuntimeError("animeIds must not be empty")
|
||||
|
||||
animeIds = sorted(set(int(i) for i in animeIds))
|
||||
|
||||
if path.exists(cachePath):
|
||||
with open(cachePath, "r") as f:
|
||||
entries = json.load(f)
|
||||
|
||||
if (
|
||||
"timestamp" in entries
|
||||
and "ttl" in entries
|
||||
and "response" in entries
|
||||
and "params" in entries
|
||||
):
|
||||
timestamp = float(entries["timestamp"])
|
||||
cacheTtl = float(entries["ttl"])
|
||||
|
||||
cacheIds = entries["params"].get("animeIds")
|
||||
|
||||
cacheExpired = time.time() - timestamp > cacheTtl
|
||||
parametersChanged = cacheIds != animeIds
|
||||
|
||||
if not cacheExpired and not parametersChanged:
|
||||
print("Airing cache is fresh, using cached data...")
|
||||
return (entries, True)
|
||||
|
||||
if cacheExpired:
|
||||
print("Airing cache has gone stale, refreshing...")
|
||||
elif parametersChanged:
|
||||
print("Airing cache parameters changed, refreshing...")
|
||||
else:
|
||||
print("Airing cache is invalid, refreshing...")
|
||||
|
||||
query = """
|
||||
query ($ids: [Int], $page: Int) {
|
||||
Page(page: $page, perPage: 50) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
currentPage
|
||||
}
|
||||
airingSchedules(mediaId_in: $ids, notYetAired: true, sort: TIME) {
|
||||
id
|
||||
airingAt
|
||||
timeUntilAiring
|
||||
episode
|
||||
mediaId
|
||||
media {
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
schedules = {}
|
||||
page = 1
|
||||
retries = 0
|
||||
|
||||
while True:
|
||||
variables = {
|
||||
"ids": animeIds,
|
||||
"page": page,
|
||||
}
|
||||
|
||||
resp = requests.post(
|
||||
"https://graphql.anilist.co",
|
||||
json={
|
||||
"query": query,
|
||||
"variables": variables,
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
# retry if rate limited
|
||||
if resp.status_code == 429:
|
||||
retries += 1
|
||||
if retries > maxRetries:
|
||||
raise RuntimeError("Rate limited too many times, giving up")
|
||||
wait = int(resp.headers.get("Retry-After", 60))
|
||||
print(f"Rate limited, waiting {wait}s...")
|
||||
time.sleep(wait)
|
||||
continue
|
||||
|
||||
retries = 0
|
||||
resp.raise_for_status()
|
||||
|
||||
data = resp.json()
|
||||
|
||||
if "errors" in data:
|
||||
raise RuntimeError(data["errors"])
|
||||
|
||||
pageData = data["data"]["Page"]
|
||||
|
||||
for item in pageData["airingSchedules"]:
|
||||
# media can be null, fall back to empty titles
|
||||
media = item.pop("media", None) or {}
|
||||
title = media.get("title") or {}
|
||||
|
||||
entry = schedules.setdefault(
|
||||
str(item["mediaId"]),
|
||||
{
|
||||
"title": {
|
||||
"romaji": title.get("romaji"),
|
||||
"english": title.get("english"),
|
||||
},
|
||||
"schedule": [],
|
||||
},
|
||||
)
|
||||
entry["schedule"].append(item)
|
||||
|
||||
if not pageData["pageInfo"]["hasNextPage"]:
|
||||
break
|
||||
|
||||
page += 1
|
||||
|
||||
form = {
|
||||
"timestamp": str(time.time()),
|
||||
"ttl": str(ttl),
|
||||
"params": {
|
||||
"animeIds": animeIds,
|
||||
},
|
||||
"response": schedules
|
||||
}
|
||||
|
||||
return (form, False)
|
||||
|
||||
if __name__ == "__main__":
|
||||
entries, cached = getMediaListCollection()
|
||||
|
||||
if not cached:
|
||||
with open("data.json", "w") as f:
|
||||
with open("listdata.json", "w") as f:
|
||||
f.write(f"{json.dumps(entries, indent=2)}")
|
||||
|
||||
lists = entries["response"]
|
||||
|
||||
print("\nList Names updating for:")
|
||||
for li in config["anical"]["listNames"]:
|
||||
print(li)
|
||||
|
||||
# get a list of all the animes ids in these lists:
|
||||
animeIds = []
|
||||
for li in config["anical"]["listNames"]:
|
||||
if li not in lists:
|
||||
print(f"Warning: list '{li}' not found, skipping")
|
||||
continue
|
||||
for entry in lists[li]:
|
||||
animeIds.append(entry["media"]["id"])
|
||||
|
||||
print(f"\nFound {len(animeIds)} anime ids")
|
||||
|
||||
airing, airingCached = getAiringScheduleBatch(animeIds)
|
||||
|
||||
if not airingCached:
|
||||
with open("airingdata.json", "w") as f:
|
||||
f.write(f"{json.dumps(airing, indent=2)}")
|
||||
|
||||
schedules = airing["response"]
|
||||
print(f"\nGot upcoming episodes for {len(schedules)} anime")
|
||||
Loading…
Reference in New Issue
Block a user