From cd8e77cdc342911c4c2c27f1e0184e2f20adfc87 Mon Sep 17 00:00:00 2001 From: simonc56 Date: Tue, 26 Dec 2023 14:15:29 +0100 Subject: [PATCH 1/7] markPlayed markUnplayed for is_discover medias --- plextraktsync/plex/PlexApi.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plextraktsync/plex/PlexApi.py b/plextraktsync/plex/PlexApi.py index c6470eebd1..b1ec92f60f 100644 --- a/plextraktsync/plex/PlexApi.py +++ b/plextraktsync/plex/PlexApi.py @@ -200,12 +200,20 @@ def history(self, m, device=False, account=False): yield h @retry() - def mark_watched(self, m): - m.markPlayed() + def mark_watched(self, m, is_discover=False): + if is_discover: + acc = self.account + acc.markPlayed(m) + else: + m.markPlayed() @retry() - def mark_unwatched(self, m): - m.markUnplayed() + def mark_unwatched(self, m, is_discover=False): + if is_discover: + acc = self.account + acc.markUnplayed(m) + else: + m.markUnplayed() def has_sessions(self): try: From c8d3da15f832fbb9f7e16891601725abdf9a1a19 Mon Sep 17 00:00:00 2001 From: simonc56 Date: Tue, 26 Dec 2023 14:18:01 +0100 Subject: [PATCH 2/7] trakt watched_movies is now a dict {trakt_id: Media} --- plextraktsync/trakt/TraktApi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plextraktsync/trakt/TraktApi.py b/plextraktsync/trakt/TraktApi.py index 9d41526c18..1b4e32bd20 100644 --- a/plextraktsync/trakt/TraktApi.py +++ b/plextraktsync/trakt/TraktApi.py @@ -69,7 +69,7 @@ def liked_lists(self): @rate_limit() @retry() def watched_movies(self): - return set(map(lambda m: m.trakt, self.me.watched_movies)) + return {m.trakt: m for m in self.me.watched_movies} @cached_property @rate_limit() @@ -162,7 +162,7 @@ def rate(self, m, rating): @retry() def mark_watched(self, m: TraktMedia, time, show_trakt_id=None): if m.media_type == "movies": - self.watched_movies.add(m.trakt) + self.watched_movies[m.trakt] = m elif m.media_type == "episodes" and show_trakt_id: self.watched_shows.add(show_trakt_id, m.season, m.number) else: From f1a11443a0597e8d941a54baa5de172e17a508e2 Mon Sep 17 00:00:00 2001 From: simonc56 Date: Tue, 26 Dec 2023 14:20:52 +0100 Subject: [PATCH 3/7] Yield trakt Medias from ratings list --- plextraktsync/trakt/TraktRatingCollection.py | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plextraktsync/trakt/TraktRatingCollection.py b/plextraktsync/trakt/TraktRatingCollection.py index 3547f32f57..b384584174 100644 --- a/plextraktsync/trakt/TraktRatingCollection.py +++ b/plextraktsync/trakt/TraktRatingCollection.py @@ -4,23 +4,60 @@ from plextraktsync.decorators.flatten import flatten_dict +from trakt.movies import Movie +from trakt.tv import TVEpisode, TVSeason, TVShow + if TYPE_CHECKING: from plextraktsync.trakt.TraktApi import TraktApi +trakt_types = { + "movie": Movie, + "show": TVShow, + "season": TVSeason, + "episode": TVEpisode, +} + class TraktRatingCollection(dict): def __init__(self, trakt: TraktApi): super().__init__() self.trakt = trakt + self.items = dict() def __missing__(self, media_type: str): ratings = self.ratings(media_type) self[media_type] = ratings + self.items[media_type] = self.rating_items(media_type) return ratings @flatten_dict def ratings(self, media_type: str): + """Yield trakt id and rating of all rated media_type""" index = media_type.rstrip("s") for r in self.trakt.get_ratings(media_type): yield r[index]["ids"]["trakt"], r["rating"] + + def rating_items(self, media_type: str): + """Yield TraktMedia of all rated media_type""" + index = media_type.rstrip("s") + for r in self.trakt.get_ratings(media_type): + title = r[index].get("title") + if index == "movie": + show = season = number = None + else: + show = title = r["show"]["title"] + if index == "episode": + season = r[index]["season"] + number = r[index]["number"] + if index == "season": + season = r[index]["number"] + number = None + ids = r[index]["ids"] + yield trakt_types[index]( + title=title, + show=show, + season=season, + number=number, + ids=ids, + ) From 9d65e23c0d475c9d5d727ed558fedf6c28b7cab8 Mon Sep 17 00:00:00 2001 From: simonc56 Date: Tue, 26 Dec 2023 14:25:15 +0100 Subject: [PATCH 4/7] use is_discover for watched toggle --- plextraktsync/media.py | 2 +- plextraktsync/plex/PlexApi.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plextraktsync/media.py b/plextraktsync/media.py index 27e4245aab..1a8ac8539e 100644 --- a/plextraktsync/media.py +++ b/plextraktsync/media.py @@ -203,7 +203,7 @@ def mark_watched_trakt(self): ) def mark_watched_plex(self): - self.plex_api.mark_watched(self.plex.item) + self.plex_api.mark_watched(self.plex.item, self.plex.is_discover) @property def trakt_rating(self): diff --git a/plextraktsync/plex/PlexApi.py b/plextraktsync/plex/PlexApi.py index b1ec92f60f..94daf07d91 100644 --- a/plextraktsync/plex/PlexApi.py +++ b/plextraktsync/plex/PlexApi.py @@ -293,9 +293,10 @@ def search_online(self, title: str, media_type: str): def reset_show(self, show: Show, reset_date: datetime): reset_count = 0 for ep in show.watched(): - ep_seen_date = PlexLibraryItem(ep).seen_date.replace(tzinfo=None) + item = PlexLibraryItem(ep) + ep_seen_date = item.seen_date.replace(tzinfo=None) if ep_seen_date < reset_date: - self.mark_unwatched(ep) + self.mark_unwatched(ep, item.is_discover) reset_count += 1 else: logger.debug( From 0b68aec19191a277d9d2d8a88c9673663041b5be Mon Sep 17 00:00:00 2001 From: simonc56 Date: Tue, 26 Dec 2023 14:25:39 +0100 Subject: [PATCH 5/7] add plex_online boolean in config --- plextraktsync/config.default.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plextraktsync/config.default.yml b/plextraktsync/config.default.yml index 6e7f59a858..a453fddeeb 100644 --- a/plextraktsync/config.default.yml +++ b/plextraktsync/config.default.yml @@ -70,6 +70,9 @@ sync: # trakt - Trakt ratings have priority. Existing Plex ratings are overwritten. # plex - Plex ratings have priority. Existing Trakt ratings are overwritten. rating_priority: plex + # watched_status and ratings of media not actually in your Plex server can + # be synced with your Plex online account + plex_online: false # settings for 'watch' command watch: From 6fc42a559afdcd504b42e1f73a68189ba0b32e56 Mon Sep 17 00:00:00 2001 From: simonc56 Date: Tue, 26 Dec 2023 14:47:34 +0100 Subject: [PATCH 6/7] sync medias from Plex online account --- plextraktsync/sync.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/plextraktsync/sync.py b/plextraktsync/sync.py index f209471503..ed22dedc0f 100644 --- a/plextraktsync/sync.py +++ b/plextraktsync/sync.py @@ -68,12 +68,33 @@ def sync(self, walker: Walker, dry_run=False): self.sync_watched(movie, dry_run=dry_run) if not is_partial: listutil.addPlexItemToLists(movie) - if self.config.clear_collected: - movie_trakt_ids.add(movie.trakt_id) + movie_trakt_ids.add(movie.trakt_id) - if movie_trakt_ids: + if self.config.clear_collected and movie_trakt_ids: self.clear_collected(self.trakt.movie_collection, movie_trakt_ids) + remaining_movies_ids = ( + set(self.trakt.watched_movies.keys()).union( + set(self.trakt.ratings["movies"]) + ) - movie_trakt_ids + ) + + if remaining_movies_ids and not is_partial and self.config["plex_online"]: + """Sync ratings and watched status of movies not in Plex library""" + items = set(self.trakt.watched_movies.values()).union( + set(self.trakt.ratings.items["movies"]) + ) + # items is a set() of trakt.movies.Movies already watched or rated (can a user rate without watch?) + sync_items = [] + for tm in items: + if tm.trakt in remaining_movies_ids: + sync_items.append(tm) + remaining_movies_ids.remove(tm.trakt) + for movie in walker.media_from_traktlist(sync_items, title="Trakt watched movies"): + self.sync_watched(movie, dry_run=dry_run) + # Rating medias from Plex Discover not implemented yet https://github.com/pkkid/python-plexapi/issues/1137 + # self.sync_ratings(movie, dry_run=dry_run) + shows = set() episode_trakt_ids = set() for episode in walker.find_episodes(): From 9fa006b3ecc093239e0bd9dd5444766e1cb7b934 Mon Sep 17 00:00:00 2001 From: simonc56 Date: Tue, 26 Dec 2023 21:59:39 +0100 Subject: [PATCH 7/7] warn & bypass if plex search failed --- plextraktsync/media.py | 3 +++ plextraktsync/sync.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plextraktsync/media.py b/plextraktsync/media.py index 1a8ac8539e..5314d2ba7f 100644 --- a/plextraktsync/media.py +++ b/plextraktsync/media.py @@ -284,6 +284,9 @@ def resolve_trakt(self, tm: TraktItem) -> Media: """Find Plex media from Trakt id using Plex Search and Discover""" result = self.plex.search_online(tm.item.title, tm.type) pm = self._guid_match(result, tm) + if pm is None: + logger.warning(f"Skipping '{tm.item.title}': not found on Plex Discover") + return None return self.make_media(pm, tm.item) def make_media(self, plex: PlexLibraryItem, trakt): diff --git a/plextraktsync/sync.py b/plextraktsync/sync.py index ed22dedc0f..e66d9865c0 100644 --- a/plextraktsync/sync.py +++ b/plextraktsync/sync.py @@ -91,9 +91,10 @@ def sync(self, walker: Walker, dry_run=False): sync_items.append(tm) remaining_movies_ids.remove(tm.trakt) for movie in walker.media_from_traktlist(sync_items, title="Trakt watched movies"): - self.sync_watched(movie, dry_run=dry_run) - # Rating medias from Plex Discover not implemented yet https://github.com/pkkid/python-plexapi/issues/1137 - # self.sync_ratings(movie, dry_run=dry_run) + if movie is not None: + self.sync_watched(movie, dry_run=dry_run) + # Rating medias from Plex Discover not implemented yet https://github.com/pkkid/python-plexapi/issues/1137 + # self.sync_ratings(movie, dry_run=dry_run) shows = set() episode_trakt_ids = set()