[75] #557, #770 add `sync_to_trakt_list` to Collections/Playlists

pull/865/head
meisnate12 3 years ago
parent 6c44ea858b
commit b9a0f87c40

@ -1 +1 @@
1.16.5-develop74 1.16.5-develop75

@ -418,6 +418,7 @@ class CollectionBuilder:
self.current_year = self.current_time.year self.current_year = self.current_time.year
self.url_theme = None self.url_theme = None
self.file_theme = None self.file_theme = None
self.sync_to_trakt_list = None
self.collection_poster = None self.collection_poster = None
self.collection_background = None self.collection_background = None
self.exists = False self.exists = False
@ -752,7 +753,7 @@ class CollectionBuilder:
self._tautulli(method_name, method_data) self._tautulli(method_name, method_data)
elif method_name in tmdb.builders: elif method_name in tmdb.builders:
self._tmdb(method_name, method_data) self._tmdb(method_name, method_data)
elif method_name in trakt.builders: elif method_name in trakt.builders or method_name == "sync_to_trakt_list":
self._trakt(method_name, method_data) self._trakt(method_name, method_data)
elif method_name in tvdb.builders: elif method_name in tvdb.builders:
self._tvdb(method_name, method_data) self._tvdb(method_name, method_data)
@ -1460,6 +1461,10 @@ class CollectionBuilder:
raise Failed(f"{self.Type} Error: {method_name} must be set to true") raise Failed(f"{self.Type} Error: {method_name} must be set to true")
elif method_name == "trakt_recommendations": elif method_name == "trakt_recommendations":
self.builders.append((method_name, util.parse(self.Type, method_name, method_data, datatype="int", default=10, maximum=100))) self.builders.append((method_name, util.parse(self.Type, method_name, method_data, datatype="int", default=10, maximum=100)))
elif method_name == "sync_to_trakt_list":
if method_data not in self.config.Trakt.slugs:
raise Failed(f"{self.Type} Error: {method_data} invalid. Options {', '.join(self.config.Trakt.slugs)}")
self.sync_to_trakt_list = method_data
elif method_name in trakt.builders: elif method_name in trakt.builders:
if method_name in ["trakt_chart", "trakt_userlist"]: if method_name in ["trakt_chart", "trakt_userlist"]:
trakt_dicts = method_data trakt_dicts = method_data
@ -2687,6 +2692,30 @@ class CollectionBuilder:
self.library.moveItem(self.obj, item, previous) self.library.moveItem(self.obj, item, previous)
previous = item previous = item
def sync_trakt_list(self):
logger.info("")
logger.separator(f"Syncing {self.name} {self.Type} to Trakt List {self.sync_to_trakt_list}", space=False, border=False)
logger.info("")
if self.obj:
self.obj.reload()
self.load_collection_items()
current_ids = []
for item in self.items:
for pl_library in self.libraries:
new_id = None
if isinstance(item, Movie) and item.ratingKey in pl_library.movie_rating_key_map:
new_id = (pl_library.movie_rating_key_map[item.ratingKey], "tmdb")
elif isinstance(item, Show) and item.ratingKey in pl_library.show_rating_key_map:
new_id = (pl_library.show_rating_key_map[item.ratingKey], "tvdb")
elif isinstance(item, Season) and item.parentRatingKey in pl_library.show_rating_key_map:
new_id = (f"{pl_library.show_rating_key_map[item.parentRatingKey]}_{item.seasonNumber}", "tvdb_season")
elif isinstance(item, Episode) and item.grandparentRatingKey in pl_library.show_rating_key_map:
new_id = (f"{pl_library.show_rating_key_map[item.grandparentRatingKey]}_{item.seasonNumber}_{item.episodeNumber}", "tvdb_episode")
if new_id:
current_ids.append(new_id)
break
self.config.Trakt.sync_list(self.sync_to_trakt_list, current_ids)
def delete(self): def delete(self):
output = "" output = ""
if self.obj: if self.obj:

@ -1,6 +1,7 @@
import requests, webbrowser import requests, time, webbrowser
from modules import util from modules import util
from modules.util import Failed, TimeoutExpired from modules.util import Failed, TimeoutExpired
from retrying import retry
from ruamel import yaml from ruamel import yaml
logger = util.logger logger = util.logger
@ -46,6 +47,7 @@ class Trakt:
if not self._save(self.authorization): if not self._save(self.authorization):
if not self._refresh(): if not self._refresh():
self._authorization() self._authorization()
self._slugs = None
self._movie_genres = None self._movie_genres = None
self._show_genres = None self._show_genres = None
self._movie_languages = None self._movie_languages = None
@ -55,6 +57,17 @@ class Trakt:
self._movie_certifications = None self._movie_certifications = None
self._show_certifications = None self._show_certifications = None
@property
def slugs(self):
if self._slugs is None:
items = []
try:
items = [i["ids"]["slug"] for i in self._request(f"/users/me/lists")]
except Failed:
pass
self._slugs = items
return self._slugs
@property @property
def movie_genres(self): def movie_genres(self):
if not self._movie_genres: if not self._movie_genres:
@ -175,7 +188,8 @@ class Trakt:
return True return True
return False return False
def _request(self, url, params=None): @retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def _request(self, url, params=None, json=None):
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": f"Bearer {self.authorization['access_token']}", "Authorization": f"Bearer {self.authorization['access_token']}",
@ -189,24 +203,28 @@ class Trakt:
current = 1 current = 1
if self.config.trace_mode: if self.config.trace_mode:
logger.debug(f"URL: {base_url}{url}") logger.debug(f"URL: {base_url}{url}")
if params:
logger.debug(f"Params: {params}")
if json:
logger.debug(f"JSON: {json}")
while current <= pages: while current <= pages:
if pages == 1: if pages > 1:
response = self.config.get(f"{base_url}{url}", headers=headers, params=params)
if "X-Pagination-Page-Count" in response.headers and not params:
pages = int(response.headers["X-Pagination-Page-Count"])
else:
params["page"] = current params["page"] = current
if json is not None:
response = self.config.post(f"{base_url}{url}", json=json, headers=headers)
else:
response = self.config.get(f"{base_url}{url}", headers=headers, params=params) response = self.config.get(f"{base_url}{url}", headers=headers, params=params)
if response.status_code == 200: if pages == 1 and "X-Pagination-Page-Count" in response.headers:
pages = int(response.headers["X-Pagination-Page-Count"])
if response.status_code >= 400:
raise Failed(f"({response.status_code}) {response.reason}")
json_data = response.json() json_data = response.json()
if self.config.trace_mode: if self.config.trace_mode:
logger.debug(f"Response: {json_data}") logger.debug(f"Response: {json_data}")
if isinstance(json_data, dict): if isinstance(json_data, dict):
return json_data return json_data
else: else:
output_json.extend(response.json()) output_json.extend(json_data)
else:
raise Failed(f"({response.status_code}) {response.reason}")
current += 1 current += 1
return output_json return output_json
@ -229,7 +247,7 @@ class Trakt:
except Failed: except Failed:
raise Failed(f"Trakt Error: List {data} not found") raise Failed(f"Trakt Error: List {data} not found")
def _parse(self, items, typeless=False, item_type=None): def _parse(self, items, typeless=False, item_type=None, trakt_ids=False):
ids = [] ids = []
for item in items: for item in items:
if typeless: if typeless:
@ -253,12 +271,55 @@ class Trakt:
if current_type in ["person", "list"]: if current_type in ["person", "list"]:
final_id = (final_id, data["name"]) final_id = (final_id, data["name"])
final_type = f"{id_type}_{current_type}" if current_type in ["episode", "season", "person"] else id_type final_type = f"{id_type}_{current_type}" if current_type in ["episode", "season", "person"] else id_type
ids.append((final_id, final_type)) ids.append((int(item["id"]), final_id, final_type) if trakt_ids else (final_id, final_type))
else: else:
name = data["name"] if current_type in ["person", "list"] else f"{data['title']} ({data['year']})" name = data["name"] if current_type in ["person", "list"] else f"{data['title']} ({data['year']})"
logger.error(f"Trakt Error: No {id_display} found for {name}") logger.error(f"Trakt Error: No {id_display} found for {name}")
return ids return ids
def _build_item_json(self, ids):
data = {}
for input_id, id_type in ids:
movies = id_type in ["imdb", "tmdb"]
shows = id_type in ["imdb", "tvdb", "tmdb_show", "tvdb_season", "tvdb_episode"]
if not movies and not shows:
continue
type_set = str(id_type).split("_")
id_set = str(input_id).split("_")
item = {"ids": {type_set[0]: id_set[0] if type_set[0] == "imdb" else int(id_set[0])}}
if id_type in ["tvdb_season", "tvdb_episode"]:
season_data = {"number": int(id_set[1])}
if id_type == "tvdb_episode":
season_data["episodes"] = [{"number": int(id_set[2])}]
item["seasons"] = [season_data]
if movies:
if "movies" not in data:
data["movies"] = []
data["movies"].append(item)
if shows:
if "shows" not in data:
data["shows"] = []
data["shows"].append(item)
return data
def sync_list(self, slug, ids):
current_ids = self._list(slug, urlparse=False)
add_ids = [id_set for id_set in ids if id_set not in current_ids]
if add_ids:
self._request(f"/users/me/lists/{slug}/items", json=self._build_item_json(add_ids))
time.sleep(1)
remove_ids = [id_set for id_set in current_ids if id_set not in ids]
if remove_ids:
self._request(f"/users/me/lists/{slug}/items/remove", json=self._build_item_json(remove_ids))
time.sleep(1)
trakt_ids = self._list(slug, urlparse=False, trakt_ids=True)
trakt_lookup = {f"{ty}_{i_id}": t_id for t_id, i_id, ty in trakt_ids}
rank_ids = [trakt_lookup[f"{ty}_{i_id}"] for i_id, ty in ids if f"{ty}_{i_id}" in trakt_lookup]
self._request(f"/users/me/lists/{slug}/items/reorder", json={"rank": rank_ids})
def all_user_lists(self, user="me"): def all_user_lists(self, user="me"):
try: try:
items = self._request(f"/users/{user}/lists") items = self._request(f"/users/{user}/lists")
@ -277,7 +338,7 @@ class Trakt:
def build_user_url(self, user, name): def build_user_url(self, user, name):
return f"{base_url.replace('api.', '')}/users/{user}/lists/{name}" return f"{base_url.replace('api.', '')}/users/{user}/lists/{name}"
def _list(self, data, urlparse=True): def _list(self, data, urlparse=True, trakt_ids=False):
try: try:
url = requests.utils.urlparse(data).path if urlparse else f"/users/me/lists/{data}" url = requests.utils.urlparse(data).path if urlparse else f"/users/me/lists/{data}"
items = self._request(f"{url}/items") items = self._request(f"{url}/items")
@ -285,7 +346,7 @@ class Trakt:
raise Failed(f"Trakt Error: List {data} not found") raise Failed(f"Trakt Error: List {data} not found")
if len(items) == 0: if len(items) == 0:
raise Failed(f"Trakt Error: List {data} is empty") raise Failed(f"Trakt Error: List {data} is empty")
return self._parse(items) return self._parse(items, trakt_ids=trakt_ids)
def _userlist(self, list_type, user, is_movie, sort_by=None): def _userlist(self, list_type, user, is_movie, sort_by=None):
try: try:

@ -601,7 +601,7 @@ def run_collection(config, library, metadata, requested_collections):
logger.info("") logger.info("")
logger.info(f"Plex Server Movie pre-roll video updated to {builder.server_preroll}") logger.info(f"Plex Server Movie pre-roll video updated to {builder.server_preroll}")
if valid and run_item_details and builder.builders and (builder.item_details or builder.custom_sort): if valid and run_item_details and builder.builders and (builder.item_details or builder.custom_sort or builder.sync_to_trakt_list):
try: try:
builder.load_collection_items() builder.load_collection_items()
except Failed: except Failed:
@ -612,6 +612,8 @@ def run_collection(config, library, metadata, requested_collections):
builder.update_item_details() builder.update_item_details()
if builder.custom_sort: if builder.custom_sort:
builder.sort_collection() builder.sort_collection()
if builder.sync_to_trakt_list:
builder.sync_trakt_list()
builder.send_notifications() builder.send_notifications()

Loading…
Cancel
Save