Plex-Meta-Manager/modules/trakttv.py

187 lines
9.7 KiB
Python
Raw Normal View History

2021-01-21 21:42:31 +00:00
import logging, requests, webbrowser
2021-01-20 21:37:59 +00:00
from modules import util
from modules.util import Failed, TimeoutExpired
from retrying import retry
from ruamel import yaml
2021-06-14 15:24:11 +00:00
from trakt import Trakt as TraktAPI
2021-01-20 21:37:59 +00:00
from trakt.objects.episode import Episode
from trakt.objects.movie import Movie
from trakt.objects.season import Season
from trakt.objects.show import Show
logger = logging.getLogger("Plex Meta Manager")
2021-03-30 05:50:53 +00:00
builders = [
"trakt_collected",
"trakt_collection",
"trakt_list",
"trakt_list_details",
"trakt_popular",
"trakt_recommended",
"trakt_trending",
"trakt_watched",
"trakt_watchlist"
]
2021-06-14 15:24:11 +00:00
class Trakt:
2021-01-20 21:37:59 +00:00
def __init__(self, params, authorization=None):
self.base_url = "https://api.trakt.tv"
2021-01-20 21:37:59 +00:00
self.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
self.aliases = {
"trakt_trending": "Trakt Trending",
"trakt_watchlist": "Trakt Watchlist",
"trakt_list": "Trakt List"
}
self.client_id = params["client_id"]
self.client_secret = params["client_secret"]
self.config_path = params["config_path"]
self.authorization = authorization
2021-06-14 15:24:11 +00:00
TraktAPI.configuration.defaults.client(self.client_id, self.client_secret)
2021-05-07 19:53:54 +00:00
if not self._save(self.authorization):
if not self._refresh():
self._authorization()
2021-01-20 21:37:59 +00:00
2021-05-07 19:53:54 +00:00
def _authorization(self):
2021-06-14 15:24:11 +00:00
url = TraktAPI["oauth"].authorize_url(self.redirect_uri)
2021-02-24 06:44:06 +00:00
logger.info(f"Navigate to: {url}")
2021-01-20 21:37:59 +00:00
logger.info("If you get an OAuth error your client_id or client_secret is invalid")
webbrowser.open(url, new=2)
try: pin = util.logger_input("Trakt pin (case insensitive)", timeout=300).strip()
except TimeoutExpired: raise Failed("Input Timeout: Trakt pin required.")
if not pin: raise Failed("Trakt Error: No input Trakt pin required.")
2021-06-14 15:24:11 +00:00
new_authorization = TraktAPI["oauth"].token(pin, self.redirect_uri)
2021-01-20 21:37:59 +00:00
if not new_authorization:
raise Failed("Trakt Error: Invalid trakt pin. If you're sure you typed it in correctly your client_id or client_secret may be invalid")
2021-05-07 19:53:54 +00:00
if not self._save(new_authorization):
2021-01-20 21:37:59 +00:00
raise Failed("Trakt Error: New Authorization Failed")
2021-05-07 19:53:54 +00:00
def _check(self, authorization):
2021-01-20 21:37:59 +00:00
try:
2021-06-14 15:24:11 +00:00
with TraktAPI.configuration.oauth.from_response(authorization, refresh=True):
if TraktAPI["users/settings"].get():
2021-01-20 21:37:59 +00:00
return True
except ValueError: pass
return False
2021-05-07 19:53:54 +00:00
def _refresh(self):
2021-01-20 21:37:59 +00:00
if self.authorization and "refresh_token" in self.authorization and self.authorization["refresh_token"]:
logger.info("Refreshing Access Token...")
2021-06-14 15:24:11 +00:00
refreshed_authorization = TraktAPI["oauth"].token_refresh(self.authorization["refresh_token"], self.redirect_uri)
2021-05-07 19:53:54 +00:00
return self._save(refreshed_authorization)
2021-01-20 21:37:59 +00:00
return False
2021-05-07 19:53:54 +00:00
def _save(self, authorization):
if authorization and self._check(authorization):
2021-01-20 21:37:59 +00:00
if self.authorization != authorization:
yaml.YAML().allow_duplicate_keys = True
config, ind, bsi = yaml.util.load_yaml_guess_indent(open(self.config_path))
config["trakt"]["authorization"] = {
"access_token": authorization["access_token"],
"token_type": authorization["token_type"],
"expires_in": authorization["expires_in"],
"refresh_token": authorization["refresh_token"],
"scope": authorization["scope"],
"created_at": authorization["created_at"]
}
2021-02-24 06:44:06 +00:00
logger.info(f"Saving authorization information to {self.config_path}")
2021-01-20 21:37:59 +00:00
yaml.round_trip_dump(config, open(self.config_path, "w"), indent=ind, block_seq_indent=bsi)
self.authorization = authorization
2021-06-14 15:24:11 +00:00
TraktAPI.configuration.defaults.oauth.from_response(self.authorization)
2021-01-20 21:37:59 +00:00
return True
return False
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
2021-05-08 00:40:07 +00:00
def convert(self, external_id, from_source, to_source, media_type):
2021-06-14 15:24:11 +00:00
lookup = TraktAPI["search"].lookup(external_id, from_source, media_type)
2021-01-20 21:37:59 +00:00
if lookup:
lookup = lookup[0] if isinstance(lookup, list) else lookup
2021-02-20 05:41:45 +00:00
if lookup.get_key(to_source):
2021-02-20 05:39:41 +00:00
return lookup.get_key(to_source) if to_source == "imdb" else int(lookup.get_key(to_source))
2021-05-08 04:05:10 +00:00
raise Failed(f"Trakt Error: No {to_source.upper().replace('B', 'b')} ID found for {from_source.upper().replace('B', 'b')} ID: {external_id}")
2021-01-20 21:37:59 +00:00
2021-03-30 05:49:10 +00:00
def collection(self, data, is_movie):
2021-05-07 19:53:54 +00:00
return self._user_list("collection", data, is_movie)
2021-03-30 05:49:10 +00:00
2021-05-07 19:53:54 +00:00
def _watchlist(self, data, is_movie):
return self._user_list("watchlist", data, is_movie)
2021-03-30 05:49:10 +00:00
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
2021-05-07 19:53:54 +00:00
def _user_list(self, list_type, data, is_movie):
2021-06-14 15:24:11 +00:00
items = TraktAPI[f"users/{data}/{list_type}"].movies() if is_movie else TraktAPI[f"users/{data}/{list_type}"].shows()
2021-01-20 21:37:59 +00:00
if items is None: raise Failed("Trakt Error: No List found")
else: return [i for i in items]
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def standard_list(self, data):
2021-06-14 15:24:11 +00:00
try: trakt_list = TraktAPI[requests.utils.urlparse(data).path].get()
2021-03-08 15:25:35 +00:00
except AttributeError: trakt_list = None
if trakt_list is None: raise Failed("Trakt Error: No List found")
else: return trakt_list
2021-01-20 21:37:59 +00:00
@retry(stop_max_attempt_number=6, wait_fixed=10000)
2021-05-07 19:53:54 +00:00
def _request(self, url):
return requests.get(url, headers={"Content-Type": "application/json", "trakt-api-version": "2", "trakt-api-key": self.client_id}).json()
2021-05-07 19:53:54 +00:00
def _collection(self, username, is_movie):
items = self._request(f"{self.base_url}/users/{username}/collection/{'movies' if is_movie else 'shows'}")
2021-03-27 07:30:07 +00:00
if is_movie: return [item["movie"]["ids"]["tmdb"] for item in items], []
else: return [], [item["show"]["ids"]["tvdb"] for item in items]
2021-05-07 19:53:54 +00:00
def _pagenation(self, pagenation, amount, is_movie):
items = self._request(f"{self.base_url}/{'movies' if is_movie else 'shows'}/{pagenation}?limit={amount}")
2021-03-18 13:41:30 +00:00
if pagenation == "popular" and is_movie: return [item["ids"]["tmdb"] for item in items], []
elif pagenation == "popular": return [], [item["ids"]["tvdb"] for item in items]
elif is_movie: return [item["movie"]["ids"]["tmdb"] for item in items], []
else: return [], [item["show"]["ids"]["tvdb"] for item in items]
2021-03-27 07:30:07 +00:00
def validate_trakt(self, values, trakt_type=None, is_movie=None):
2021-01-20 21:37:59 +00:00
trakt_values = []
for value in values:
try:
2021-03-27 07:30:07 +00:00
if trakt_type == "watchlist" and is_movie is not None:
2021-05-07 19:53:54 +00:00
self._watchlist(value, is_movie)
2021-03-27 07:30:07 +00:00
elif trakt_type == "collection" and is_movie is not None:
2021-05-07 19:53:54 +00:00
self._collection(value, is_movie)
2021-03-27 07:30:07 +00:00
else:
self.standard_list(value)
2021-01-20 21:37:59 +00:00
trakt_values.append(value)
except Failed as e:
logger.error(e)
if len(trakt_values) == 0:
2021-03-27 07:30:07 +00:00
if trakt_type == "watchlist" and is_movie is not None:
raise Failed(f"Trakt Error: No valid Trakt Watchlists in {values}")
elif trakt_type == "collection" and is_movie is not None:
raise Failed(f"Trakt Error: No valid Trakt Collections in {values}")
else:
raise Failed(f"Trakt Error: No valid Trakt Lists in {values}")
2021-01-20 21:37:59 +00:00
return trakt_values
2021-05-09 05:37:45 +00:00
def get_items(self, method, data, is_movie):
2021-01-20 21:37:59 +00:00
pretty = self.aliases[method] if method in self.aliases else method
media_type = "Movie" if is_movie else "Show"
2021-03-10 16:58:39 +00:00
if method in ["trakt_trending", "trakt_popular", "trakt_recommended", "trakt_watched", "trakt_collected"]:
2021-05-07 19:53:54 +00:00
movie_ids, show_ids = self._pagenation(method[6:], data, is_movie)
2021-05-09 05:37:45 +00:00
logger.info(f"Processing {pretty}: {data} {media_type}{'' if data == 1 else 's'}")
2021-03-27 07:30:07 +00:00
elif method == "trakt_collection":
2021-05-07 19:53:54 +00:00
movie_ids, show_ids = self._collection(data, is_movie)
2021-05-09 05:37:45 +00:00
logger.info(f"Processing {pretty} {media_type}s for {data}")
2021-01-20 21:37:59 +00:00
else:
show_ids = []
movie_ids = []
2021-05-07 19:53:54 +00:00
if method == "trakt_watchlist": trakt_items = self._watchlist(data, is_movie)
2021-03-08 15:25:35 +00:00
elif method == "trakt_list": trakt_items = self.standard_list(data).items()
2021-02-24 06:44:06 +00:00
else: raise Failed(f"Trakt Error: Method {method} not supported")
2021-05-09 05:37:45 +00:00
logger.info(f"Processing {pretty}: {data}")
for trakt_item in trakt_items:
2021-05-07 19:53:54 +00:00
if isinstance(trakt_item, Movie):
movie_ids.append(int(trakt_item.get_key("tmdb")))
elif isinstance(trakt_item, Show) and trakt_item.pk[1] not in show_ids:
show_ids.append(int(trakt_item.pk[1]))
elif (isinstance(trakt_item, (Season, Episode))) and trakt_item.show.pk[1] not in show_ids:
show_ids.append(int(trakt_item.show.pk[1]))
2021-05-09 05:37:45 +00:00
logger.debug(f"Trakt {media_type} Found: {trakt_items}")
2021-05-24 03:38:46 +00:00
logger.debug("")
2021-05-09 05:37:45 +00:00
logger.debug(f"TMDb IDs Found: {movie_ids}")
logger.debug(f"TVDb IDs Found: {show_ids}")
2021-01-20 21:37:59 +00:00
return movie_ids, show_ids