added anilist_search

pull/362/head
meisnate12 3 years ago
parent 9d27f959fd
commit fcbfcf5ecd

@ -6,17 +6,27 @@ logger = logging.getLogger("Plex Meta Manager")
builders = [ builders = [
"anilist_genre", "anilist_id", "anilist_popular", "anilist_relations", "anilist_genre", "anilist_id", "anilist_popular", "anilist_relations",
"anilist_season", "anilist_studio", "anilist_tag", "anilist_top_rated" "anilist_season", "anilist_studio", "anilist_tag", "anilist_top_rated", "anilist_search"
] ]
pretty_names = {"score": "Average Score", "popular": "Popularity"} pretty_names = {"score": "Average Score", "popular": "Popularity"}
search_translation = { attr_translation = {"year": "seasonYear", "adult": "isAdult", "start": "startDate", "end": "endDate", "tag_category": "tagCategory", "score": "averageScore", "min_tag_percent": "minimumTagRank"}
"season": "MediaSeason", "seasonYear": "Int", "isAdult": "Boolean", mod_translation = {"": "in", "not": "not_in", "before": "greater", "after": "lesser", "gt": "greater", "gte": "greater", "lt": "lesser", "lte": "lesser"}
"startDate_greater": "FuzzyDateInt", "startDate_lesser": "FuzzyDateInt", "endDate_greater": "FuzzyDateInt", "endDate_lesser": "FuzzyDateInt", mod_searches = [
"format_in": "[MediaFormat]", "format_not_in": "[MediaFormat]", "status_in": "[MediaStatus]", "status_not_in": "[MediaStatus]", "start.before", "start.after", "end.before", "end.after",
"episodes_greater": "Int", "episodes_lesser": "Int", "duration_greater": "Int", "duration_lesser": "Int", "format", "format.not", "status", "status.not", "genre", "genre.not", "tag", "tag.not", "tag_category", "tag_category.not",
"genre_in": "[String]", "genre_not_in": "[String]", "tag_in": "[String]", "tag_not_in": "[String]", "episodes.gt", "episodes.gte", "episodes.lt", "episodes.lte", "duration.gt", "duration.gte", "duration.lt", "duration.lte",
"averageScore_greater": "Int", "averageScore_lesser": "Int", "popularity_greater": "Int", "popularity_lesser": "Int" "score.gt", "score.gte", "score.lt", "score.lte", "popularity.gt", "popularity.gte", "popularity.lt", "popularity.lte"
]
no_mod_searches = ["season", "year", "adult", "min_tag_percent"]
searches = mod_searches + no_mod_searches
search_types = {
"season": "MediaSeason", "seasonYear": "Int", "isAdult": "Boolean", "startDate": "FuzzyDateInt", "endDate": "FuzzyDateInt",
"format": "[MediaFormat]", "status": "[MediaStatus]", "genre": "[String]", "tag": "[String]", "tagCategory": "[String]",
"episodes": "Int", "duration": "Int", "averageScore": "Int", "popularity": "Int", "minimumTagRank": "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"}
base_url = "https://graphql.anilist.co" base_url = "https://graphql.anilist.co"
tag_query = "query{MediaTagCollection {name, category}}" tag_query = "query{MediaTagCollection {name, category}}"
genre_query = "query{GenreCollection}" genre_query = "query{GenreCollection}"
@ -24,12 +34,14 @@ genre_query = "query{GenreCollection}"
class AniList: class AniList:
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.tags = {} self.options = {
self.categories = {} "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
}
for media_tag in self._request(tag_query, {})["data"]["MediaTagCollection"]: for media_tag in self._request(tag_query, {})["data"]["MediaTagCollection"]:
self.tags[media_tag["name"].lower().replace(" ", "-")] = media_tag["name"] self.options["Tag"][media_tag["name"].lower().replace(" ", "-")] = media_tag["name"]
self.categories[media_tag["category"].lower().replace(" ", "-")] = media_tag["category"] self.options["Tag Category"][media_tag["category"].lower().replace(" ", "-")] = media_tag["category"]
self.genres = {g.lower().replace(" ", "-"): g for g in self._request(genre_query, {})["data"]["GenreCollection"]}
def _request(self, query, variables, level=1): def _request(self, query, variables, level=1):
response = self.config.post(base_url, json={"query": query, "variables": variables}) response = self.config.post(base_url, json={"query": query, "variables": variables})
@ -76,32 +88,31 @@ class AniList:
break break
return anilist_ids return anilist_ids
def _top_rated(self, limit): def _search(self, **kwargs):
return self._search(limit=limit, averageScore_greater=3)
def _popular(self, limit):
return self._search(sort="popular", limit=limit, popularity_greater=1000)
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]" query_vars = "$page: Int, $sort: [MediaSort]"
media_vars = "sort: $sort, type: ANIME" media_vars = "sort: $sort, type: ANIME"
variables = {"sort": "SCORE_DESC" if sort == "score" else "POPULARITY_DESC"} variables = {"sort": "SCORE_DESC" if kwargs['sort_by'] == "score" else "POPULARITY_DESC"}
for key, value in kwargs.items(): for key, value in kwargs.items():
query_vars += f", ${key}: {search_translation[key]}" if key not in ["sort_by", "limit"]:
media_vars += f", {key}: ${key}" if "." in key:
variables[key] = value attr, mod = key.split(".")
else:
attr = key
mod = ""
ani_attr = attr_translation[attr] if attr in attr_translation else attr
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"))
if mod == "gte":
value -= 1
elif mod == "lte":
value += 1
query_vars += f", ${final}: {search_types[ani_attr]}"
media_vars += f", {final}: ${final}"
variables[key] = value
query = f"query ({query_vars}) {{Page(page: $page){{pageInfo {{hasNextPage}}media({media_vars}){{id}}}}}}" query = f"query ({query_vars}) {{Page(page: $page){{pageInfo {{hasNextPage}}media({media_vars}){{id}}}}}}"
logger.info(query) logger.debug(query)
return self._pagenation(query, limit=limit, variables=variables) return self._pagenation(query, limit=kwargs["limit"], variables=variables)
def _genre(self, genre, sort, limit):
return self._search(sort=sort, limit=limit, genre=genre)
def _tag(self, tag, sort, limit):
return self._search(sort=sort, limit=limit, tag=tag)
def _studio(self, studio_id): def _studio(self, studio_id):
query = """ query = """
@ -164,20 +175,15 @@ class AniList:
return anilist_ids, ignore_ids, name return anilist_ids, ignore_ids, name
def validate_tag(self, tag): def validate(self, name, data):
return self._validate(tag, self.tags, "Tag") valid = []
for d in util.get_list(data):
def validate_category(self, category): data_check = d.lower().replace(" / ", "-").replace(" ", "-")
return self._validate(category, self.categories, "Category") if data_check in self.options[name]:
valid.append(self.options[name][data_check])
def validate_genre(self, genre): if len(valid) > 0:
return self._validate(genre, self.genres, "Genre") return valid
raise Failed(f"AniList Error: {name}: {data} does not exist\nOptions: {', '.join([v for k, v in self.options[name].items()])}")
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()])}")
def validate_anilist_ids(self, anilist_ids, studio=False): def validate_anilist_ids(self, anilist_ids, studio=False):
anilist_id_list = util.get_int_list(anilist_ids, "AniList ID") anilist_id_list = util.get_int_list(anilist_ids, "AniList ID")
@ -197,21 +203,6 @@ class AniList:
logger.info(f"Processing AniList ID: {data}") logger.info(f"Processing AniList ID: {data}")
anilist_id, name = self._validate_id(data) anilist_id, name = self._validate_id(data)
anilist_ids = [anilist_id] anilist_ids = [anilist_id]
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)
elif method == "anilist_season":
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']]}")
anilist_ids = self._season(data["season"], data["year"], data["sort_by"], data["limit"])
elif method == "anilist_genre":
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']]}")
anilist_ids = self._genre(data["genre"], data["sort_by"], data["limit"])
elif method == "anilist_tag":
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']]}")
anilist_ids = self._tag(data["tag"], data["sort_by"], data["limit"])
elif method == "anilist_studio": elif method == "anilist_studio":
anilist_ids, name = self._studio(data) anilist_ids, name = self._studio(data)
logger.info(f"Processing AniList Studio: ({data}) {name} ({len(anilist_ids)} Anime)") logger.info(f"Processing AniList Studio: ({data}) {name} ({len(anilist_ids)} Anime)")
@ -219,7 +210,24 @@ class AniList:
anilist_ids, _, name = self._relations(data) anilist_ids, _, name = self._relations(data)
logger.info(f"Processing AniList Relations: ({data}) {name} ({len(anilist_ids)} Anime)") logger.info(f"Processing AniList Relations: ({data}) {name} ({len(anilist_ids)} Anime)")
else: else:
raise Failed(f"AniList Error: Method {method} not supported") if method == "anilist_popular":
data = {"limit": data, "popularity.gt": 3, "sort_by": "popular"}
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']]}"
if data['limit'] > 0:
message += f"\nLimit: {data['limit']}"
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}"
util.print_multiline(message)
anilist_ids = self._search(**data)
logger.debug("") logger.debug("")
logger.debug(f"{len(anilist_ids)} AniList IDs Found: {anilist_ids}") logger.debug(f"{len(anilist_ids)} AniList IDs Found: {anilist_ids}")
return anilist_ids return anilist_ids

@ -37,7 +37,9 @@ method_alias = {
"producers": "producer", "producers": "producer",
"writers": "writer", "writers": "writer",
"years": "year", "show_year": "year", "show_years": "year", "years": "year", "show_year": "year", "show_years": "year",
"show_title": "title" "show_title": "title",
"seasonyear": "year", "isadult": "adult", "startdate": "start", "enddate": "end", "averagescore": "score",
"minimum_tag_percentage": "min_tag_percent", "minimumtagrank": "min_tag_percent", "minimum_tag_rank": "min_tag_percent"
} }
filter_translation = { filter_translation = {
"actor": "actors", "actor": "actors",
@ -757,12 +759,45 @@ class CollectionBuilder:
elif self.current_time.month in [3, 4, 5]: new_dictionary["season"] = "spring" elif self.current_time.month in [3, 4, 5]: new_dictionary["season"] = "spring"
elif self.current_time.month in [6, 7, 8]: new_dictionary["season"] = "summer" elif self.current_time.month in [6, 7, 8]: new_dictionary["season"] = "summer"
elif self.current_time.month in [9, 10, 11]: new_dictionary["season"] = "fall" elif self.current_time.month in [9, 10, 11]: new_dictionary["season"] = "fall"
new_dictionary["season"] = util.parse("season", dict_data, methods=dict_methods, parent=method_name, default=new_dictionary["season"], options=["winter", "spring", "summer", "fall"]) new_dictionary["season"] = util.parse("season", dict_data, methods=dict_methods, parent=method_name, default=new_dictionary["season"], options=util.seasons)
new_dictionary["year"] = util.parse("year", dict_data, datatype="int", methods=dict_methods, default=self.current_time.year, parent=method_name, minimum=1917, maximum=self.current_time.year + 1) new_dictionary["year"] = util.parse("year", dict_data, datatype="int", methods=dict_methods, default=self.current_year, parent=method_name, minimum=1917, maximum=self.current_year + 1)
elif method_name == "anilist_genre": elif method_name == "anilist_genre":
new_dictionary["genre"] = self.config.AniList.validate_genre(util.parse("genre", dict_data, methods=dict_methods, parent=method_name)) new_dictionary["genre"] = self.config.AniList.validate("Genre", util.parse("genre", dict_data, methods=dict_methods, parent=method_name))
elif method_name == "anilist_tag": elif method_name == "anilist_tag":
new_dictionary["tag"] = self.config.AniList.validate_tag(util.parse("tag", dict_data, methods=dict_methods, parent=method_name)) new_dictionary["tag"] = self.config.AniList.validate("Tag", util.parse("tag", dict_data, methods=dict_methods, parent=method_name))
elif method_name == "anilist_search":
for search_method, search_data in dict_data.items():
search_attr, modifier, search_final = self._split(search_method)
if search_data is None:
raise Failed(f"Collection Error: {method_name} {search_final} attribute is blank")
elif search_final not in anilist.searches:
raise Failed(f"Collection Error: {method_name} {search_final} attribute not supported")
elif search_attr == "season":
if self.current_time.month in [12, 1, 2]: new_dictionary["season"] = "winter"
elif self.current_time.month in [3, 4, 5]: new_dictionary["season"] = "spring"
elif self.current_time.month in [6, 7, 8]: new_dictionary["season"] = "summer"
elif self.current_time.month in [9, 10, 11]: new_dictionary["season"] = "fall"
new_dictionary["season"] = util.parse("season", dict_data, parent=method_name, default=new_dictionary["season"], options=util.seasons)
if "year" not in dict_methods:
logger.warning(f"Collection Warning: {method_name} {search_final} attribute must be used with the year attribute using this year by default")
elif search_attr == "year":
if "season" not in dict_methods:
raise Failed(f"Collection Error: {method_name} {search_final} attribute must be used with the season attribute")
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)
elif search_attr == "adult":
new_dictionary[search_attr] = util.parse(search_attr, search_data, datatype="bool", 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"]:
new_dictionary[search_final] = self.config.AniList.validate(search_attr.replace("_", " ").title(), util.parse(search_final, search_data))
elif search_attr in ["start", "end"]:
new_dictionary[search_final] = util.validate_date(search_data, f"{method_name} {search_final} attribute", return_as="%m/%d/%Y")
elif search_attr == "min_tag_percent":
new_dictionary[search_attr] = util.parse(search_attr, search_data, datatype="int", parent=method_name, minimum=0, maximum=100)
elif search_final not in ["sort_by", "limit"]:
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=["score", "popular"])
new_dictionary["limit"] = util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=0, parent=method_name, maximum=500) new_dictionary["limit"] = util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=0, parent=method_name, maximum=500)
self.builders.append((method_name, new_dictionary)) self.builders.append((method_name, new_dictionary))
@ -808,9 +843,9 @@ class CollectionBuilder:
elif self.current_time.month in [7, 8, 9]: default_season = "summer" elif self.current_time.month in [7, 8, 9]: default_season = "summer"
else: default_season = "fall" else: default_season = "fall"
self.builders.append((method_name, { self.builders.append((method_name, {
"season": util.parse("season", dict_data, methods=dict_methods, parent=method_name, default=default_season, options=["winter", "spring", "summer", "fall"]), "season": util.parse("season", dict_data, methods=dict_methods, parent=method_name, default=default_season, options=util.seasons),
"sort_by": util.parse("sort_by", dict_data, methods=dict_methods, parent=method_name, default="members", options=mal.season_sort_options, translation=mal.season_sort_translation), "sort_by": util.parse("sort_by", dict_data, methods=dict_methods, parent=method_name, default="members", options=mal.season_sort_options, translation=mal.season_sort_translation),
"year": util.parse("year", dict_data, datatype="int", methods=dict_methods, default=self.current_time.year, parent=method_name, minimum=1917, maximum=self.current_time.year + 1), "year": util.parse("year", dict_data, datatype="int", methods=dict_methods, default=self.current_year, parent=method_name, minimum=1917, maximum=self.current_year + 1),
"limit": util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name, maximum=500) "limit": util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name, maximum=500)
})) }))
elif method_name == "mal_userlist": elif method_name == "mal_userlist":
@ -866,58 +901,45 @@ class CollectionBuilder:
def _tmdb(self, method_name, method_data): def _tmdb(self, method_name, method_data):
if method_name == "tmdb_discover": if method_name == "tmdb_discover":
for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"): for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
new_dictionary = {"limit": 100} new_dictionary = {"limit": util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name)}
for discover_name, discover_data in dict_data.items(): for discover_method, discover_data in dict_data.items():
discover_final = discover_name.lower() discover_attr, modifier, discover_final = self._split(discover_method)
if discover_data: if discover_data is None:
if (self.library.is_movie and discover_final in tmdb.discover_movie) or (self.library.is_show and discover_final in tmdb.discover_tv): raise Failed(f"Collection Error: {method_name} {discover_final} attribute is blank")
if discover_final == "language": elif discover_final not in tmdb.discover_all:
if re.compile("([a-z]{2})-([A-Z]{2})").match(str(discover_data)): raise Failed(f"Collection Error: {method_name} {discover_final} attribute not supported")
new_dictionary[discover_final] = str(discover_data) elif self.library.is_movie and discover_attr in tmdb.discover_tv_only:
else: raise Failed(f"Collection Error: {method_name} {discover_final} attribute only works for show libraries")
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} must match pattern ([a-z]{{2}})-([A-Z]{{2}}) e.g. en-US") elif self.library.is_show and discover_attr in tmdb.discover_movie_only:
elif discover_final == "region": raise Failed(f"Collection Error: {method_name} {discover_final} attribute only works for movie libraries")
if re.compile("^[A-Z]{2}$").match(str(discover_data)): elif discover_attr in ["language", "region"]:
new_dictionary[discover_final] = str(discover_data) regex = ("([a-z]{2})-([A-Z]{2})", "en-US") if discover_attr == "language" else ("^[A-Z]{2}$", "US")
else: new_dictionary[discover_attr] = util.parse(discover_attr, discover_data, parent=method_name, regex=regex)
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} must match pattern ^[A-Z]{{2}}$ e.g. US") elif discover_attr == "sort_by" and self.library.is_movie:
elif discover_final == "sort_by": options = tmdb.discover_movie_sort if self.library.is_movie else tmdb.discover_tv_sort
if (self.library.is_movie and discover_data in tmdb.discover_movie_sort) or (self.library.is_show and discover_data in tmdb.discover_tv_sort): new_dictionary[discover_attr] = util.parse(discover_attr, discover_data, parent=method_name, options=options)
new_dictionary[discover_final] = discover_data elif discover_attr == "certification_country":
else: if "certification" in dict_data or "certification.lte" in dict_data or "certification.gte" in dict_data:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} is invalid") new_dictionary[discover_attr] = discover_data
elif discover_final == "certification_country":
if "certification" in dict_data or "certification.lte" in dict_data or "certification.gte" in dict_data:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be used with either certification, certification.lte, or certification.gte")
elif discover_final in ["certification", "certification.lte", "certification.gte"]:
if "certification_country" in dict_data:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be used with certification_country")
elif discover_final in ["include_adult", "include_null_first_air_dates", "screened_theatrically"]:
if discover_data is True:
new_dictionary[discover_final] = discover_data
elif discover_final in tmdb.discover_dates:
new_dictionary[discover_final] = util.validate_date(discover_data, f"{method_name} attribute {discover_final}", return_as="%m/%d/%Y")
elif discover_final in ["primary_release_year", "year", "first_air_date_year"]:
new_dictionary[discover_final] = util.parse(discover_final, discover_data, datatype="int", parent=method_name, minimum=1800, maximum=self.current_year + 1)
elif discover_final in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]:
new_dictionary[discover_final] = util.parse(discover_final, discover_data, datatype="int", parent=method_name)
elif discover_final in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final} not supported")
elif discover_final == "limit":
if isinstance(discover_data, int) and discover_data > 0:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be a valid number greater then 0")
else: else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final} not supported") raise Failed(f"Collection Error: {method_name} {discover_attr} attribute: must be used with either certification, certification.lte, or certification.gte")
else: elif discover_attr == "certification":
raise Failed(f"Collection Error: {method_name} parameter {discover_final} is blank") if "certification_country" in dict_data:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} {discover_final} attribute: must be used with certification_country")
elif discover_attr in ["include_adult", "include_null_first_air_dates", "screened_theatrically"]:
new_dictionary[discover_attr] = util.parse(discover_attr, discover_data, datatype="bool", parent=method_name)
elif discover_final in tmdb.discover_dates:
new_dictionary[discover_final] = util.validate_date(discover_data, f"{method_name} {discover_final} attribute", return_as="%m/%d/%Y")
elif discover_attr in ["primary_release_year", "year", "first_air_date_year"]:
new_dictionary[discover_attr] = util.parse(discover_attr, discover_data, datatype="int", parent=method_name, minimum=1800, maximum=self.current_year + 1)
elif discover_attr in ["vote_count", "vote_average", "with_runtime"]:
new_dictionary[discover_final] = util.parse(discover_final, discover_data, datatype="int", parent=method_name)
elif discover_final in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]:
new_dictionary[discover_final] = discover_data
elif discover_attr != "limit":
raise Failed(f"Collection Error: {method_name} {discover_final} attribute not supported")
if len(new_dictionary) > 1: if len(new_dictionary) > 1:
self.builders.append((method_name, new_dictionary)) self.builders.append((method_name, new_dictionary))
else: else:
@ -1191,7 +1213,7 @@ class CollectionBuilder:
if attr in string_filters and modifier in ["", ".not"]: if attr in string_filters and modifier in ["", ".not"]:
mod_s = "does not contain" if modifier == ".not" else "contains" mod_s = "does not contain" if modifier == ".not" else "contains"
elif mod_s is None: elif mod_s is None:
mod_s = plex.mod_displays[modifier] mod_s = util.mod_displays[modifier]
param_s = plex.search_display[attr] if attr in plex.search_display else attr.title().replace('_', ' ') param_s = plex.search_display[attr] if attr in plex.search_display else attr.title().replace('_', ' ')
display_line = f"{indent}{param_s} {mod_s} {arg_s}" display_line = f"{indent}{param_s} {mod_s} {arg_s}"
return f"{arg_key}{mod}={arg}&", display_line return f"{arg_key}{mod}={arg}&", display_line

@ -208,7 +208,7 @@ class MyAnimeList:
logger.info(f"Processing {mal_ranked_pretty[method]} ID: {data['producer_id']}") logger.info(f"Processing {mal_ranked_pretty[method]} ID: {data['producer_id']}")
mal_ids = self._producer(data["producer_id"], data["limit"]) mal_ids = self._producer(data["producer_id"], data["limit"])
elif method == "mal_season": elif method == "mal_season":
logger.info(f"Processing MyAnimeList Season: {data['limit']} Anime from {util.pretty_seasons[data['season']]} {data['year']} sorted by {pretty_names[data['sort_by']]}") logger.info(f"Processing MyAnimeList Season: {data['limit']} Anime from {data['season'].title()} {data['year']} sorted by {pretty_names[data['sort_by']]}")
mal_ids = self._season(data["season"], data["year"], data["sort_by"], data["limit"]) mal_ids = self._season(data["season"], data["year"], data["sort_by"], data["limit"])
elif method == "mal_suggested": elif method == "mal_suggested":
logger.info(f"Processing MyAnimeList Suggested: {data} Anime") logger.info(f"Processing MyAnimeList Suggested: {data} Anime")

@ -152,10 +152,6 @@ sorts = {
"added.asc": "addedAt:asc", "added.desc": "addedAt:desc" "added.asc": "addedAt:asc", "added.desc": "addedAt:desc"
} }
modifiers = {".not": "!", ".begins": "<", ".ends": ">", ".before": "<<", ".after": ">>", ".gt": ">>", ".gte": "__gte", ".lt": "<<", ".lte": "__lte"} modifiers = {".not": "!", ".begins": "<", ".ends": ">", ".before": "<<", ".after": ">>", ".gt": ">>", ".gte": "__gte", ".lt": "<<", ".lte": "__lte"}
mod_displays = {
"": "is", ".not": "is not", ".begins": "begins with", ".ends": "ends with", ".before": "is before", ".after": "is after",
".gt": "is greater than", ".gte": "is greater than or equal", ".lt": "is less than", ".lte": "is less than or equal"
}
tags = [ tags = [
"actor", "audio_language", "collection", "content_rating", "country", "director", "genre", "label", "actor", "audio_language", "collection", "content_rating", "country", "director", "genre", "label",
"network", "producer", "resolution", "studio", "subtitle_language", "writer" "network", "producer", "resolution", "studio", "subtitle_language", "writer"

@ -21,19 +21,23 @@ type_map = {
"tmdb_network": "Network", "tmdb_person": "Person", "tmdb_producer": "Person", "tmdb_producer_details": "Person", "tmdb_network": "Network", "tmdb_person": "Person", "tmdb_producer": "Person", "tmdb_producer_details": "Person",
"tmdb_show": "Show", "tmdb_show_details": "Show", "tmdb_writer": "Person", "tmdb_writer_details": "Person" "tmdb_show": "Show", "tmdb_show_details": "Show", "tmdb_writer": "Person", "tmdb_writer_details": "Person"
} }
discover_movie = [ discover_all = [
"language", "with_original_language", "region", "sort_by", "with_cast", "with_crew", "with_people", "language", "with_original_language", "region", "sort_by", "with_cast", "with_crew", "with_people",
"certification_country", "certification", "certification.lte", "certification.gte", "certification_country", "certification", "certification.lte", "certification.gte",
"year", "primary_release_year", "primary_release_date.gte", "primary_release_date.lte", "year", "primary_release_year", "primary_release_date.gte", "primary_release_date.lte",
"release_date.gte", "release_date.lte", "vote_count.gte", "vote_count.lte", "release_date.gte", "release_date.lte", "vote_count.gte", "vote_count.lte",
"vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte",
"with_companies", "with_genres", "without_genres", "with_keywords", "without_keywords", "include_adult" "with_companies", "with_genres", "without_genres", "with_keywords", "without_keywords", "include_adult",
"timezone", "screened_theatrically", "include_null_first_air_dates", "limit",
"air_date.gte", "air_date.lte", "first_air_date.gte", "first_air_date.lte", "first_air_date_year", "with_networks"
] ]
discover_tv = [ discover_movie_only = [
"language", "with_original_language", "timezone", "sort_by", "screened_theatrically", "include_null_first_air_dates", "region", "with_cast", "with_crew", "with_people", "certification_country", "certification",
"air_date.gte", "air_date.lte", "first_air_date.gte", "first_air_date.lte", "first_air_date_year", "year", "primary_release_year", "primary_release_date", "release_date", "include_adult"
"vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte", ]
"with_genres", "without_genres", "with_keywords", "without_keywords", "with_networks", "with_companies" discover_tv_only = [
"timezone", "screened_theatrically", "include_null_first_air_dates",
"air_date", "first_air_date", "first_air_date_year", "with_networks",
] ]
discover_dates = [ discover_dates = [
"primary_release_date.gte", "primary_release_date.lte", "release_date.gte", "release_date.lte", "primary_release_date.gte", "primary_release_date.lte", "release_date.gte", "release_date.lte",

@ -48,12 +48,16 @@ days_alias = {
"saturday": 5, "sat": 5, "s": 5, "saturday": 5, "sat": 5, "s": 5,
"sunday": 6, "sun": 6, "su": 6, "u": 6 "sunday": 6, "sun": 6, "su": 6, "u": 6
} }
mod_displays = {
"": "is", ".not": "is not", ".begins": "begins with", ".ends": "ends with", ".before": "is before", ".after": "is after",
".gt": "is greater than", ".gte": "is greater than or equal", ".lt": "is less than", ".lte": "is less than or equal"
}
pretty_days = {0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday", 4: "Friday", 5: "Saturday", 6: "Sunday"} pretty_days = {0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday", 4: "Friday", 5: "Saturday", 6: "Sunday"}
pretty_months = { pretty_months = {
1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", 1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June",
7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December" 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December"
} }
pretty_seasons = {"winter": "Winter", "spring": "Spring", "summer": "Summer", "fall": "Fall"} seasons = ["winter", "spring", "summer", "fall"]
pretty_ids = {"anidbid": "AniDB", "imdbid": "IMDb", "mal_id": "MyAnimeList", "themoviedb_id": "TMDb", "thetvdb_id": "TVDb", "tvdbid": "TVDb"} pretty_ids = {"anidbid": "AniDB", "imdbid": "IMDb", "mal_id": "MyAnimeList", "themoviedb_id": "TMDb", "thetvdb_id": "TVDb", "tvdbid": "TVDb"}
def tab_new_lines(data): def tab_new_lines(data):
@ -283,7 +287,7 @@ def is_string_filter(values, modifier, data):
if jailbreak: break if jailbreak: break
return (jailbreak and modifier == ".not") or (not jailbreak and modifier in ["", ".begins", ".ends", ".regex"]) return (jailbreak and modifier == ".not") or (not jailbreak and modifier in ["", ".begins", ".ends", ".regex"])
def parse(attribute, data, datatype=None, methods=None, parent=None, default=None, options=None, translation=None, minimum=1, maximum=None): def parse(attribute, data, datatype=None, methods=None, parent=None, default=None, options=None, translation=None, minimum=1, maximum=None, regex=None):
display = f"{parent + ' ' if parent else ''}{attribute} attribute" display = f"{parent + ' ' if parent else ''}{attribute} attribute"
if options is None and translation is not None: if options is None and translation is not None:
options = [o for o in translation] options = [o for o in translation]
@ -305,6 +309,12 @@ def parse(attribute, data, datatype=None, methods=None, parent=None, default=Non
message = f"{display} not found" message = f"{display} not found"
elif value is None: elif value is None:
message = f"{display} is blank" message = f"{display} is blank"
elif regex is not None:
regex_str, example = regex
if re.compile(regex_str).match(str(value)):
return str(value)
else:
message = f"{display}: {value} must match pattern {regex_str} e.g. {example}"
elif datatype == "bool": elif datatype == "bool":
if isinstance(value, bool): if isinstance(value, bool):
return value return value
@ -330,7 +340,7 @@ def parse(attribute, data, datatype=None, methods=None, parent=None, default=Non
message = f"{pre} between {minimum} and {maximum}" message = f"{pre} between {minimum} and {maximum}"
elif (translation is not None and str(value).lower() not in translation) or \ elif (translation is not None and str(value).lower() not in translation) or \
(options is not None and translation is None and str(value).lower() not in options): (options is not None and translation is None and str(value).lower() not in options):
message = f"{display} {value} must be in {options}" message = f"{display} {value} must be in {', '.join([str(o) for o in options])}"
else: else:
return translation[value] if translation is not None else value return translation[value] if translation is not None else value

Loading…
Cancel
Save