From 6cfd46fd6719c3eb6679945bac2ee0ef716dee01 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Tue, 12 Dec 2023 16:24:37 -0500 Subject: [PATCH] [47] add imdb_award --- VERSION | 2 +- docs/builders/imdb.md | 46 +++++++++++++++++++++++++++++------- modules/builder.py | 36 +++++++++++++++++++++++++++++ modules/imdb.py | 54 +++++++++++++++++++++++++++++++++++++++---- modules/util.py | 2 +- 5 files changed, 126 insertions(+), 14 deletions(-) diff --git a/VERSION b/VERSION index 57e4b1fd..0fcee333 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.19.1-develop46 +1.19.1-develop47 diff --git a/docs/builders/imdb.md b/docs/builders/imdb.md index 8312d139..12fa4baa 100644 --- a/docs/builders/imdb.md +++ b/docs/builders/imdb.md @@ -2,13 +2,14 @@ You can find items using the features of [IMDb.com](https://www.imdb.com/) (IMDb). -| Attribute | Description | Works with Movies | Works with Shows | Works with Playlists and Custom Sort | -|:------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------:|:----------------:|:------------------------------------:| -| [`imdb_id`](#imdb-id) | Gets the movie/show specified. | ✅ | ✅ | ❌ | -| [`imdb_chart`](#imdb-chart) | Gets every movie/show in an IMDb Chart like [IMDb Top 250 Movies](https://www.imdb.com/chart/top). | ✅ | ✅ | ✅ | -| [`imdb_list`](#imdb-list) | Gets every movie/show in an IMDb List or [IMDb Keyword Search](https://www.imdb.com/search/keyword/). | ✅ | ✅ | ✅ | -| [`imdb_watchlist`](#imdb-watchlist) | Gets every movie/show in an IMDb User's Watchlist. | ✅ | ✅ | ✅ | -| [`imdb_search`](#imdb-search) | Gets every movie/show in an [IMDb Search](https://www.imdb.com/search/title/). | ✅ | ✅ | ✅ | +| Attribute | Description | Works with Movies | Works with Shows | Works with Playlists and Custom Sort | +|:------------------------------------|:------------------------------------------------------------------------------------------------------|:-----------------:|:----------------:|:------------------------------------:| +| [`imdb_id`](#imdb-id) | Gets the movie/show specified. | ✅ | ✅ | ❌ | +| [`imdb_chart`](#imdb-chart) | Gets every movie/show in an IMDb Chart like [IMDb Top 250 Movies](https://www.imdb.com/chart/top). | ✅ | ✅ | ✅ | +| [`imdb_list`](#imdb-list) | Gets every movie/show in an IMDb List or [IMDb Keyword Search](https://www.imdb.com/search/keyword/). | ✅ | ✅ | ✅ | +| [`imdb_watchlist`](#imdb-watchlist) | Gets every movie/show in an IMDb User's Watchlist. | ✅ | ✅ | ✅ | +| [`imdb_award`](#imdb-award) | Gets every movie/show in an [IMDb Event](https://www.imdb.com/event/). | ✅ | ✅ | ❌ | +| [`imdb_search`](#imdb-search) | Gets every movie/show in an [IMDb Search](https://www.imdb.com/search/title/). | ✅ | ✅ | ✅ | ## IMDb ID @@ -154,9 +155,38 @@ collections: sync_mode: sync ``` +## IMDb Award + +Finds every item in an [IMDb Event](https://www.imdb.com/event/). + +| Award Parameter | Description | +|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `event_id` | Specify the IMDb Event ID to search.
**Options:** The ID found in the URLs linked on the [IMDb Events Page](https://www.imdb.com/event/). (ex. `ev0000003`) | +| `event_year` | Specify the Year of the Event to look at.
**Options:** Any Year under the Event History Sidebar on an Event page. | +| `award_filter` | Filter by the Award heading. Can only accept multiple values as a list.
**Options:** Any Black Award heading on an Event Page. | +| `category_filter` | Filter by the Category heading. Can only accept multiple values as a list.
**Options:** Any Gold/Yellow Category heading on an Event Page. | +| `winning` | Filter by if the Item Won the award.
**Options:** `true`/`false`
**Default:** `false` | + +```yaml +collections: + Academy Award Winners 2023: + imdb_award: + event_id: ev0000003 + event_year: 2023 + winning: true +``` +```yaml +collections: + Academy Award 2023 Best Picture Nominees: + imdb_award: + event_id: ev0000003 + event_year: 2023 + category_filter: Best Motion Picture of the Year +``` + ## IMDb Search -Finds every item using an [IMDb Advance Title Search](https://www.imdb.com/search/title/) +Finds every item using an [IMDb Advance Title Search](https://www.imdb.com/search/title/). The `sync_mode: sync` and `collection_order: custom` Details are recommended since the lists are continuously updated and in a specific order. diff --git a/modules/builder.py b/modules/builder.py index a1d7195c..4433b7ef 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1500,6 +1500,42 @@ class CollectionBuilder: elif method_name == "imdb_watchlist": for imdb_user in self.config.IMDb.validate_imdb_watchlists(self.Type, method_data, self.language): self.builders.append((method_name, imdb_user)) + elif method_name == "imdb_award": + for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"): + dict_methods = {dm.lower(): dm for dm in dict_data} + event_id = util.parse(self.Type, "event_id", dict_data, parent=method_name, methods=dict_methods, regex=(r"(ev\d+)", "ev0000003")) + year_options = self.config.IMDb.get_event_years(event_id) + if not year_options: + raise Failed(f"{self.Type} Error: imdb_award event_id attribute: No event found at {imdb.base_url}/event/{event_id}") + event_year = util.parse(self.Type, "event_year", dict_data, parent=method_name, methods=dict_methods, options=year_options) + try: + award_filters = util.parse(self.Type, "award_filter", dict_data, parent=method_name, methods=dict_methods, datatype="lowerlist") + except Failed: + award_filters = [] + try: + category_filters = util.parse(self.Type, "category_filter", dict_data, parent=method_name, methods=dict_methods, datatype="lowerlist") + except Failed: + category_filters = [] + final_category = [] + final_awards = [] + if award_filters or category_filters: + award_names, category_names = self.config.IMDb.get_award_names(event_id, event_year) + lower_award = {a.lower(): a for a in award_names if a} + for award_filter in award_filters: + if award_filter in lower_award: + final_awards.append(lower_award[award_filter]) + else: + raise Failed(f"{self.Type} Error: imdb_award award_filter attribute invalid: {award_filter} must be in in [{', '.join([v for _, v in lower_award.items()])}]") + lower_category = {c.lower(): c for c in category_names if c} + for category_filter in category_filters: + if category_filter in lower_category: + final_category.append(lower_category[category_filter]) + else: + raise Failed(f"{self.Type} Error: imdb_award category_filter attribute invalid: {category_filter} must be in in [{', '.join([v for _, v in lower_category.items()])}]") + self.builders.append((method_name, { + "event_id": event_id, "event_year": event_year, "award_filter": final_awards if final_awards else None, "category_filter": final_category if final_category else None, + "winning": util.parse(self.Type, "winning", dict_data, parent=method_name, methods=dict_methods, datatype="bool", default=False) + })) elif method_name == "imdb_search": for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"): dict_methods = {dm.lower(): dm for dm in dict_data} diff --git a/modules/imdb.py b/modules/imdb.py index 4cd0f0de..97679dc4 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -5,7 +5,7 @@ from urllib.parse import urlparse, parse_qs logger = util.logger -builders = ["imdb_list", "imdb_id", "imdb_chart", "imdb_watchlist", "imdb_search"] +builders = ["imdb_list", "imdb_id", "imdb_chart", "imdb_watchlist", "imdb_search", "imdb_award"] movie_charts = ["box_office", "popular_movies", "top_movies", "top_english", "top_indian", "lowest_rated"] show_charts = ["popular_shows", "top_shows"] charts = { @@ -145,6 +145,23 @@ class IMDb: valid_users.append(user) return valid_users + def get_event_years(self, event_id): + return self._request(f"{base_url}/event/{event_id}", xpath="//div[@class='event-history-widget']//a/text()") + + def get_award_names(self, event_id, event_year): + award_names = [] + category_names = [] + for text in self._request(f"{base_url}/event/{event_id}/{event_year}", xpath="//div[@class='article']/script/text()")[0].split("\n"): + if text.strip().startswith("IMDbReactWidgets.NomineesWidget.push"): + jsonline = text.strip() + obj = json.loads(jsonline[jsonline.find("{"):-3]) + for award in obj["nomineesWidgetModel"]["eventEditionSummary"]["awards"]: + award_names.append(award["awardName"]) + for category in award["categories"]: + category_names.append(category["categoryName"]) + break + return award_names, category_names + def _watchlist(self, user, language): imdb_url = f"{base_url}/user/{user}/watchlist" group = self._request(imdb_url, language=language, xpath="//span[@class='ab_widget']/script[@type='text/javascript']/text()") @@ -401,6 +418,27 @@ class IMDb: return imdb_ids raise Failed("IMDb Error: No IMDb IDs Found") + def _award(self, data): + final_list = [] + for text in self._request(f"{base_url}/event/{data['event_id']}/{data['event_year']}", xpath="//div[@class='article']/script/text()")[0].split("\n"): + if text.strip().startswith("IMDbReactWidgets.NomineesWidget.push"): + jsonline = text.strip() + obj = json.loads(jsonline[jsonline.find('{'):-3]) + for award in obj["nomineesWidgetModel"]["eventEditionSummary"]["awards"]: + if data["award_filter"] and award["awardName"] not in data["award_filter"]: + continue + for cat in award["categories"]: + if data["category_filter"] and cat["categoryName"] not in data["category_filter"]: + continue + for nom in cat["nominations"]: + if data["winning"] and not nom["isWinner"]: + continue + imdb_set = next(((n["const"], n["name"]) for n in nom["primaryNominees"] + nom["secondaryNominees"] if n["const"].startswith("tt")), None) + if imdb_set: + final_list.append(imdb_set) + break + return final_list + def keywords(self, imdb_id, language, ignore_cache=False): imdb_keywords = {} expired = None @@ -408,7 +446,7 @@ class IMDb: imdb_keywords, expired = self.config.Cache.query_imdb_keywords(imdb_id, self.config.Cache.expiration) if imdb_keywords and expired is False: return imdb_keywords - keywords = self._request(f"https://www.imdb.com/title/{imdb_id}/keywords", language=language, xpath="//td[@class='soda sodavote']") + keywords = self._request(f"{base_url}/title/{imdb_id}/keywords", language=language, xpath="//td[@class='soda sodavote']") if not keywords: raise Failed(f"IMDb Error: No Item Found for IMDb ID: {imdb_id}") for k in keywords: @@ -430,7 +468,7 @@ class IMDb: parental_dict, expired = self.config.Cache.query_imdb_parental(imdb_id, self.config.Cache.expiration) if parental_dict and expired is False: return parental_dict - response = self._request(f"https://www.imdb.com/title/{imdb_id}/parentalguide") + response = self._request(f"{base_url}/title/{imdb_id}/parentalguide") for ptype in util.parental_types: results = response.xpath(f"//section[@id='advisory-{ptype}']//span[contains(@class,'ipl-status-pill')]/text()") if results: @@ -460,7 +498,7 @@ class IMDb: url = "chart/bottom" else: raise Failed(f"IMDb Error: chart: {chart} not ") - links = self._request(f"https://www.imdb.com/{url}", language=language, xpath="//li//a[@class='ipc-title-link-wrapper']/@href") + links = self._request(f"{base_url}/{url}", language=language, xpath="//li//a[@class='ipc-title-link-wrapper']/@href") return [re.search("(tt\\d+)", l).group(1) for l in links] def get_imdb_ids(self, method, data, language): @@ -477,6 +515,14 @@ class IMDb: elif method == "imdb_watchlist": logger.info(f"Processing IMDb Watchlist: {data}") return [(_i, "imdb") for _i in self._watchlist(data, language)] + elif method == "imdb_award": + logger.info(f"Processing IMDb Award: {base_url}/{data['event_id']}/{data['event_year']}") + for k in ["award_filter", "category_filter", "winning"]: + logger.info(f" {k}: {data[k]}") + awards = self._award(data) + for award in awards: + logger.info(award) + return [(_i, "imdb") for _i, _ in awards] elif method == "imdb_search": logger.info(f"Processing IMDb Search:") for k, v in data.items(): diff --git a/modules/util.py b/modules/util.py index 27a8dae6..8a4dc5bb 100644 --- a/modules/util.py +++ b/modules/util.py @@ -852,7 +852,7 @@ def parse(error, attribute, data, datatype=None, methods=None, parent=None, defa message = f"{e}" 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 {', '.join([str(o) for o in options])}" + message = f"{display} {value} must be in [{', '.join([str(o) for o in options])}]" else: return translation[str(value).lower()] if translation is not None else value