diff --git a/utility/sync_watch_status.py b/utility/sync_watch_status.py new file mode 100644 index 0000000..9c3e3dc --- /dev/null +++ b/utility/sync_watch_status.py @@ -0,0 +1,154 @@ +""" +Description: Sync the watch status from one user to another. Either by user or user/libraries +Author: Blacktwin +Requires: requests, plexapi, argparse + +Enabling Scripts in Tautulli: +Taultulli > Settings > Notification Agents > Add a Notification Agent > Script + +Configuration: +Taultulli > Settings > Notification Agents > New Script > Configuration: + + Script Name: sync_watch_status.py + Set Script Timeout: default + Description: Sync watch status + Save + +Triggers: +Taultulli > Settings > Notification Agents > New Script > Triggers: + + Check: Notify on Watched + Save + +Conditions: +Taultulli > Settings > Notification Agents > New Script > Conditions: + + Set Conditions: [{username} | {is} | {user_to_sync_from} ] + Save + +Script Arguments: +Taultulli > Settings > Notification Agents > New Script > Script Arguments: + + Select: Notify on Watched + Arguments: --ratingKey {rating_key} --userTo "Username2" "Username3" --userFrom {username} + + Save + Close + + Example: + Set in Tautulli in script notification agent or run manually + + plex_api_share.py --userFrom USER1 --userTo USER2 --libraries Movies + - Synced watch status of {title from library} to {USER2}'s account. + + plex_api_share.py --userFrom USER1 --userTo USER2 USER3 --allLibraries + - Synced watch status of {title from library} to {USER2 or USER3}'s account. + + Excluding; + --libraries becomes excluded if --allLibraries is set + sync_watch_status.py --userFrom USER1 --userTo USER2 --allLibraries --libraries Movies + - Shared [all libraries but Movies] with USER. + +""" +import requests +import argparse +import os +from plexapi.server import PlexServer + + +PLEX_FALLBACK_URL = 'http://127.0.0.1:32400' +PLEX_FALLBACK_TOKEN = '' +PLEX_URL = os.getenv('PLEX_URL', PLEX_FALLBACK_URL) +PLEX_TOKEN = os.getenv('PLEX_TOKEN', PLEX_FALLBACK_TOKEN) + +PLEX_OVERRIDE_URL = '' +PLEX_OVERRIDE_TOKEN = '' + +if PLEX_OVERRIDE_URL: + PLEX_URL = PLEX_OVERRIDE_URL +if PLEX_OVERRIDE_TOKEN: + PLEX_TOKEN = PLEX_OVERRIDE_TOKEN + + +sess = requests.Session() +sess.verify = False +plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess) + +sections_lst = [x.title for x in plex.library.sections()] +user_lst = [x.title for x in plex.myPlexAccount().users()] + +def get_account(user): + # Access Plex User's Account + userAccount = plex.myPlexAccount().user(user) + token = userAccount.get_token(plex.machineIdentifier) + server = PlexServer(PLEX_URL, token) + return server + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description="Sync watch status from one user to others.", + formatter_class=argparse.RawTextHelpFormatter) + requiredNamed = parser.add_argument_group('required named arguments') + parser.add_argument('--libraries', nargs='*', choices=sections_lst, metavar='library', + help='Space separated list of case sensitive names to process. Allowed names are: \n' + '(choices: %(choices)s)') + parser.add_argument('--allLibraries', action='store_true', + help='Select all libraries.') + parser.add_argument('--ratingKey', nargs=1, + help='Rating key of item whose watch status is to be synced.') + requiredNamed.add_argument('--userFrom', nargs=None, choices=user_lst, metavar='username', required=True, + help='Space separated list of case sensitive names to process. Allowed names are: \n' + '(choices: %(choices)s)') + requiredNamed.add_argument('--userTo', nargs='*', choices=user_lst, metavar='usernames', required=True, + help='Space separated list of case sensitive names to process. Allowed names are: \n' + '(choices: %(choices)s)') + + + opts = parser.parse_args() + # print(opts) + + # Create Sync-From user account + plexFrom = get_account(opts.userFrom) + + # Defining libraries + libraries = '' + if opts.allLibraries and not opts.libraries: + libraries = sections_lst + elif not opts.allLibraries and opts.libraries: + libraries = opts.libraries + elif opts.allLibraries and opts.libraries: + # If allLibraries is used then any libraries listed will be excluded + for library in opts.libraries: + sections_lst.remove(library) + libraries = sections_lst + + for user in opts.userTo: + # Create Sync-To user account + plexTo = get_account(user) + if libraries: + for library in libraries: + try: + print('Checking library: {}'.format(library)) + section = plexFrom.library.section(library) + for item in section.search(unwatched=False): + title = item.title.encode('utf-8') + plexTo.fetchItem(item.key).markWatched() + print('Synced watch status of {} to {}\'s account.'.format(title, user)) + except Exception as e: + if str(e).startswith('Unknown'): + print('Library ({}) does not have a watch status.'.format(library)) + elif str(e).startswith('Invalid'): + print('Library ({}) not shared to user: {}.'.format(library, opts.userFrom)) + elif str(e).startswith('(404)'): + print('Library ({}) not shared to user: {}.'.format(library, user)) + else: + print(e) + pass + elif opts.ratingKey: + item = plexTo.fetchItem(opts.ratingKey) + title = item.title.encode('utf-8') + print('Syncing watch status of {} to {}\'s account.'.format(title, user)) + item.markWatched() + else: + print('No libraries or rating key provided.') \ No newline at end of file