Plex-Meta-Manager/modules/anilist.py

226 lines
10 KiB
Python
Raw Normal View History

2021-07-14 14:47:20 +00:00
import logging, time
2021-03-10 16:51:27 +00:00
from modules import util
from modules.util import Failed
logger = logging.getLogger("Plex Meta Manager")
2021-03-30 05:50:53 +00:00
builders = [
2021-07-23 19:44:21 +00:00
"anilist_genre", "anilist_id", "anilist_popular", "anilist_relations",
"anilist_season", "anilist_studio", "anilist_tag", "anilist_top_rated"
2021-03-30 05:50:53 +00:00
]
2021-07-23 19:44:21 +00:00
pretty_names = {"score": "Average Score", "popular": "Popularity"}
search_translation = {
"season": "MediaSeason", "seasonYear": "Int", "isAdult": "Boolean",
"startDate_greater": "FuzzyDateInt", "startDate_lesser": "FuzzyDateInt", "endDate_greater": "FuzzyDateInt", "endDate_lesser": "FuzzyDateInt",
"format_in": "[MediaFormat]", "format_not_in": "[MediaFormat]", "status_in": "[MediaStatus]", "status_not_in": "[MediaStatus]",
"episodes_greater": "Int", "episodes_lesser": "Int", "duration_greater": "Int", "duration_lesser": "Int",
"genre_in": "[String]", "genre_not_in": "[String]", "tag_in": "[String]", "tag_not_in": "[String]",
"averageScore_greater": "Int", "averageScore_lesser": "Int", "popularity_greater": "Int", "popularity_lesser": "Int"
}
2021-07-14 14:47:20 +00:00
base_url = "https://graphql.anilist.co"
tag_query = "query{MediaTagCollection {name, category}}"
2021-05-07 19:53:54 +00:00
genre_query = "query{GenreCollection}"
2021-03-30 05:50:53 +00:00
2021-06-14 15:24:11 +00:00
class AniList:
2021-03-10 16:51:27 +00:00
def __init__(self, config):
self.config = config
self.tags = {}
self.categories = {}
for media_tag in self._request(tag_query, {})["data"]["MediaTagCollection"]:
self.tags[media_tag["name"].lower().replace(" ", "-")] = media_tag["name"]
self.categories[media_tag["category"].lower().replace(" ", "-")] = media_tag["category"]
self.genres = {g.lower().replace(" ", "-"): g for g in self._request(genre_query, {})["data"]["GenreCollection"]}
2021-03-10 16:51:27 +00:00
2021-08-14 03:32:26 +00:00
def _request(self, query, variables, level=1):
2021-07-14 14:47:20 +00:00
response = self.config.post(base_url, json={"query": query, "variables": variables})
2021-03-10 16:51:27 +00:00
json_obj = response.json()
if "errors" in json_obj:
if json_obj['errors'][0]['message'] == "Too Many Requests.":
2021-08-14 03:32:26 +00:00
wait_time = int(response.headers["Retry-After"]) if "Retry-After" in response.headers else 0
time.sleep(wait_time if wait_time > 0 else 10)
if level < 6:
return self._request(query, variables, level=level + 1)
raise Failed(f"AniList Error: Connection Failed")
2021-03-10 16:51:27 +00:00
else:
raise Failed(f"AniList Error: {json_obj['errors'][0]['message']}")
else:
2021-08-14 03:32:26 +00:00
time.sleep(60 / 90)
2021-03-10 16:51:27 +00:00
return json_obj
def _validate_id(self, anilist_id):
2021-04-15 20:45:35 +00:00
query = "query ($id: Int) {Media(id: $id) {id title{romaji english}}}"
2021-05-07 19:53:54 +00:00
media = self._request(query, {"id": anilist_id})["data"]["Media"]
2021-04-15 20:45:35 +00:00
if media["id"]:
return media["id"], media["title"]["english" if media["title"]["english"] else "romaji"]
raise Failed(f"AniList Error: No AniList ID found for {anilist_id}")
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _pagenation(self, query, limit=0, variables=None):
2021-04-15 20:45:35 +00:00
anilist_ids = []
2021-03-10 16:51:27 +00:00
count = 0
page_num = 0
if variables is None:
2021-03-17 13:49:34 +00:00
variables = {}
2021-03-10 16:51:27 +00:00
next_page = True
while next_page:
page_num += 1
2021-03-17 13:49:34 +00:00
variables["page"] = page_num
2021-05-07 19:53:54 +00:00
json_obj = self._request(query, variables)
2021-03-10 16:51:27 +00:00
next_page = json_obj["data"]["Page"]["pageInfo"]["hasNextPage"]
for media in json_obj["data"]["Page"]["media"]:
2021-04-15 20:45:35 +00:00
if media["id"]:
anilist_ids.append(media["id"])
2021-03-10 16:51:27 +00:00
count += 1
if 0 < limit == count:
break
if 0 < limit == count:
break
2021-04-15 20:45:35 +00:00
return anilist_ids
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _top_rated(self, limit):
return self._search(limit=limit, averageScore_greater=3)
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _popular(self, limit):
return self._search(sort="popular", limit=limit, popularity_greater=1000)
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _season(self, season, year, sort, limit):
return self._search(sort=sort, limit=limit, season=season.upper(), year=year)
def _search(self, sort="score", limit=0, **kwargs):
query_vars = "$page: Int, $sort: [MediaSort]"
media_vars = "sort: $sort, type: ANIME"
variables = {"sort": "SCORE_DESC" if sort == "score" else "POPULARITY_DESC"}
for key, value in kwargs.items():
query_vars += f", ${key}: {search_translation[key]}"
media_vars += f", {key}: ${key}"
variables[key] = value
query = f"query ({query_vars}) {{Page(page: $page){{pageInfo {{hasNextPage}}media({media_vars}){{id}}}}}}"
logger.info(query)
2021-05-07 19:53:54 +00:00
return self._pagenation(query, limit=limit, variables=variables)
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _genre(self, genre, sort, limit):
return self._search(sort=sort, limit=limit, genre=genre)
2021-05-07 19:53:54 +00:00
def _tag(self, tag, sort, limit):
return self._search(sort=sort, limit=limit, tag=tag)
2021-05-07 19:53:54 +00:00
def _studio(self, studio_id):
2021-03-10 16:51:27 +00:00
query = """
query ($page: Int, $id: Int) {
Studio(id: $id) {
name
media(page: $page) {
2021-04-15 20:45:35 +00:00
nodes {id type}
2021-03-10 16:51:27 +00:00
pageInfo {hasNextPage}
}
}
}
"""
2021-04-15 20:45:35 +00:00
anilist_ids = []
2021-03-10 16:51:27 +00:00
page_num = 0
next_page = True
name = None
while next_page:
page_num += 1
2021-05-07 19:53:54 +00:00
json_obj = self._request(query, {"id": studio_id, "page": page_num})
2021-03-10 16:51:27 +00:00
if not name:
name = json_obj["data"]["Studio"]["name"]
next_page = json_obj["data"]["Studio"]["media"]["pageInfo"]["hasNextPage"]
for media in json_obj["data"]["Studio"]["media"]["nodes"]:
2021-04-15 20:45:35 +00:00
if media["id"] and media["type"] == "ANIME":
anilist_ids.append(media["id"])
return anilist_ids, name
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _relations(self, anilist_id, ignore_ids=None):
2021-03-10 16:51:27 +00:00
query = """
query ($id: Int) {
Media(id: $id) {
2021-04-15 20:45:35 +00:00
id
2021-03-10 16:51:27 +00:00
relations {
2021-04-15 20:45:35 +00:00
edges {node{id type} relationType}
nodes {id type}
2021-03-10 16:51:27 +00:00
}
}
}
"""
2021-04-15 20:45:35 +00:00
new_anilist_ids = []
2021-03-10 16:51:27 +00:00
anilist_ids = []
name = ""
if not ignore_ids:
ignore_ids = [anilist_id]
anilist_id, name = self._validate_id(anilist_id)
2021-04-15 20:45:35 +00:00
anilist_ids.append(anilist_id)
2021-05-07 19:53:54 +00:00
json_obj = self._request(query, {"id": anilist_id})
2021-03-10 16:51:27 +00:00
edges = [media["node"]["id"] for media in json_obj["data"]["Media"]["relations"]["edges"]
if media["relationType"] not in ["CHARACTER", "OTHER"] and media["node"]["type"] == "ANIME"]
for media in json_obj["data"]["Media"]["relations"]["nodes"]:
2021-04-15 20:45:35 +00:00
if media["id"] and media["id"] not in ignore_ids and media["id"] in edges and media["type"] == "ANIME":
new_anilist_ids.append(media["id"])
2021-03-10 16:51:27 +00:00
ignore_ids.append(media["id"])
2021-04-15 20:45:35 +00:00
anilist_ids.append(media["id"])
2021-03-10 16:51:27 +00:00
2021-04-15 20:45:35 +00:00
for next_id in new_anilist_ids:
2021-05-07 19:53:54 +00:00
new_relation_ids, ignore_ids, _ = self._relations(next_id, ignore_ids=ignore_ids)
2021-04-15 20:45:35 +00:00
anilist_ids.extend(new_relation_ids)
2021-03-10 16:51:27 +00:00
2021-04-15 20:45:35 +00:00
return anilist_ids, ignore_ids, name
2021-03-10 16:51:27 +00:00
def validate_tag(self, tag):
return self._validate(tag, self.tags, "Tag")
def validate_category(self, category):
return self._validate(category, self.categories, "Category")
def validate_genre(self, genre):
return self._validate(genre, self.genres, "Genre")
def _validate(self, data, options, name):
data_check = data.lower().replace(" / ", "-").replace(" ", "-")
if data_check in options:
return options[data_check]
raise Failed(f"AniList Error: {name}: {data} does not exist\nOptions: {', '.join([v for k, v in options.items()])}")
2021-03-10 16:51:27 +00:00
def validate_anilist_ids(self, anilist_ids, studio=False):
2021-07-21 17:40:05 +00:00
anilist_id_list = util.get_int_list(anilist_ids, "AniList ID")
2021-03-10 16:51:27 +00:00
anilist_values = []
query = f"query ($id: Int) {{{'Studio(id: $id) {name}' if studio else 'Media(id: $id) {id}'}}}"
2021-07-26 19:03:17 +00:00
for anilist_id in anilist_id_list:
2021-03-10 16:51:27 +00:00
try:
2021-05-07 19:53:54 +00:00
self._request(query, {"id": anilist_id})
2021-03-10 16:51:27 +00:00
anilist_values.append(anilist_id)
except Failed as e: logger.error(e)
if len(anilist_values) > 0:
return anilist_values
raise Failed(f"AniList Error: No valid AniList IDs in {anilist_ids}")
2021-08-07 06:01:21 +00:00
def get_anilist_ids(self, method, data):
2021-03-10 16:51:27 +00:00
if method == "anilist_id":
2021-08-01 04:35:42 +00:00
logger.info(f"Processing AniList ID: {data}")
anilist_id, name = self._validate_id(data)
2021-04-15 20:45:35 +00:00
anilist_ids = [anilist_id]
2021-08-01 04:35:42 +00:00
elif method == "anilist_popular":
logger.info(f"Processing AniList Popular: {data} Anime")
anilist_ids = self._popular(data)
elif method == "anilist_top_rated":
logger.info(f"Processing AniList Top Rated: {data} Anime")
anilist_ids = self._top_rated(data)
2021-03-10 16:51:27 +00:00
elif method == "anilist_season":
2021-08-01 04:35:42 +00:00
logger.info(f"Processing AniList Season: {data['limit'] if data['limit'] > 0 else 'All'} Anime from {util.pretty_seasons[data['season']]} {data['year']} sorted by {pretty_names[data['sort_by']]}")
2021-05-07 19:53:54 +00:00
anilist_ids = self._season(data["season"], data["year"], data["sort_by"], data["limit"])
elif method == "anilist_genre":
2021-08-01 04:35:42 +00:00
logger.info(f"Processing AniList Genre: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Genre: {data['genre']} sorted by {pretty_names[data['sort_by']]}")
2021-05-07 19:53:54 +00:00
anilist_ids = self._genre(data["genre"], data["sort_by"], data["limit"])
elif method == "anilist_tag":
2021-08-01 04:35:42 +00:00
logger.info(f"Processing AniList Tag: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Tag: {data['tag']} sorted by {pretty_names[data['sort_by']]}")
2021-05-07 19:53:54 +00:00
anilist_ids = self._tag(data["tag"], data["sort_by"], data["limit"])
2021-08-01 04:35:42 +00:00
elif method == "anilist_studio":
anilist_ids, name = self._studio(data)
logger.info(f"Processing AniList Studio: ({data}) {name} ({len(anilist_ids)} Anime)")
elif method == "anilist_relations":
anilist_ids, _, name = self._relations(data)
logger.info(f"Processing AniList Relations: ({data}) {name} ({len(anilist_ids)} Anime)")
2021-03-10 16:51:27 +00:00
else:
raise Failed(f"AniList Error: Method {method} not supported")
2021-05-24 03:38:46 +00:00
logger.debug("")
2021-07-03 01:47:09 +00:00
logger.debug(f"{len(anilist_ids)} AniList IDs Found: {anilist_ids}")
2021-08-07 06:01:21 +00:00
return anilist_ids