diff --git a/modules/anilist.py b/modules/anilist.py index 45377cb5..2109197d 100644 --- a/modules/anilist.py +++ b/modules/anilist.py @@ -4,9 +4,12 @@ from modules.util import Failed logger = logging.getLogger("Plex Meta Manager") -builders = ["anilist_id", "anilist_popular", "anilist_relations", "anilist_studio", "anilist_top_rated", "anilist_search"] -pretty_names = {"score": "Average Score", "popular": "Popularity"} -attr_translation = {"year": "seasonYear", "adult": "isAdult", "start": "startDate", "end": "endDate", "tag_category": "tagCategory", "score": "averageScore", "min_tag_percent": "minimumTagRank"} +builders = ["anilist_id", "anilist_popular", "anilist_trending", "anilist_relations", "anilist_studio", "anilist_top_rated", "anilist_search"] +pretty_names = {"score": "Average Score", "popular": "Popularity", "trending": "Trending"} +attr_translation = { + "year": "seasonYear", "adult": "isAdult", "start": "startDate", "end": "endDate", "tag_category": "tagCategory", + "score": "averageScore", "min_tag_percent": "minimumTagRank", "country": "countryOfOrigin", +} mod_translation = {"": "in", "not": "not_in", "before": "greater", "after": "lesser", "gt": "greater", "gte": "greater", "lt": "lesser", "lte": "lesser"} mod_searches = [ "start.before", "start.after", "end.before", "end.after", @@ -14,20 +17,41 @@ mod_searches = [ "episodes.gt", "episodes.gte", "episodes.lt", "episodes.lte", "duration.gt", "duration.gte", "duration.lt", "duration.lte", "score.gt", "score.gte", "score.lt", "score.lte", "popularity.gt", "popularity.gte", "popularity.lt", "popularity.lte" ] -no_mod_searches = ["search", "season", "year", "adult", "min_tag_percent", "limit", "sort_by"] +no_mod_searches = ["search", "season", "year", "adult", "min_tag_percent", "limit", "sort_by", "source", "country"] searches = mod_searches + no_mod_searches +sort_options = {"score": "SCORE_DESC", "popular": "POPULARITY_DESC", "trending": "TRENDING_DESC"} search_types = { "search": "String", "season": "MediaSeason", "seasonYear": "Int", "isAdult": "Boolean", "minimumTagRank": "Int", "startDate": "FuzzyDateInt", "endDate": "FuzzyDateInt", "format": "[MediaFormat]", "status": "[MediaStatus]", - "genre": "[String]", "tag": "[String]", "tagCategory": "[String]", - "episodes": "Int", "duration": "Int", "averageScore": "Int", "popularity": "Int" + "genre": "[String]", "tag": "[String]", "tagCategory": "[String]", "source": "MediaSource", + "countryOfOrigin": "CountryCode", "episodes": "Int", "duration": "Int", "averageScore": "Int", "popularity": "Int" } media_season = {"winter": "WINTER", "spring": "SPRING", "summer": "SUMMER", "fall": "FALL"} media_format = {"tv": "TV", "short": "TV_SHORT", "movie": "MOVIE", "special": "SPECIAL", "ova": "OVA", "ona": "ONA", "music": "MUSIC"} media_status = {"finished": "FINISHED", "airing": "RELEASING", "not_yet_aired": "NOT_YET_RELEASED", "cancelled": "CANCELLED", "hiatus": "HIATUS"} +media_source = { + "original": "ORIGINAL", "manga": "MANGA", "light_novel": "LIGHT_NOVEL", "visual_novel": "VISUAL_NOVEL", + "video_game": "VIDEO_GAME", "other": "OTHER", "novel": "NOVEL", "doujinshi": "DOUJINSHI", "anime": "ANIME" +} base_url = "https://graphql.anilist.co" tag_query = "query{MediaTagCollection {name, category}}" genre_query = "query{GenreCollection}" +country_codes = [ + "af", "ax", "al", "dz", "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "au", "at", "az", "bs", "bh", "bd", + "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "bq", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "cv", + "kh", "cm", "ca", "ky", "cf", "td", "cl", "cn", "cx", "cc", "co", "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", + "cw", "cy", "cz", "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "sz", "et", "fk", "fo", "fj", "fi", + "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg", "gn", "gw", + "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", + "jo", "kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly", "li", "lt", "lu", "mo", "mg", + "mw", "my", "mv", "ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "me", "ms", "ma", "mz", + "mm", "na", "nr", "np", "nl", "nc", "nz", "ni", "ne", "ng", "nu", "nf", "mk", "mp", "no", "om", "pk", "pw", "ps", + "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re", "ro", "ru", "rw", "bl", "sh", "kn", "lc", "mf", + "pm", "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sx", "sk", "si", "sb", "so", "za", "gs", "ss", + "es", "lk", "sd", "sr", "sj", "se", "ch", "sy", "tw", "tj", "tz", "th", "tl", "tg", "tk", "to", "tt", "tn", "tr", + "tm", "tc", "tv", "ug", "ua", "ae", "gb", "us", "um", "uy", "uz", "vu", "ve", "vn", "vg", "vi", "wf", "eh", "ye", + "zm", "zw", +] class AniList: def __init__(self, config): @@ -35,7 +59,8 @@ class AniList: self.options = { "Tag": {}, "Tag Category": {}, "Genre": {g.lower().replace(" ", "-"): g for g in self._request(genre_query, {})["data"]["GenreCollection"]}, - "Season": media_season, "Format": media_format, "Status": media_status + "Country": {c: c.upper() for c in country_codes}, + "Season": media_season, "Format": media_format, "Status": media_status, "Source": media_source, } for media_tag in self._request(tag_query, {})["data"]["MediaTagCollection"]: self.options["Tag"][media_tag["name"].lower().replace(" ", "-")] = media_tag["name"] @@ -89,7 +114,7 @@ class AniList: def _search(self, **kwargs): query_vars = "$page: Int, $sort: [MediaSort]" media_vars = "sort: $sort, type: ANIME" - variables = {"sort": "SCORE_DESC" if kwargs['sort_by'] == "score" else "POPULARITY_DESC"} + variables = {"sort": sort_options[kwargs['sort_by']]} for key, value in kwargs.items(): if key not in ["sort_by", "limit"]: if "." in key: @@ -101,8 +126,10 @@ class AniList: final = ani_attr if attr in no_mod_searches else f"{ani_attr}_{mod_translation[mod]}" if attr in ["start", "end"]: value = int(util.validate_date(value, f"anilist_search {key}", return_as="%Y%m%d")) - elif attr in ["season", "format", "status", "genre", "tag", "tag_category"]: + elif attr in ["format", "status", "genre", "tag", "tag_category"]: value = [self.options[attr.replace("_", " ").title()][v.lower().replace(" / ", "-").replace(" ", "-")] for v in value] + elif attr in ["season", "source", "country"]: + value = self.options[attr.replace("_", " ").title()][value] if mod == "gte": value -= 1 elif mod == "lte": @@ -211,20 +238,24 @@ class AniList: else: if method == "anilist_popular": data = {"limit": data, "popularity.gt": 3, "sort_by": "popular"} + elif method == "anilist_trending": + data = {"limit": data, "sort_by": "trending"} elif method == "anilist_top_rated": data = {"limit": data, "score.gt": 3, "sort_by": "score"} elif method not in builders: raise Failed(f"AniList Error: Method {method} not supported") - message = f"Processing {method.replace('_', ' ').title().replace('Anilist', 'AniList')}:\nSort By: {pretty_names[data['sort_by']]}" + message = f"Processing {method.replace('_', ' ').title().replace('Anilist', 'AniList')}:\n\tSort By {pretty_names[data['sort_by']]}" if data['limit'] > 0: - message += f"\nLimit: {data['limit']}" + message += f"\n\tLimit to {data['limit']} Anime" for key, value in data.items(): - if "." in key: - attr, mod = key.split(".") - else: - attr = key - mod = "" - message += f"\n{attr.replace('_', ' ').title()} {util.mod_displays[mod]} {value}" + if key not in ["limit", "sort_by"]: + if "." in key: + attr, mod = key.split(".") + mod = f".{mod}" + else: + attr = key + mod = "" + message += f"\n\t{attr.replace('_', ' ').title()} {util.mod_displays[mod]} {value}" util.print_multiline(message) anilist_ids = self._search(**data) logger.debug("") diff --git a/modules/builder.py b/modules/builder.py index ef897ce6..f03ce95b 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -782,7 +782,7 @@ class CollectionBuilder: if method_name in ["anilist_id", "anilist_relations", "anilist_studio"]: for anilist_id in self.config.AniList.validate_anilist_ids(method_data, studio=method_name == "anilist_studio"): self.builders.append((method_name, anilist_id)) - elif method_name in ["anilist_popular", "anilist_top_rated"]: + elif method_name in ["anilist_popular", "anilist_trending", "anilist_top_rated"]: self.builders.append((method_name, util.parse(method_name, method_data, datatype="int", default=10))) elif method_name == "anilist_search": if self.current_time.month in [12, 1, 2]: current_season = "winter" @@ -802,13 +802,14 @@ class CollectionBuilder: new_dictionary["year"] = self.current_year elif search_attr == "year": new_dictionary[search_attr] = util.parse(search_attr, search_data, datatype="int", parent=method_name, default=self.current_year, minimum=1917, maximum=self.current_year + 1) - if "season" not in dict_methods: - logger.warning(f"Collection Warning: {method_name} season attribute not found using this season: {current_season} by default") - new_dictionary["season"] = current_season elif search_data is None: raise Failed(f"Collection Error: {method_name} {search_final} attribute is blank") elif search_attr == "adult": new_dictionary[search_attr] = util.parse(search_attr, search_data, datatype="bool", parent=method_name) + elif search_attr == "country": + new_dictionary[search_attr] = util.parse(search_attr, search_data, options=anilist.country_codes, parent=method_name) + elif search_attr == "source": + new_dictionary[search_attr] = util.parse(search_attr, search_data, options=anilist.media_source, parent=method_name) elif search_attr in ["episodes", "duration", "score", "popularity"]: new_dictionary[search_final] = util.parse(search_final, search_data, datatype="int", parent=method_name) elif search_attr in ["format", "status", "genre", "tag", "tag_category"]: @@ -823,7 +824,7 @@ class CollectionBuilder: raise Failed(f"Collection Error: {method_name} {search_final} attribute not supported") if len(new_dictionary) == 0: raise Failed(f"Collection Error: {method_name} must have at least one valid search option") - new_dictionary["sort_by"] = util.parse("sort_by", dict_data, methods=dict_methods, parent=method_name, default="score", options=["score", "popular"]) + new_dictionary["sort_by"] = util.parse("sort_by", dict_data, methods=dict_methods, parent=method_name, default="score", options=anilist.sort_options) new_dictionary["limit"] = util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=0, parent=method_name) self.builders.append((method_name, new_dictionary))