[11] add BoxOfficeMojo builders

pull/1831/head
meisnate12 4 months ago
parent 3356a40e22
commit e6523d6902

@ -1 +1 @@
1.20.0-develop10
1.20.0-develop11

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

@ -0,0 +1,394 @@
# BoxOfficeMojo Builders
You can find items using the lists on [boxofficemojo.com](https://www.boxofficemojo.com/) (BoxOfficeMojo).
No configuration is required for these builders.
??? blank "`mojo_domestic` - Uses the Domestic Box Office.<a class="headerlink" href="#mojo-domestic" title="Permanent link"></a>"
<div id="mojo-domestic" />Uses the Domestic Box Office to collection items.
<hr style="margin: 0px;">
**Works With:** Movies, Playlists, and Custom Sort
**Builder Attribute:** `mojo_domestic`
**Builder Value:** [Dictionary](../../pmm/yaml.md#dictionaries) of Attributes
??? blank "`range` - Determines the type of time range of the Box Office"
Determines the type of the time range of the Box Office.
**Allowed Values:** `daily`, `weekend`, `weekly`, `monthly`, `quarterly`, `yearly`, `season`, or `holiday`
??? blank "`year` - Determines the year of the Box Office"
Determines the year of the Box Office. This attribute is ignored for the `daily` range.
**Default Value:** `current`
**Allowed Values:** Number between 1977 and the current year, `current`, or relative current (`current-#`; where
`#` is the number of year before the current)
??? blank "`range_data` - Determines the actual time range of the Box Office"
Determines the actual time range of the Box Office. The input for this value changes depending on the value
of `range`. This attribute is required for all ranges expect the `yearly` range.
**Daily Allowed Values:** Date in the format `MM-DD-YYYY`, `current`, or relative current (`current-#`; where
`#` is the number of days before the current)
**Weekend Allowed Values:** Week Number between 1-53, `current`, or relative current (`current-#`; where `#`
is the number of days before the current)
**Weekly Allowed Values:** Week Number between 1-53, `current`, or relative current (`current-#`; where `#`
is the number of days before the current)
**Monthly Allowed Values:** `january`, `february`, `march`, `april`, `may`, `june`, `july`, `august`,
`september`, `october`, `november`, `december`, `current`, or relative current (`current-#`; where `#` is the
number of days before the current)
**Quarterly Allowed Values:** `q1`, `q2`, `q3`, `q4`, `current`, or relative current (`current-#`; where `#`
is the number of days before the current)
**Season Allowed Values:** `winter`, `spring`, `summer`, `fall`, `holiday`, or `current`
**Holiday Allowed Values:** `new_years_day`, `new_year_weekend`, `mlk_day`, `mlk_day_weekend`,
`presidents_day`, `presidents_day_weekend`, `easter`, `easter_weekend`, `memorial_day`, `memorial_day_weekend`,
`independence_day`, `independence_day_weekend`, `labor_day`, `labor_day_weekend`, `indigenous_day`,
`indigenous_day_weekend`, `halloween`, `thanksgiving`, `thanksgiving_3`, `thanksgiving_4`, `thanksgiving_5`,
`post_thanksgiving_weekend`, `christmas_day`, `christmas_weekend`, or `new_years_eve`
??? blank "`limit` - The maximum number of result to return"
This determines the maximum number of results to return. If there are less results then the limit then all will
be returned.
**Default Value:** Returns all results
**Allowed Values:** Number greter than 0
???+ example "Example"
```yaml
collections:
Current Domestic Box Office:
mojo_domestic:
range: yearly
year: current
Last Year's Domestic Box Office:
mojo_domestic:
range: yearly
year: current-1
Last Months Top 10 Domestic Box Office:
mojo_domestic:
range: monthly
year: current-1
limit: 10
```
??? blank "`mojo_international` - Uses the International Box Office.<a class="headerlink" href="#mojo-international" title="Permanent link"></a>"
<div id="mojo-international" />Uses the International Box Office to collection items.
<hr style="margin: 0px;">
**Works With:** Movies, Playlists, and Custom Sort
**Builder Attribute:** `mojo_international`
**Builder Value:** [Dictionary](../../pmm/yaml.md#dictionaries) of Attributes
??? blank "`range` - Determines the type of time range of the Box Office"
Determines the type of the time range of the Box Office.
**Allowed Values:** `weekend`, `monthly`, `quarterly`, or `yearly`
??? blank "`chart` - Determines the chart you want to use"
Determines the chart you want to use.
**Default Value:** `international`
**Allowed Values:** Item in the drop down found [here](https://www.boxofficemojo.com/intl/)
??? blank "`year` - Determines the year of the Box Office"
Determines the year of the Box Office.
**Default Value:** `current`
**Allowed Values:** Number between 1977 and the current year, `current`, or relative current (`current-#`; where
`#` is the number of year before the current)
??? blank "`range_data` - Determines the actual time range of the Box Office"
Determines the actual time range of the Box Office. The input for this value changes depending on the value
of `range`. This attribute is required for all ranges expect the `yearly` range.
**Weekend Allowed Values:** Week Number between 1-53, `current`, or relative current (`current-#`; where `#`
is the number of days before the current)
**Monthly Allowed Values:** `january`, `february`, `march`, `april`, `may`, `june`, `july`, `august`,
`september`, `october`, `november`, `december`, `current`, or relative current (`current-#`; where `#` is the
number of days before the current)
**Quarterly Allowed Values:** `q1`, `q2`, `q3`, `q4`, `current`, or relative current (`current-#`; where `#`
is the number of days before the current)
??? blank "`limit` - The maximum number of result to return"
This determines the maximum number of results to return. If there are less results then the limit then all will
be returned.
**Default Value:** Returns all results
**Allowed Values:** Number greter than 0
???+ example "Example"
```yaml
collections:
Current International Box Office:
mojo_international:
range: yearly
year: current
Last Year's International Box Office:
mojo_international:
range: yearly
year: current-1
Last Months Top 10 German Box Office:
mojo_international:
range: monthly
chart: germany
year: current-1
limit: 10
```
??? blank "`mojo_world` - Uses the Worldwide Box Office.<a class="headerlink" href="#mojo-world" title="Permanent link"></a>"
<div id="mojo-world" />Uses the [Worldwide Box Office](https://www.boxofficemojo.com/year/world/) to collection items.
<hr style="margin: 0px;">
**Works With:** Movies, Playlists, and Custom Sort
**Builder Attribute:** `mojo_world`
**Builder Value:** [Dictionary](../../pmm/yaml.md#dictionaries) of Attributes
??? blank "`year` - The year of the Worldwide Box Office"
This determines the year of the [Worldwide Box Office](https://www.boxofficemojo.com/year/world/) to pull.
**Allowed Values:** Number between 1977 and the current year, `current`, or relative current (`current-#`; where
`#` is the number of year before the current)
??? blank "`limit` - The maximum number of result to return"
This determines the maximum number of results to return. If there are less results then the limit then all will
be returned.
**Default Value:** Returns all results
**Allowed Values:** Number greter than 0
???+ example "Example"
```yaml
collections:
Current Worlwide Box Office:
mojo_world:
year: current
Last Year's Worlwide Box Office:
mojo_world:
year: current-1
2020 Top 10 Worlwide Box Office:
mojo_world:
year: 2020
limit: 10
```
??? blank "`mojo_all_time` - Uses the All Time Lists.<a class="headerlink" href="#mojo-all-time" title="Permanent link"></a>"
<div id="mojo-all-time" />Uses the [All Time Lists](https://www.boxofficemojo.com/charts/overall/) to collection items.
<hr style="margin: 0px;">
**Works With:** Movies, Playlists, and Custom Sort
**Builder Attribute:** `mojo_all_time`
**Builder Value:** [Dictionary](../../pmm/yaml.md#dictionaries) of Attributes
??? blank "`chart` - Determines the chart you want to use"
Determines the chart you want to use.
**Allowed Values:** `domestic` or `worldwide`
??? blank "`content_rating_filter` - Determines the content rating chart to use"
Determines the content rating chart to use.
**Allowed Values:** `g`, `g/pg`, `pg`, `pg-13`, `r` or `nc-17`
??? blank "`limit` - The maximum number of result to return"
This determines the maximum number of results to return. If there are less results then the limit then all will
be returned.
**Default Value:** Returns all results
**Allowed Values:** Number greter than 0
???+ example "Example"
```yaml
collections:
Top 100 Domestic All Time Grosses:
mojo_all_time:
chart: domestic
limit: 100
Top 100 Worldwide All Time Grosses:
mojo_all_time:
chart: worldwide
limit: 100
Top 10 Domestic All Time G Movie Grosses:
mojo_world:
chart: domestic
content_rating_filter: g
limit: 10
```
??? blank "`mojo_never` - Uses the Never Hit Lists.<a class="headerlink" href="#mojo-never" title="Permanent link"></a>"
<div id="mojo-never" />Uses the [Never Hit Lists](https://www.boxofficemojo.com/charts/overall/) (Bottom Section) to
collection items.
<hr style="margin: 0px;">
**Works With:** Movies, Playlists, and Custom Sort
**Builder Attribute:** `mojo_never`
**Builder Value:** [Dictionary](../../pmm/yaml.md#dictionaries) of Attributes
??? blank "`chart` - Determines the chart you want to use"
Determines the chart you want to use.
**Allowed Values:** Item in the drop down found [here](https://www.boxofficemojo.com/charts/overall/)
??? blank "`never` - Determines the never filter to use"
Determines the never filter to use.
**Default Value:** `1`
**Allowed Values:** `1`, `5`, or `10`
??? blank "`limit` - The maximum number of result to return"
This determines the maximum number of results to return. If there are less results then the limit then all will
be returned.
**Default Value:** Returns all results
**Allowed Values:** Number greter than 0
???+ example "Example"
```yaml
collections:
Top 100 Domestic Never #1:
mojo_never:
chart: domestic
limit: 100
Top 100 Domestic Never #10:
mojo_never:
chart: domestic
never: 10
limit: 100
Top 100 German Never #1:
mojo_never:
chart: germany
limit: 100
```
??? blank "`mojo_record` - Uses other Record Lists.<a class="headerlink" href="#mojo-record" title="Permanent link"></a>"
<div id="mojo-record" />Uses the [Weekend Records](https://www.boxofficemojo.com/charts/weekend/),
[Daily Records](https://www.boxofficemojo.com/charts/daily/), and
[Miscellaneous Records](https://www.boxofficemojo.com/charts/misc/) to collection items.
<hr style="margin: 0px;">
**Works With:** Movies, Playlists, and Custom Sort
**Builder Attribute:** `mojo_record`
**Builder Value:** [Dictionary](../../pmm/yaml.md#dictionaries) of Attributes
??? blank "`chart` - Determines the record you want to use"
Determines the chart you want to use.
**Allowed Values:** `second_weekend_drop`, `post_thanksgiving_weekend_drop`, `top_opening_weekend`,
`worst_opening_weekend_theater_avg`, `mlk_opening`, `easter_opening`, `memorial_opening`, `labor_opening`,
`president_opening`, `thanksgiving_3_opening`, `thanksgiving_5_opening`, `mlk`, `easter`, `4th`, `memorial`,
`labor`, `president`, `thanksgiving_3`, `thanksgiving_5`, `january`, `february`, `march`, `april`, `may`,
`june`, `july`, `august`, `september`, `october`, `november`, `december`, `spring`, `summer`, `fall`,
`holiday_season`, `winter`, `g`, `g/pg`, `pg`, `pg-13`, `r`, `nc-17`, `top_opening_weekend_theater_avg_all`,
`top_opening_weekend_theater_avg_wide`, `opening_day`, `single_day_grosses`, `christmas_day_gross`,
`new_years_day_gross`, `friday`, `saturday`, `sunday`, `monday`, `tuesday`, `wednesday`, `thursday`,
`friday_non_opening`, `saturday_non_opening`, `sunday_non_opening`, `monday_non_opening`, `tuesday_non_opening`,
`wednesday_non_opening`, `thursday_non_opening`, `biggest_theater_drop`, or `opening_week`
??? blank "`limit` - The maximum number of result to return"
This determines the maximum number of results to return. If there are less results then the limit then all will
be returned.
**Default Value:** Returns all results
**Allowed Values:** Number greter than 0
???+ example "Example"
```yaml
collections:
Top 10 Biggest Opening Weekends:
mojo_record:
chart: top_opening_weekend
limit: 10
Top 10 Biggest Opening Day:
mojo_record:
chart: opening_day
limit: 10
Top 10 Biggest Opening Weeks:
mojo_record:
chart: opening_week
limit: 10
```

@ -30,6 +30,11 @@ Builders use third-party services to source items to be added to the collection.
image: ../../../assets/icons/imdb.png
url: "../imdb"
- title: Box Office Mojo
content: Grabs items based on metadata and lists on Boxofficemojo.com
image: ../../../assets/icons/boxofficemojo.png
url: "../boxofficemojo"
- title: Trakt
content: Grabs items based on metadata and lists on Trakt.tv
image: ../../../assets/icons/trakt.png

@ -319,6 +319,7 @@ nav:
- Letterboxd Builders: files/builders/letterboxd.md
- ICheckMovies Builders: files/builders/icheckmovies.md
- FlixPatrol Builders: files/builders/flixpatrol.md
- BoxOfficeMojo Builders: files/builders/mojo.md
- Reciperr Builders: files/builders/reciperr.md
- StevenLu Builders: files/builders/stevenlu.md
- AniDB Builders: files/builders/anidb.md

@ -1,7 +1,8 @@
import os, re, time
from arrapi import ArrException
from datetime import datetime
from modules import anidb, anilist, flixpatrol, icheckmovies, imdb, letterboxd, mal, plex, radarr, reciperr, sonarr, tautulli, tmdb, trakt, tvdb, mdblist, util
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from modules import anidb, anilist, flixpatrol, icheckmovies, imdb, letterboxd, mal, mojo, plex, radarr, reciperr, sonarr, tautulli, tmdb, trakt, tvdb, mdblist, util
from modules.util import Failed, FilterFailed, NonExisting, NotScheduled, NotScheduledRange, Deleted
from modules.overlay import Overlay
from modules.poster import PMMImage
@ -16,7 +17,7 @@ logger = util.logger
advance_new_agent = ["item_metadata_language", "item_use_original_title"]
advance_show = ["item_episode_sorting", "item_keep_episodes", "item_delete_episodes", "item_season_display", "item_episode_sorting"]
all_builders = anidb.builders + anilist.builders + flixpatrol.builders + icheckmovies.builders + imdb.builders + \
letterboxd.builders + mal.builders + plex.builders + reciperr.builders + tautulli.builders + \
letterboxd.builders + mal.builders + mojo.builders + plex.builders + reciperr.builders + tautulli.builders + \
tmdb.builders + trakt.builders + tvdb.builders + mdblist.builders + radarr.builders + sonarr.builders
show_only_builders = [
"tmdb_network", "tmdb_show", "tmdb_show_details", "tvdb_show", "tvdb_show_details", "tmdb_airing_today",
@ -25,7 +26,8 @@ show_only_builders = [
movie_only_builders = [
"letterboxd_list", "letterboxd_list_details", "icheckmovies_list", "icheckmovies_list_details", "stevenlu_popular",
"tmdb_collection", "tmdb_collection_details", "tmdb_movie", "tmdb_movie_details", "tmdb_now_playing", "item_edition",
"tvdb_movie", "tvdb_movie_details", "tmdb_upcoming", "trakt_boxoffice", "reciperr_list", "radarr_all", "radarr_taglist"
"tvdb_movie", "tvdb_movie_details", "tmdb_upcoming", "trakt_boxoffice", "reciperr_list", "radarr_all", "radarr_taglist",
"mojo_world", "mojo_domestic", "mojo_international", "mojo_record", "mojo_all_time", "mojo_never"
]
music_only_builders = ["item_album_sorting"]
summary_details = [
@ -153,7 +155,8 @@ custom_sort_builders = [
"tautulli_popular", "tautulli_watched", "mdblist_list", "letterboxd_list", "icheckmovies_list", "flixpatrol_top",
"anilist_top_rated", "anilist_popular", "anilist_trending", "anilist_search", "anilist_userlist",
"mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_movie", "mal_ova", "mal_special", "mal_search",
"mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio"
"mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio",
"mojo_world", "mojo_domestic", "mojo_international", "mojo_record", "mojo_all_time", "mojo_never"
]
episode_parts_only = ["plex_pilots"]
overlay_only = ["overlay", "suppress_overlays"]
@ -1037,6 +1040,8 @@ class CollectionBuilder:
self._imdb(method_name, method_data)
elif method_name in mal.builders:
self._mal(method_name, method_data)
elif method_name in mojo.builders:
self._mojo(method_name, method_data)
elif method_name in plex.builders or method_final in plex.searches:
self._plex(method_name, method_data)
elif method_name in reciperr.builders:
@ -1800,6 +1805,127 @@ class CollectionBuilder:
final_text = f"MyAnimeList Search\n{method_name[4:].capitalize()}: {' or '.join([str(all_items[i]) for i in final_items])}"
self.builders.append(("mal_search", ({"genres" if method_name == "mal_genre" else "producers": ",".join(final_items)}, final_text, 0)))
def _mojo(self, method_name, method_data):
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
final = {}
if method_name == "mojo_record":
final["chart"] = util.parse(self.Type, "chart", dict_data, methods=dict_methods, parent=method_name, options=mojo.top_options)
elif method_name == "mojo_world":
if "year" not in dict_methods:
raise Failed(f"{self.Type} Error: {method_name} year attribute not found")
og_year = dict_data[dict_methods["year"]]
if not og_year:
raise Failed(f"{self.Type} Error: {method_name} year attribute is blank")
if og_year == "current":
final["year"] = str(self.current_year) # noqa
elif str(og_year).startswith("current-"):
try:
final["year"] = str(self.current_year - int(og_year.split("-")[1])) # noqa
if final["year"] not in mojo.year_options:
raise Failed(f"{self.Type} Error: {method_name} year attribute final value must be 1977 or greater: {og_year}")
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} year attribute invalid: {og_year}")
else:
final["year"] = util.parse(self.Type, "year", dict_data, methods=dict_methods, parent=method_name, options=mojo.year_options)
elif method_name == "mojo_all_time":
final["chart"] = util.parse(self.Type, "chart", dict_data, methods=dict_methods, parent=method_name, options=mojo.chart_options)
final["content_rating_filter"] = util.parse(self.Type, "content_rating_filter", dict_data, methods=dict_methods, parent=method_name, options=mojo.content_rating_options) if "content_rating_filter" in dict_methods else None
elif method_name == "mojo_never":
final["chart"] = util.parse(self.Type, "chart", dict_data, methods=dict_methods, parent=method_name, default="domestic", options=self.config.BoxOfficeMojo.never_options)
final["never"] = str(util.parse(self.Type, "never", dict_data, methods=dict_methods, parent=method_name, default="1", options=mojo.never_in_options)) if "never" in dict_methods else "1"
elif method_name in ["mojo_domestic", "mojo_international"]:
dome = method_name == "mojo_domestic"
final["range"] = util.parse(self.Type, "range", dict_data, methods=dict_methods, parent=method_name, options=mojo.dome_range_options if dome else mojo.intl_range_options)
if not dome:
final["chart"] = util.parse(self.Type, "chart", dict_data, methods=dict_methods, parent=method_name, default="international", options=self.config.BoxOfficeMojo.intl_options)
chart_date = self.current_time
if final["range"] != "daily":
_m = "range_data" if final["range"] == "yearly" and "year" not in dict_methods and "range_data" in dict_methods else "year"
if _m not in dict_methods:
raise Failed(f"{self.Type} Error: {method_name} {_m} attribute not found")
og_year = dict_data[dict_methods[_m]]
if not og_year:
raise Failed(f"{self.Type} Error: {method_name} {_m} attribute is blank")
if str(og_year).startswith("current-"):
try:
chart_date = self.current_time - relativedelta(years=int(og_year.split("-")[1]))
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} {_m} attribute invalid: {og_year}")
else:
_y = util.parse(self.Type, _m, dict_data, methods=dict_methods, parent=method_name, default="current", options=mojo.year_options)
if _y != "current":
chart_date = self.current_time - relativedelta(years=self.current_time.year - _y)
if final["range"] != "yearly":
if "range_data" not in dict_methods:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute not found")
og_data = dict_data[dict_methods["range_data"]]
if not og_data:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute is blank")
if final["range"] == "holiday":
final["range_data"] = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, options=mojo.holiday_options)
elif final["range"] == "daily":
if og_data == "current":
final["range_data"] = datetime.strftime(self.current_time, "%Y-%m-%d") # noqa
elif str(og_data).startswith("current-"):
try:
final["range_data"] = datetime.strftime(self.current_time - timedelta(days=int(og_data.split("-")[1])), "%Y-%m-%d") # noqa
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute invalid: {og_data}")
else:
final["range_data"] = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", datatype="date", date_return="%Y-%m-%d")
if final["range_data"] == "current":
final["range_data"] = datetime.strftime(self.current_time, "%Y-%m-%d") # noqa
elif final["range"] in ["weekend", "weekly"]:
if str(og_data).startswith("current-"):
try:
final_date = chart_date - timedelta(weeks=int(og_data.split("-")[1]))
final_iso = final_date.isocalendar()
final["range_data"] = final_iso.week
final["year"] = final_iso.year
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute invalid: {og_data}")
else:
_v = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", options=["current"] + [str(i) for i in range(1, 54)])
current_iso = chart_date.isocalendar()
final["range_data"] = current_iso.week if _v == "current" else _v
final["year"] = current_iso.year
elif final["range"] == "monthly":
if str(og_data).startswith("current-"):
try:
final_date = chart_date - relativedelta(months=int(og_data.split("-")[1]))
final["range_data"] = final_date.month
final["year"] = final_date.year
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute invalid: {og_data}")
else:
_v = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", options=["current"] + util.lower_months)
final["range_data"] = chart_date.month if _v == "current" else util.lower_months[_v]
elif final["range"] == "quarterly":
if str(og_data).startswith("current-"):
try:
final_date = chart_date - relativedelta(months=int(og_data.split("-")[1]) * 3)
final["range_data"] = mojo.quarters[final_date.month]
final["year"] = final_date.year
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute invalid: {og_data}")
else:
_v = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", options=mojo.quarter_options)
final["range_data"] = mojo.quarters[chart_date.month] if _v == "current" else _v
elif final["range"] == "season":
_v = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", options=mojo.season_options)
final["range_data"] = mojo.seasons[chart_date.month] if _v == "current" else _v
else:
final["range_data"] = chart_date.year
if "year" not in final:
final["year"] = chart_date.year
if final["year"] < 1977:
raise Failed(f"{self.Type} Error: {method_name} attribute final date value must be on year 1977 or greater: {final['year']}")
final["limit"] = util.parse(self.Type, "limit", dict_data, methods=dict_methods, parent=method_name, default=0, datatype="int", maximum=1000) if "limit" in dict_methods else 0
self.builders.append((method_name, final))
def _plex(self, method_name, method_data):
if method_name in ["plex_all", "plex_pilots"]:
self.builders.append((method_name, self.builder_level))
@ -2088,6 +2214,8 @@ class CollectionBuilder:
ids = self.config.Letterboxd.get_tmdb_ids(method, value, self.language)
elif "reciperr" in method or "stevenlu" in method:
ids = self.config.Reciperr.get_imdb_ids(method, value)
elif "mojo" in method:
ids = self.config.BoxOfficeMojo.get_imdb_ids(method, value)
elif "mdblist" in method:
ids = self.config.Mdblist.get_tmdb_ids(method, value, self.library.is_movie if not self.playlist else None)
elif "tmdb" in method:

@ -72,6 +72,13 @@ class Cache:
tmdb_id TEXT,
expiration_date TEXT)"""
)
cursor.execute(
"""CREATE TABLE IF NOT EXISTS mojo_map (
key INTEGER PRIMARY KEY,
mojo_url TEXT UNIQUE,
imdb_id TEXT,
expiration_date TEXT)"""
)
cursor.execute(
"""CREATE TABLE IF NOT EXISTS omdb_data3 (
key INTEGER PRIMARY KEY,
@ -377,6 +384,12 @@ class Cache:
def update_letterboxd_map(self, expired, letterboxd_id, tmdb_id):
self._update_map("letterboxd_map", "letterboxd_id", letterboxd_id, "tmdb_id", tmdb_id, expired)
def query_mojo_map(self, mojo_url):
return self._query_map("mojo_map", mojo_url, "mojo_url", "imdb_id")
def update_mojo_map(self, expired, mojo_url, imdb_id):
self._update_map("mojo_map", "mojo_url", mojo_url, "imdb_id", imdb_id, expired)
def _query_map(self, map_name, _id, from_id, to_id, media_type=None, return_type=False):
id_to_return = None
expired = None

@ -14,6 +14,7 @@ from modules.github import GitHub
from modules.letterboxd import Letterboxd
from modules.mal import MyAnimeList
from modules.meta import PlaylistFile
from modules.mojo import BoxOfficeMojo
from modules.notifiarr import Notifiarr
from modules.omdb import OMDb
from modules.overlays import Overlays
@ -704,6 +705,7 @@ class ConfigFile:
self.FlixPatrol = FlixPatrol(self)
self.ICheckMovies = ICheckMovies(self)
self.Letterboxd = Letterboxd(self)
self.BoxOfficeMojo = BoxOfficeMojo(self)
self.Reciperr = Reciperr(self)
self.Ergast = Ergast(self)

@ -0,0 +1,273 @@
from datetime import datetime
from modules import util
from modules.util import Failed
from num2words import num2words
from urllib.parse import urlparse, parse_qs
logger = util.logger
builders = ["mojo_world", "mojo_domestic", "mojo_international", "mojo_record", "mojo_all_time", "mojo_never"]
top_options = {
"second_weekend_drop": ("Biggest Second Weekend Drops", "/chart/biggest_second_weekend_gross_drop/", None),
"post_thanksgiving_weekend_drop": ("Largest Post-Thanksgiving Weekend Drops", "/chart/post_thanksgiving_weekend_drop/", None),
"top_opening_weekend": ("Top Opening Weekends", "/chart/top_opening_weekend/", None),
"worst_opening_weekend_theater_avg": ("Worst Opening Weekend Per-Theater Averages", "/chart/btm_wide_opening_weekend_theater_avg/", None),
"top_opening_weekend_theater_avg_all": ("Top Opening Theater Averages", "/chart/top_opening_weekend_theater_avg/", {"by_release_scale": "all"}),
"top_opening_weekend_theater_avg_wide": ("Top Wide Opening Theater Averages", "/chart/top_opening_weekend_theater_avg/", {"by_release_scale": "wide"}),
"january": ("Top Opening Weekend in January", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "january"}),
"february": ("Top Opening Weekend in February", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "february"}),
"march": ("Top Opening Weekend in March", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "march"}),
"april": ("Top Opening Weekend in April", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "april"}),
"may": ("Top Opening Weekend in May", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "may"}),
"june": ("Top Opening Weekend in June", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "june"}),
"july": ("Top Opening Weekend in July", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "july"}),
"august": ("Top Opening Weekend in August", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "august"}),
"september": ("Top Opening Weekend in September", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "september"}),
"october": ("Top Opening Weekend in October", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "october"}),
"november": ("Top Opening Weekend in November", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "november"}),
"december": ("Top Opening Weekend in December", "/chart/release_top_opn_wkd_in_month/", {"in_occasion": "december"}),
"spring": ("Top Opening Weekend in Spring", "/chart/release_top_opn_wkd_in_season/", {"in_occasion": "spring"}),
"summer": ("Top Opening Weekend in Summer", "/chart/release_top_opn_wkd_in_season/", {"in_occasion": "summer"}),
"fall": ("Top Opening Weekend in Fall", "/chart/release_top_opn_wkd_in_season/", {"in_occasion": "fall"}),
"holiday_season": ("Top Opening Weekend in The Holiday Season", "/chart/release_top_opn_wkd_in_season/", {"in_occasion": "holiday_season"}),
"winter": ("Top Opening Weekend in Winter", "/chart/release_top_opn_wkd_in_season/", {"in_occasion": "winter"}),
"g": ("Top Opening Weekend for G Ratings", "/chart/top_opening_wknd_by_mpaa/", {"by_mpaa": "G"}),
"g/pg": ("Top Opening Weekend for G/PG Ratings", "/chart/top_opening_wknd_by_mpaa/", {"by_mpaa": "G%2FPG"}),
"pg": ("Top Opening Weekend for PG Ratings", "/chart/top_opening_wknd_by_mpaa/", {"by_mpaa": "PG"}),
"pg-13": ("Top Opening Weekend for PG-13 Ratings", "/chart/top_opening_wknd_by_mpaa/", {"by_mpaa": "PG-13"}),
"r": ("Top Opening Weekend for R Ratings", "/chart/top_opening_wknd_by_mpaa/", {"by_mpaa": "R"}),
"nc-17": ("Top Opening Weekend for NC-17 Ratings", "/chart/top_opening_wknd_by_mpaa/", {"by_mpaa": "NC-17"}),
"mlk": ("Top Weekend for MLK Day", "/chart/release_top_weekend_gross/", {"by_occasion", "us_mlkday_weekend"}),
"easter": ("Top Weekend for Easter", "/chart/release_top_weekend_gross/", {"by_occasion", "easter_weekend"}),
"4th": ("Top Weekend for the 4th of July", "/chart/release_top_weekend_gross/", {"by_occasion", "us_july4_weekend"}),
"memorial": ("Top Weekend for Memorial Day", "/chart/release_top_weekend_gross/", {"by_occasion", "us_memorialday_weekend"}),
"labor": ("Top Weekend for Labor Day", "/chart/release_top_weekend_gross/", {"by_occasion", "us_laborday_weekend"}),
"president": ("Top Weekend for President's Day", "/chart/release_top_weekend_gross/", {"by_occasion", "us_presidentsday_weekend"}),
"thanksgiving_3": ("Top 3 Day Weekend for Thanksgiving", "/chart/release_top_weekend_gross/", {"by_occasion", "us_thanksgiving_3"}),
"thanksgiving_5": ("Top 5 Day Weekend for Thanksgiving", "/chart/release_top_weekend_gross/", {"by_occasion", "us_thanksgiving_5"}),
"mlk_opening": ("Top Opening Weekend for MLK Day", "/chart/top_opening_holiday_weekends/", {"by_occasion", "us_mlkday_weekend"}),
"easter_opening": ("Top Opening Weekend for Easter", "/chart/top_opening_holiday_weekends/", {"by_occasion", "easter_weekend"}),
"memorial_opening": ("Top Opening Weekend for Memorial Day", "/chart/top_opening_holiday_weekends/", {"by_occasion", "us_memorialday_weekend"}),
"labor_opening": ("Top Opening Weekend for Labor Day", "/chart/top_opening_holiday_weekends/", {"by_occasion", "us_laborday_weekend"}),
"president_opening": ("Top Opening Weekend for MLK Day", "/chart/top_opening_holiday_weekends/", {"by_occasion", "us_presidentsday_weekend"}),
"thanksgiving_3_opening": ("Top 3 Day Opening Weekend for Thanksgiving", "/chart/top_thanksgiving_openings/", {"by_occasion", "us_thanksgiving_3"}),
"thanksgiving_5_opening": ("Top 5 Day Opening Weekend for Thanksgiving", "/chart/top_thanksgiving_openings/", {"by_occasion", "us_thanksgiving_5"}),
"opening_week": ("Top Opening Week", "/chart/top_opening_week/", None),
"biggest_theater_drop": ("Biggest Theater Drops", "/chart/biggest_third_weekend_num_theaters_drop/", None),
"opening_day": ("Top Opening Day", "/chart/top_opening_day/", None),
"single_day_grosses": ("Top Day", "/chart/release_top_daily_gross/", None),
"christmas_day_gross": ("Top Christmas Day", "/chart/release_top_holiday_gross/", {"by_occasion": "christmas_day"}),
"new_years_day_gross": ("Top New Years Day", "/chart/release_top_holiday_gross/", {"by_occasion": "newyearsday"}),
"friday": ("Top Friday", "/chart/release_top_daily_gross_by_dow/", {"by_occasion": "friday"}),
"saturday": ("Top Saturday", "/chart/release_top_daily_gross_by_dow/", {"by_occasion": "saturday"}),
"sunday": ("Top Sunday", "/chart/release_top_daily_gross_by_dow/", {"by_occasion": "sunday"}),
"monday": ("Top Monday", "/chart/release_top_daily_gross_by_dow/", {"by_occasion": "monday"}),
"tuesday": ("Top Tuesday", "/chart/release_top_daily_gross_by_dow/", {"by_occasion": "tuesday"}),
"wednesday": ("Top Wednesday", "/chart/release_top_daily_gross_by_dow/", {"by_occasion": "wednesday"}),
"thursday": ("Top Thursday", "/chart/release_top_daily_gross_by_dow/", {"by_occasion": "thursday"}),
"friday_non_opening": ("Top Friday Non-Opening", "/chart/top_non_opening_by_dow/", {"by_occasion": "friday"}),
"saturday_non_opening": ("Top Saturday Non-Opening", "/chart/top_non_opening_by_dow/", {"by_occasion": "saturday"}),
"sunday_non_opening": ("Top Sunday Non-Opening", "/chart/top_non_opening_by_dow/", {"by_occasion": "sunday"}),
"monday_non_opening": ("Top Monday Non-Opening", "/chart/top_non_opening_by_dow/", {"by_occasion": "monday"}),
"tuesday_non_opening": ("Top Tuesday Non-Opening", "/chart/top_non_opening_by_dow/", {"by_occasion": "tuesday"}),
"wednesday_non_opening": ("Top Wednesday Non-Opening", "/chart/top_non_opening_by_dow/", {"by_occasion": "wednesday"}),
"thursday_non_opening": ("Top Thursday Non-Opening", "/chart/top_non_opening_by_dow/", {"by_occasion": "thursday"}),
}
chart_options = ["domestic", "worldwide"]
content_rating_options = {
"g": "G",
"g/pg": "G%2FPG",
"pg": "PG",
"pg-13": "PG-13",
"r": "R",
"nc-17": "NC-17",
}
never_in_options = {
"1": ("#1", "never_1"),
"5": ("the Top 5", "never_5"),
"10": ("the Top 10", "never_10"),
}
intl_range_options = ["weekend", "monthly", "quarterly", "yearly"]
dome_range_options = intl_range_options + ["daily", "weekly", "season", "holiday"]
year_options = ["current"] + [str(t) for t in range(1977, datetime.now().year + 1)]
quarter_options = ["current", "q1", "q2", "q3", "q4"]
quarters = {1: "q1", 2: "q1", 3: "q1", 4: "q2", 5: "q2", 6: "q2", 7: "q3", 8: "q3", 9: "q3", 10: "q4", 11: "q4", 12: "q4"}
season_options = ["current", "winter", "spring", "summer", "fall", "holiday"]
seasons = {1: "winter", 2: "winter", 3: "spring", 4: "spring", 5: "summer", 6: "summer", 7: "summer", 8: "summer", 9: "fall", 10: "fall", 11: "holiday", 12: "holiday"}
holiday_options = {
"new_years_day": ("New Year's Day", "newyearsday"),
"new_year_weekend": ("New Year Weekend", "us_newyear_weekend"),
"mlk_day": ("MLK Day", "us_mlkday"),
"mlk_day_weekend": ("MLK Day Weekend", "us_mlkday_weekend"),
"presidents_day": ("President's Day", "us_presidentsday"),
"presidents_day_weekend": ("President's Day Weekend", "us_presidentsday_weekend"),
"easter": ("Easter", "easter_sunday"),
"easter_weekend": ("Easter Weekend", "easter_weekend"),
"memorial_day": ("Memorial Day", "us_memorialday"),
"memorial_day_weekend": ("Memorial Day Weekend", "us_memorialday_weekend"),
"independence_day": ("Independence Day", "us_july4"),
"independence_day_weekend": ("Independence Day Weekend", "us_july4_weekend"),
"labor_day": ("Labor Day", "us_laborday"),
"labor_day_weekend": ("Labor Day Weekend", "us_laborday_weekend"),
"indigenous_day": ("Indigenous People's Day", "us_indig_peoples_day"),
"indigenous_day_weekend": ("", "us_indig_peoples_day_weekend"),
"halloween": ("Halloween", "halloween"),
"thanksgiving": ("Thanksgiving", "us_thanksgiving"),
"thanksgiving_3": ("Thanksgiving Weekend", "us_thanksgiving_3"),
"thanksgiving_4": ("Thanksgiving 4-Day Weekend", "us_thanksgiving_4"),
"thanksgiving_5": ("Thanksgiving 5-Day Weekend", "us_thanksgiving_5"),
"post_thanksgiving_weekend": ("Post-Thanksgiving Weekend", "us_post_thanksgiving_weekend"),
"christmas_day": ("Christmas Day", "christmas_day"),
"christmas_weekend": ("Christmas Weekend", "us_christmas_weekend"),
"new_years_eve": ("New Year's Eve", "newyearseve")
}
base_url = "https://www.boxofficemojo.com"
class BoxOfficeMojo:
def __init__(self, config):
self.config = config
self._never_options = None
self._intl_options = None
self._year_options = None
def _options(self, url, nav_type="area"):
output = {}
options = self._request(url, xpath=f"//select[@id='{nav_type}-navSelector']/option")
for option in options:
query = parse_qs(urlparse(option.xpath("@value")[0]).query)
output[option.xpath("text()")[0].lower()] = query["area"][0] if "area" in query else ""
return output
@property
def never_options(self):
if self._never_options is None:
self._never_options = self._options("/chart/never_in_top/")
return self._never_options
@property
def intl_options(self):
if self._intl_options is None:
self._intl_options = self._options("/intl/")
return self._intl_options
@property
def year_options(self):
if self._year_options is None:
self._year_options = [y for y in self._options("/year/world/", nav_type="year")]
return self._year_options
def _request(self, url, xpath=None, params=None):
logger.trace(f"URL: {base_url}{url}")
if params:
logger.trace(f"Params: {params}")
response = self.config.get_html(f"{base_url}{url}", headers=util.header(), params=params)
return response.xpath(xpath) if xpath else response
def _parse_list(self, url, params, limit):
response = self._request(url, params=params)
total_html = response.xpath("//li[contains(@class, 'mojo-pagination-button-center')]/a/text()")
total = int(total_html[0].replace(",", "").split(" ")[2]) if total_html else 0
if total and (limit < 1 or total < limit):
limit = total
pages = int((limit - 1) / 200) + 1 if total else 0
for field_name in ["release ", "title", "release_group"]:
output = response.xpath(f"//td[contains(@class, 'mojo-field-type-{field_name}')]/a/@href")
if output:
break
for i in range(1, pages):
response = self._request(url, params={"offset": 200 * i})
output.extend(response.xpath(f"//td[contains(@class, 'mojo-field-type-{field_name}')]/a/@href"))
if not limit or len(output) < limit:
limit = len(output)
return [i[:i.index("?")] for i in output[:limit]]
def _imdb(self, url):
response = self._request(url)
imdb_url = response.xpath("//select[@id='releasegroup-picker-navSelector']/option[text()='All Releases']/@value")
if not imdb_url:
raise Failed(f"Mojo Error: IMDb ID not found at {base_url}{url}")
return imdb_url[0][7:-1]
def get_imdb_ids(self, method, data):
params = None
if method == "mojo_record":
text, url, params = top_options[data["chart"]]
elif method == "mojo_world":
text = f"{data['year']} Worldwide Box Office"
url = f"/year/world/{data['year']}/"
elif method == "mojo_all_time":
text = f"Top Lifetime {data['chart'].capitalize()}"
if data["content_rating_filter"] is None:
url = "/chart/top_lifetime_gross/" if data["chart"] == "domestic" else "/chart/ww_top_lifetime_gross/"
else:
text += f" {data['content_rating_filter'].upper()}"
url = f"/chart/mpaa_title_lifetime_gross/"
params = {"by_mpaa": content_rating_options[data['content_rating_filter']]}
text += " Grosses"
elif method == "mojo_never":
pretty, arg_key = never_in_options[data["never"]]
text = f"Top-Grossing Movies That Never Hit {pretty} {data['chart'].capitalize()}"
url = f"/chart/never_in_top/"
params = {"by_rank_threshold": data["never"]}
if data["chart"] != "domestic":
params["area"] = self.never_options[data["chart"]]
else:
chart = data["chart"].capitalize() if "chart" in data else "Domestic"
if data["range"] == "daily":
day = datetime.strptime(data["range_data"], "%Y-%m-%d")
day = day.strftime("%b {th}, %Y").replace("{th}", num2words(day.day, to='ordinal_num'))
chart_title = f"{day}"
url = f"/date/{data['range_data']}/"
elif data["range"] == "weekend":
chart_title = f"Weekend {data['range_data']} {data['year']}"
url = f"/weekend/{data['year']}W{data['range_data']:02}/"
elif data["range"] == "weekly":
chart_title = f"Week {data['range_data']} {data['year']}"
url = f"/weekly/{data['year']}W{data['range_data']:02}/"
elif data["range"] == "monthly":
chart_title = f"{data['range_data'].capitalize()} {data['year']}"
url = f"/month/{data['range_data']}/{data['year']}/"
elif data["range"] == "quarterly":
chart_title = f"{data['range_data'].capitalize()} {data['year']}"
url = f"/quarter/{data['range_data']}/{data['year']}/"
elif data["range"] == "season":
chart_title = f"{data['range_data'].capitalize()} {data['year']}"
url = f"/season/{data['range_data']}/{data['year']}/"
elif data["range"] == "holiday":
title, slug = holiday_options[data["range_data"]]
chart_title = f"{title} {data['year']}"
url = f"/holiday/{slug}/{data['year']}/"
else:
chart_title = f"{data['year']}"
url = f"/year/{data['year']}/"
text = f"{chart} Box Office For {chart_title}"
if data["limit"]:
text += f" ({data['limit']})"
logger.info(f"Processing {method.replace('_', ' ').title()}: {text}")
items = self._parse_list(url, params, data["limit"])
if not items:
raise Failed(f"Mojo Error: No List Items found in {method}: {data}")
ids = []
total_items = len(items)
for i, item in enumerate(items, 1):
logger.ghost(f"Finding IMDb ID {i}/{total_items}")
if "title" in item:
imdb_id = item[7:-1]
else:
imdb_id = None
expired = None
if self.config.Cache:
imdb_id, expired = self.config.Cache.query_letterboxd_map(item)
if not imdb_id or expired is not False:
try:
imdb_id = self._imdb(item)
except Failed as e:
logger.error(e)
continue
if self.config.Cache:
self.config.Cache.update_letterboxd_map(expired, item, imdb_id)
ids.append((imdb_id, "imdb"))
logger.info(f"Processed {total_items} IMDb IDs")
return ids

@ -76,10 +76,12 @@ mod_displays = {
".gt": "is greater than", ".gte": "is greater than or equal", ".lt": "is less than", ".lte": "is less than or equal", ".regex": "is"
}
pretty_days = {0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday", 4: "Friday", 5: "Saturday", 6: "Sunday"}
lower_days = {v.lower(): k for k, v in pretty_days.items()}
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"
}
lower_months = {v.lower(): k for k, v in pretty_months.items()}
seasons = ["current", "winter", "spring", "summer", "fall"]
advance_tags_to_edit = {
"Movie": ["metadata_language", "use_original_title"],
@ -782,7 +784,7 @@ def parse(error, attribute, data, datatype=None, methods=None, parent=None, defa
if options is None or (options and (v in options or (datatype == "strlist" and str(v) in options))):
final_list.append(str(v) if datatype == "strlist" else v)
elif options:
raise Failed(f"{error} Error: {display} {v} is invalid; Options include: {', '.join(options)}")
raise Failed(f"{error} Error: {display} {v} is invalid; Options include: {', '.join([o for o in options])}")
return final_list
elif datatype == "intlist":
if value:
@ -861,7 +863,9 @@ def parse(error, attribute, data, datatype=None, methods=None, parent=None, defa
message = f"{message} separated by a {range_split}"
elif datatype == "date":
try:
return validate_date(datetime.now() if data == "today" else data, return_as=date_return)
if default in ["today", "current"]:
default = validate_date(datetime.now(), return_as=date_return)
return validate_date(datetime.now() if data in ["today", "current"] else data, return_as=date_return)
except Failed as e:
message = f"{e}"
elif (translation is not None and str(value).lower() not in translation) or \

@ -7,9 +7,10 @@ pillow==10.2.0
PlexAPI==4.15.7
psutil==5.9.7
python-dotenv==1.0.0
python-dateutil==2.8.2
requests==2.31.0
retrying==1.3.4
ruamel.yaml==0.18.5
schedule==1.2.1
tmdbapis==1.2.6
setuptools==69.0.3
setuptools==69.0.3
tmdbapis==1.2.6
Loading…
Cancel
Save