diff --git a/helpers/radarr.py b/helpers/radarr.py index c8732f2..bf6df50 100644 --- a/helpers/radarr.py +++ b/helpers/radarr.py @@ -17,7 +17,7 @@ def movies_to_tmdb_dict(radarr_movies): return None -def remove_existing_movies(radarr_movies, trakt_movies): +def remove_existing_movies(radarr_movies, trakt_movies, callback=None): new_movies_list = [] if not radarr_movies or not trakt_movies: @@ -34,10 +34,14 @@ def remove_existing_movies(radarr_movies, trakt_movies): for tmp in trakt_movies: if 'movie' not in tmp or 'ids' not in tmp['movie'] or 'tmdb' not in tmp['movie']['ids']: log.debug("Skipping movie because it did not have required fields: %s", tmp) + if callback: + callback('movie', tmp) continue # check if movie exists in processed_movies if tmp['movie']['ids']['tmdb'] in processed_movies: log.debug("Removing existing movie: %s", tmp['movie']['title']) + if callback: + callback('movie', tmp) continue new_movies_list.append(tmp) diff --git a/helpers/sonarr.py b/helpers/sonarr.py index 52c2f91..99d34ca 100644 --- a/helpers/sonarr.py +++ b/helpers/sonarr.py @@ -48,7 +48,7 @@ def series_to_tvdb_dict(sonarr_series): return None -def remove_existing_series(sonarr_series, trakt_series): +def remove_existing_series(sonarr_series, trakt_series, callback=None): new_series_list = [] if not sonarr_series or not trakt_series: @@ -65,10 +65,14 @@ def remove_existing_series(sonarr_series, trakt_series): for tmp in trakt_series: if 'show' not in tmp or 'ids' not in tmp['show'] or 'tvdb' not in tmp['show']['ids']: log.debug("Skipping show because it did not have required fields: %s", tmp) + if callback: + callback('show', tmp) continue # check if show exists in processed_series if tmp['show']['ids']['tvdb'] in processed_series: log.debug("Removing existing show: %s", tmp['show']['title']) + if callback: + callback('show', tmp) continue new_series_list.append(tmp) diff --git a/helpers/trakt.py b/helpers/trakt.py index 484d28a..289b4dd 100644 --- a/helpers/trakt.py +++ b/helpers/trakt.py @@ -106,7 +106,7 @@ def blacklisted_show_id(show, blacklisted_ids): return blacklisted -def is_show_blacklisted(show, blacklist_settings, ignore_blacklist): +def is_show_blacklisted(show, blacklist_settings, ignore_blacklist, callback=None): if ignore_blacklist: return False @@ -125,6 +125,10 @@ def is_show_blacklisted(show, blacklist_settings, ignore_blacklist): blacklisted = True if blacklisted_show_id(show, blacklist_settings.blacklisted_tvdb_ids): blacklisted = True + + if blacklisted and callback: + callback('show', show) + except Exception: log.exception("Exception determining if show was blacklisted %s: ", show) return blacklisted @@ -231,7 +235,7 @@ def blacklisted_movie_id(movie, blacklisted_ids): return blacklisted -def is_movie_blacklisted(movie, blacklist_settings, ignore_blacklist): +def is_movie_blacklisted(movie, blacklist_settings, ignore_blacklist, callback=None): if ignore_blacklist: return False @@ -250,6 +254,10 @@ def is_movie_blacklisted(movie, blacklist_settings, ignore_blacklist): blacklisted = True if blacklisted_movie_id(movie, blacklist_settings.blacklisted_tmdb_ids): blacklisted = True + + if blacklisted and callback: + callback('movie', movie) + except Exception: log.exception("Exception determining if movie was blacklisted %s: ", movie) return blacklisted diff --git a/media/trakt.py b/media/trakt.py index 133f117..cf4e3bf 100644 --- a/media/trakt.py +++ b/media/trakt.py @@ -20,14 +20,17 @@ class Trakt: # Requests ############################################################ - def _make_request(self, url, payload={}, authenticate_user=None): + def _make_request(self, url, payload={}, authenticate_user=None, request_type='get'): headers, authenticate_user = self._headers(authenticate_user) if authenticate_user: url = url.replace('{authenticate_user}', authenticate_user) # make request - req = requests.get(url, headers=headers, params=payload, timeout=30) + if request_type == 'delete': + req = requests.delete(url, headers=headers, params=payload, timeout=30) + else: + req = requests.get(url, headers=headers, params=payload, timeout=30) log.debug("Request URL: %s", req.url) log.debug("Request Payload: %s", payload) log.debug("Request User: %s", authenticate_user) @@ -130,6 +133,16 @@ class Trakt: log.exception("Exception validating client_id: ") return False + def remove_recommended_item(self, item_type, trakt_id, authenticate_user=None): + ret = self._make_request( + url='https://api.trakt.tv/recommendations/%ss/%s' % (item_type, str(trakt_id)), + authenticate_user=authenticate_user, + request_type='delete' + ) + if ret.status_code == 204: + return True + return False + ############################################################ # OAuth Authentication ############################################################ @@ -363,6 +376,17 @@ class Trakt: genres=genres ) + def get_recommended_shows(self, authenticate_user=None, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/recommendations/shows', + authenticate_user=authenticate_user, + limit=limit, + languages=languages, + object_name='shows', + type_name='recommended from {authenticate_user}', + genres=genres + ) + def get_watchlist_shows(self, authenticate_user=None, limit=1000, languages=None): return self._make_items_request( url='https://api.trakt.tv/users/{authenticate_user}/watchlist/shows', @@ -456,6 +480,17 @@ class Trakt: type_name='anticipated', ) + def get_recommended_movies(self, authenticate_user=None, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/recommendations/movies', + authenticate_user=authenticate_user, + limit=limit, + languages=languages, + object_name='movies', + type_name='recommended from {authenticate_user}', + genres=genres + ) + def get_watchlist_movies(self, authenticate_user=None, limit=1000, languages=None): return self._make_items_request( url='https://api.trakt.tv/users/{authenticate_user}/watchlist/movies', diff --git a/traktarr.py b/traktarr.py index 0e12cab..2abaa57 100755 --- a/traktarr.py +++ b/traktarr.py @@ -185,8 +185,9 @@ def show(show_id, folder=None, no_search=False): @click.option('--authenticate-user', help='Specify which user to authenticate with to retrieve Trakt lists. Default: first user in the config') @click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.') +@click.option('--remove-rejected-recommended', is_flag=True, help='Removes rejected/existing movies from recommended.') def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folder=None, no_search=False, - notifications=False, authenticate_user=None, ignore_blacklist=False): + notifications=False, authenticate_user=None, ignore_blacklist=False, remove_rejected_recommended=False): from media.sonarr import Sonarr from media.trakt import Trakt from helpers import misc as misc_helper @@ -222,6 +223,9 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde trakt_objects_list = trakt.get_trending_shows(genres=genre, languages=cfg.filters.shows.allowed_languages) elif list_type.lower() == 'popular': trakt_objects_list = trakt.get_popular_shows(genres=genre, languages=cfg.filters.shows.allowed_languages) + elif list_type.lower() == 'recommended': + trakt_objects_list = trakt.get_recommended_shows(authenticate_user, genres=genre, + languages=cfg.filters.shows.allowed_languages) elif list_type.lower().startswith('played'): most_type = misc_helper.substring_after(list_type.lower(), "_") trakt_objects_list = trakt.get_most_played_shows(genres=genre, languages=cfg.filters.shows.allowed_languages, @@ -245,8 +249,14 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde else: log.info("Retrieved Trakt %s shows list, shows found: %d", list_type, len(trakt_objects_list)) + # set remove_rejected_recommended to False if this is not the recommended list + if list_type.lower() != 'recommended': + remove_rejected_recommended = False + # build filtered series list without series that exist in sonarr - processed_series_list = sonarr_helper.remove_existing_series(pvr_objects_list, trakt_objects_list) + processed_series_list = sonarr_helper.remove_existing_series(pvr_objects_list, trakt_objects_list, + callback_remove_recommended + if remove_rejected_recommended else None) if processed_series_list is None: log.error("Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list") if notifications: @@ -279,7 +289,9 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde continue # check if series passes out blacklist criteria inspection - if not trakt_helper.is_show_blacklisted(series, cfg.filters.shows, ignore_blacklist): + if not trakt_helper.is_show_blacklisted(series, cfg.filters.shows, ignore_blacklist, + callback_remove_recommended + if remove_rejected_recommended else None): log.info("Adding: %s | Genres: %s | Network: %s | Country: %s", series['show']['title'], ', '.join(series['show']['genres']), series['show']['network'], series['show']['country'].upper()) @@ -380,8 +392,9 @@ def movie(movie_id, folder=None, no_search=False): @click.option('--authenticate-user', help='Specify which user to authenticate with to retrieve Trakt lists. Default: first user in the config.') @click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.') +@click.option('--remove-rejected-recommended', is_flag=True, help='Removes rejected/existing movies from recommended.') def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folder=None, no_search=False, - notifications=False, authenticate_user=None, ignore_blacklist=False): + notifications=False, authenticate_user=None, ignore_blacklist=False, remove_rejected_recommended=False): from media.radarr import Radarr from media.trakt import Trakt from helpers import misc as misc_helper @@ -418,6 +431,9 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold trakt_objects_list = trakt.get_popular_movies(genres=genre, languages=cfg.filters.movies.allowed_languages) elif list_type.lower() == 'boxoffice': trakt_objects_list = trakt.get_boxoffice_movies() + elif list_type.lower() == 'recommended': + trakt_objects_list = trakt.get_recommended_movies(authenticate_user, genres=genre, + languages=cfg.filters.movies.allowed_languages) elif list_type.lower().startswith('played'): most_type = misc_helper.substring_after(list_type.lower(), "_") trakt_objects_list = trakt.get_most_played_movies(genres=genre, languages=cfg.filters.movies.allowed_languages, @@ -441,8 +457,14 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold else: log.info("Retrieved Trakt %s movies list, movies found: %d", list_type, len(trakt_objects_list)) + # set remove_rejected_recommended to False if this is not the recommended list + if list_type.lower() != 'recommended': + remove_rejected_recommended = False + # build filtered movie list without movies that exist in radarr - processed_movies_list = radarr_helper.remove_existing_movies(pvr_objects_list, trakt_objects_list) + processed_movies_list = radarr_helper.remove_existing_movies(pvr_objects_list, trakt_objects_list, + callback_remove_recommended + if remove_rejected_recommended else None) if processed_movies_list is None: log.error("Aborting due to failure to remove existing Radarr movies from retrieved Trakt movies list") if notifications: @@ -475,7 +497,9 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold continue # check if movie passes out blacklist criteria inspection - if not trakt_helper.is_movie_blacklisted(movie, cfg.filters.movies, ignore_blacklist): + if not trakt_helper.is_movie_blacklisted(movie, cfg.filters.movies, ignore_blacklist, + callback_remove_recommended if remove_rejected_recommended + else None): log.info("Adding: %s (%d) | Genres: %s | Country: %s", movie['movie']['title'], movie['movie']['year'], ', '.join(movie['movie']['genres']), movie['movie']['country'].upper()) # add movie to radarr @@ -508,9 +532,28 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold ############################################################ -# AUTOMATIC +# CALLBACKS ############################################################ + +def callback_remove_recommended(media_type, media_info): + from media.trakt import Trakt + + trakt = Trakt(cfg) + + if not media_info[media_type]['title'] or not media_info[media_type]['year']: + log.debug("Skipping removing %s item from recommended list as no title/year was available:\n%s", media_type, + media_info) + return + + media_name = '%s (%d)' % (media_info[media_type]['title'], media_info[media_type]['year']) + + if trakt.remove_recommended_item(media_type, media_info[media_type]['ids']['trakt']): + log.info("Removed rejected recommended %s: %s", media_type, media_name) + else: + log.info("FAILED removing rejected recommended %s: %s", media_type, media_name) + + def callback_notify(data): log.debug("Received callback data: %s", data) @@ -536,6 +579,11 @@ def callback_notify(data): return +############################################################ +# AUTOMATIC +############################################################ + + def automatic_shows(add_delay=2.5, sort='votes', no_search=False, notifications=False, ignore_blacklist=False): from media.trakt import Trakt