mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-10 06:54:21 +00:00
added anilist_search
This commit is contained in:
parent
9d27f959fd
commit
fcbfcf5ecd
6 changed files with 179 additions and 139 deletions
|
@ -6,17 +6,27 @@ logger = logging.getLogger("Plex Meta Manager")
|
|||
|
||||
builders = [
|
||||
"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"}
|
||||
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"
|
||||
attr_translation = {"year": "seasonYear", "adult": "isAdult", "start": "startDate", "end": "endDate", "tag_category": "tagCategory", "score": "averageScore", "min_tag_percent": "minimumTagRank"}
|
||||
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",
|
||||
"format", "format.not", "status", "status.not", "genre", "genre.not", "tag", "tag.not", "tag_category", "tag_category.not",
|
||||
"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 = ["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"
|
||||
tag_query = "query{MediaTagCollection {name, category}}"
|
||||
genre_query = "query{GenreCollection}"
|
||||
|
@ -24,12 +34,14 @@ genre_query = "query{GenreCollection}"
|
|||
class AniList:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.tags = {}
|
||||
self.categories = {}
|
||||
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
|
||||
}
|
||||
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"]}
|
||||
self.options["Tag"][media_tag["name"].lower().replace(" ", "-")] = media_tag["name"]
|
||||
self.options["Tag Category"][media_tag["category"].lower().replace(" ", "-")] = media_tag["category"]
|
||||
|
||||
def _request(self, query, variables, level=1):
|
||||
response = self.config.post(base_url, json={"query": query, "variables": variables})
|
||||
|
@ -76,32 +88,31 @@ class AniList:
|
|||
break
|
||||
return anilist_ids
|
||||
|
||||
def _top_rated(self, limit):
|
||||
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):
|
||||
def _search(self, **kwargs):
|
||||
query_vars = "$page: Int, $sort: [MediaSort]"
|
||||
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():
|
||||
query_vars += f", ${key}: {search_translation[key]}"
|
||||
media_vars += f", {key}: ${key}"
|
||||
variables[key] = value
|
||||
if key not in ["sort_by", "limit"]:
|
||||
if "." in key:
|
||||
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}}}}}}"
|
||||
logger.info(query)
|
||||
return self._pagenation(query, limit=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)
|
||||
logger.debug(query)
|
||||
return self._pagenation(query, limit=kwargs["limit"], variables=variables)
|
||||
|
||||
def _studio(self, studio_id):
|
||||
query = """
|
||||
|
@ -164,20 +175,15 @@ class AniList:
|
|||
|
||||
return anilist_ids, ignore_ids, name
|
||||
|
||||
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()])}")
|
||||
def validate(self, name, data):
|
||||
valid = []
|
||||
for d in util.get_list(data):
|
||||
data_check = d.lower().replace(" / ", "-").replace(" ", "-")
|
||||
if data_check in self.options[name]:
|
||||
valid.append(self.options[name][data_check])
|
||||
if len(valid) > 0:
|
||||
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_anilist_ids(self, anilist_ids, studio=False):
|
||||
anilist_id_list = util.get_int_list(anilist_ids, "AniList ID")
|
||||
|
@ -197,21 +203,6 @@ class AniList:
|
|||
logger.info(f"Processing AniList ID: {data}")
|
||||
anilist_id, name = self._validate_id(data)
|
||||
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":
|
||||
anilist_ids, name = self._studio(data)
|
||||
logger.info(f"Processing AniList Studio: ({data}) {name} ({len(anilist_ids)} Anime)")
|
||||
|
@ -219,7 +210,24 @@ class AniList:
|
|||
anilist_ids, _, name = self._relations(data)
|
||||
logger.info(f"Processing AniList Relations: ({data}) {name} ({len(anilist_ids)} Anime)")
|
||||
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(f"{len(anilist_ids)} AniList IDs Found: {anilist_ids}")
|
||||
return anilist_ids
|
||||
|
|
|
@ -37,7 +37,9 @@ method_alias = {
|
|||
"producers": "producer",
|
||||
"writers": "writer",
|
||||
"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 = {
|
||||
"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 [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, methods=dict_methods, parent=method_name, default=new_dictionary["season"], options=["winter", "spring", "summer", "fall"])
|
||||
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["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_year, parent=method_name, minimum=1917, maximum=self.current_year + 1)
|
||||
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":
|
||||
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["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))
|
||||
|
@ -808,9 +843,9 @@ class CollectionBuilder:
|
|||
elif self.current_time.month in [7, 8, 9]: default_season = "summer"
|
||||
else: default_season = "fall"
|
||||
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),
|
||||
"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)
|
||||
}))
|
||||
elif method_name == "mal_userlist":
|
||||
|
@ -866,58 +901,45 @@ class CollectionBuilder:
|
|||
def _tmdb(self, method_name, method_data):
|
||||
if method_name == "tmdb_discover":
|
||||
for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
|
||||
new_dictionary = {"limit": 100}
|
||||
for discover_name, discover_data in dict_data.items():
|
||||
discover_final = discover_name.lower()
|
||||
if discover_data:
|
||||
if (self.library.is_movie and discover_final in tmdb.discover_movie) or (self.library.is_show and discover_final in tmdb.discover_tv):
|
||||
if discover_final == "language":
|
||||
if re.compile("([a-z]{2})-([A-Z]{2})").match(str(discover_data)):
|
||||
new_dictionary[discover_final] = str(discover_data)
|
||||
else:
|
||||
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 discover_final == "region":
|
||||
if re.compile("^[A-Z]{2}$").match(str(discover_data)):
|
||||
new_dictionary[discover_final] = str(discover_data)
|
||||
else:
|
||||
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} must match pattern ^[A-Z]{{2}}$ e.g. US")
|
||||
elif discover_final == "sort_by":
|
||||
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_final] = discover_data
|
||||
else:
|
||||
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} is invalid")
|
||||
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")
|
||||
new_dictionary = {"limit": util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name)}
|
||||
for discover_method, discover_data in dict_data.items():
|
||||
discover_attr, modifier, discover_final = self._split(discover_method)
|
||||
if discover_data is None:
|
||||
raise Failed(f"Collection Error: {method_name} {discover_final} attribute is blank")
|
||||
elif discover_final not in tmdb.discover_all:
|
||||
raise Failed(f"Collection Error: {method_name} {discover_final} attribute not supported")
|
||||
elif self.library.is_movie and discover_attr in tmdb.discover_tv_only:
|
||||
raise Failed(f"Collection Error: {method_name} {discover_final} attribute only works for show libraries")
|
||||
elif self.library.is_show and discover_attr in tmdb.discover_movie_only:
|
||||
raise Failed(f"Collection Error: {method_name} {discover_final} attribute only works for movie libraries")
|
||||
elif discover_attr in ["language", "region"]:
|
||||
regex = ("([a-z]{2})-([A-Z]{2})", "en-US") if discover_attr == "language" else ("^[A-Z]{2}$", "US")
|
||||
new_dictionary[discover_attr] = util.parse(discover_attr, discover_data, parent=method_name, regex=regex)
|
||||
elif discover_attr == "sort_by" and self.library.is_movie:
|
||||
options = tmdb.discover_movie_sort if self.library.is_movie else tmdb.discover_tv_sort
|
||||
new_dictionary[discover_attr] = util.parse(discover_attr, discover_data, parent=method_name, options=options)
|
||||
elif discover_attr == "certification_country":
|
||||
if "certification" in dict_data or "certification.lte" in dict_data or "certification.gte" in dict_data:
|
||||
new_dictionary[discover_attr] = discover_data
|
||||
else:
|
||||
raise Failed(f"Collection Error: {method_name} attribute {discover_final} not supported")
|
||||
else:
|
||||
raise Failed(f"Collection Error: {method_name} parameter {discover_final} is blank")
|
||||
raise Failed(f"Collection Error: {method_name} {discover_attr} attribute: must be used with either certification, certification.lte, or certification.gte")
|
||||
elif discover_attr == "certification":
|
||||
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:
|
||||
self.builders.append((method_name, new_dictionary))
|
||||
else:
|
||||
|
@ -1191,7 +1213,7 @@ class CollectionBuilder:
|
|||
if attr in string_filters and modifier in ["", ".not"]:
|
||||
mod_s = "does not contain" if modifier == ".not" else "contains"
|
||||
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('_', ' ')
|
||||
display_line = f"{indent}{param_s} {mod_s} {arg_s}"
|
||||
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']}")
|
||||
mal_ids = self._producer(data["producer_id"], data["limit"])
|
||||
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"])
|
||||
elif method == "mal_suggested":
|
||||
logger.info(f"Processing MyAnimeList Suggested: {data} Anime")
|
||||
|
|
|
@ -152,10 +152,6 @@ sorts = {
|
|||
"added.asc": "addedAt:asc", "added.desc": "addedAt:desc"
|
||||
}
|
||||
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 = [
|
||||
"actor", "audio_language", "collection", "content_rating", "country", "director", "genre", "label",
|
||||
"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_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",
|
||||
"certification_country", "certification", "certification.lte", "certification.gte",
|
||||
"year", "primary_release_year", "primary_release_date.gte", "primary_release_date.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",
|
||||
"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 = [
|
||||
"language", "with_original_language", "timezone", "sort_by", "screened_theatrically", "include_null_first_air_dates",
|
||||
"air_date.gte", "air_date.lte", "first_air_date.gte", "first_air_date.lte", "first_air_date_year",
|
||||
"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_movie_only = [
|
||||
"region", "with_cast", "with_crew", "with_people", "certification_country", "certification",
|
||||
"year", "primary_release_year", "primary_release_date", "release_date", "include_adult"
|
||||
]
|
||||
discover_tv_only = [
|
||||
"timezone", "screened_theatrically", "include_null_first_air_dates",
|
||||
"air_date", "first_air_date", "first_air_date_year", "with_networks",
|
||||
]
|
||||
discover_dates = [
|
||||
"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,
|
||||
"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_months = {
|
||||
1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June",
|
||||
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"}
|
||||
|
||||
def tab_new_lines(data):
|
||||
|
@ -283,7 +287,7 @@ def is_string_filter(values, modifier, data):
|
|||
if jailbreak: break
|
||||
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"
|
||||
if options is None and translation is not None:
|
||||
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"
|
||||
elif value is None:
|
||||
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":
|
||||
if isinstance(value, bool):
|
||||
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}"
|
||||
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):
|
||||
message = f"{display} {value} must be in {options}"
|
||||
message = f"{display} {value} must be in {', '.join([str(o) for o in options])}"
|
||||
else:
|
||||
return translation[value] if translation is not None else value
|
||||
|
||||
|
|
Loading…
Reference in a new issue