total rewrite while resolving #146.
Now able to read watched status from Tautulli and apply to any user on any owned server.
This commit is contained in:
parent
80557f4a61
commit
e605979a16
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
"""
|
"""
|
||||||
Description: Sync the watch status from one user to another. Either by user or user/libraries
|
Description: Sync the watch status from one user to another across multiple servers including from Tautulli
|
||||||
Author: Blacktwin
|
Author: Blacktwin
|
||||||
Requires: requests, plexapi, argparse
|
Requires: requests, plexapi, argparse
|
||||||
|
|
||||||
@ -31,32 +31,35 @@ Script Arguments:
|
|||||||
Taultulli > Settings > Notification Agents > New Script > Script Arguments:
|
Taultulli > Settings > Notification Agents > New Script > Script Arguments:
|
||||||
|
|
||||||
Select: Notify on Watched
|
Select: Notify on Watched
|
||||||
Arguments: --ratingKey {rating_key} --userTo "Username2=Server1" "Username3=Server1" --userFrom {username}={server_name}
|
Arguments: --ratingKey {rating_key} --userFrom Tautulli=Tautulli --userTo "Username2=Server1" "Username3=Server1"
|
||||||
|
|
||||||
Save
|
Save
|
||||||
Close
|
Close
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
Set in Tautulli in script notification agent or run manually
|
Set in Tautulli in script notification agent (above) or run manually (below)
|
||||||
|
|
||||||
sync_watch_status.py --userFrom USER1=Server --userTo USER2=Server --libraries Movies
|
sync_watch_status.py --userFrom USER1=Server1 --userTo USER2=Server1 --libraries Movies
|
||||||
- Synced watch status of {title from library} to {USER2}'s account.
|
- Synced watch status from Server1 {title from library} to {USER2}'s account on Server1.
|
||||||
|
|
||||||
sync_watch_status.py --userFrom USER1=Server --userTo USER2=Server USER3=Server --allLibraries
|
sync_watch_status.py --userFrom USER1=Server2 --userTo USER2=Server1 USER3=Server1 --libraries Movies "TV Shows"
|
||||||
- Synced watch status of {title from library} to {USER2 or USER3}'s account.
|
- Synced watch status from Server2 {title from library} to {USER2 or USER3}'s account on Server1.
|
||||||
|
|
||||||
Excluding;
|
sync_watch_status.py --userFrom USER1=Tautulli --userTo USER2=Server1 USER3=Server2 --libraries Movies "TV Shows"
|
||||||
--libraries becomes excluded if --allLibraries is set
|
- Synced watch statuses from Tautulli {title from library} to {USER2 or USER3}'s account on selected servers.
|
||||||
sync_watch_status.py --userFrom USER1=Server --userTo USER2=Server --allLibraries --libraries Movies
|
|
||||||
- Shared [all libraries but Movies] with USER.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
import requests
|
|
||||||
import argparse
|
import argparse
|
||||||
from plexapi.myplex import MyPlexAccount
|
from plexapi.myplex import MyPlexAccount
|
||||||
from plexapi.server import PlexServer, CONFIG
|
from plexapi.server import PlexServer
|
||||||
|
from plexapi.server import CONFIG
|
||||||
|
from requests import Session
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
pp = pprint.PrettyPrinter(indent=4)
|
||||||
|
|
||||||
# Using CONFIG file
|
# Using CONFIG file
|
||||||
PLEX_TOKEN = ''
|
PLEX_TOKEN = ''
|
||||||
@ -64,206 +67,441 @@ TAUTULLI_URL = ''
|
|||||||
TAUTULLI_APIKEY = ''
|
TAUTULLI_APIKEY = ''
|
||||||
|
|
||||||
if not PLEX_TOKEN:
|
if not PLEX_TOKEN:
|
||||||
PLEX_TOKEN = CONFIG.data['auth'].get('server_token', '')
|
PLEX_TOKEN = CONFIG.data['auth'].get('server_token')
|
||||||
if not TAUTULLI_URL:
|
if not TAUTULLI_URL:
|
||||||
TAUTULLI_URL = CONFIG.data['auth'].get('tautulli_baseurl')
|
TAUTULLI_URL = CONFIG.data['auth'].get('tautulli_baseurl')
|
||||||
if not TAUTULLI_APIKEY:
|
if not TAUTULLI_APIKEY:
|
||||||
TAUTULLI_APIKEY = CONFIG.data['auth'].get('tautulli_apikey')
|
TAUTULLI_APIKEY = CONFIG.data['auth'].get('tautulli_apikey')
|
||||||
|
|
||||||
|
VERIFY_SSL = False
|
||||||
sess = requests.Session()
|
|
||||||
# Ignore verifying the SSL certificate
|
|
||||||
sess.verify = False # '/path/to/certfile'
|
|
||||||
# If verify is set to a path to a directory,
|
|
||||||
# the directory must have been processed using the c_rehash utility supplied
|
|
||||||
# with OpenSSL.
|
|
||||||
if sess.verify is False:
|
|
||||||
# Disable the warning that the request is insecure, we know that...
|
|
||||||
import urllib3
|
|
||||||
|
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
||||||
|
|
||||||
account = MyPlexAccount(PLEX_TOKEN)
|
|
||||||
|
|
||||||
# todo-me This is cleaned up. Should only connect to servers that are selected
|
|
||||||
sections_lst = []
|
|
||||||
user_servers = {}
|
|
||||||
admin_servers = {}
|
|
||||||
server_users = account.users()
|
|
||||||
user_server_dict = {'data': {}}
|
|
||||||
user_data = user_server_dict['data']
|
|
||||||
|
|
||||||
# Finding and connecting to owned servers.
|
|
||||||
print('Connecting to admin owned servers.')
|
|
||||||
for resource in account.resources():
|
|
||||||
if 'server' in [resource.provides] and resource.ownerid == 0:
|
|
||||||
server_connect = resource.connect()
|
|
||||||
admin_servers[resource.name] = server_connect
|
|
||||||
# Pull section names to check against
|
|
||||||
server_sections = [section.title for section in server_connect.library.sections()]
|
|
||||||
sections_lst += server_sections
|
|
||||||
|
|
||||||
sections_lst = list(set(sections_lst))
|
|
||||||
|
|
||||||
# Add admin account
|
|
||||||
user_data[account.title] = {'account': account,
|
|
||||||
'servers': admin_servers}
|
|
||||||
|
|
||||||
# Finding what user has access to which admin owned servers
|
|
||||||
for user in server_users:
|
|
||||||
for server in user.servers:
|
|
||||||
if admin_servers.get(server.name):
|
|
||||||
user_servers[server.name] = admin_servers.get(server.name)
|
|
||||||
if not user_data.get(user.title):
|
|
||||||
user_data[user.title] = {'account': user,
|
|
||||||
'servers': user_servers}
|
|
||||||
|
|
||||||
# todo-me Add Tautulli history for syncing watch statuses from Tautulli to Plex
|
|
||||||
def get_history(user_id, media_type):
|
|
||||||
# Get the user history from Tautulli.
|
|
||||||
payload = {'apikey': TAUTULLI_APIKEY,
|
|
||||||
'cmd': 'get_history',
|
|
||||||
'user_id': user_id,
|
|
||||||
'media_type': media_type}
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
|
||||||
response = r.json()
|
|
||||||
res_data = response['response']['data']['data']
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e))
|
|
||||||
|
|
||||||
|
|
||||||
def get_account(user, server):
|
class Connection:
|
||||||
|
def __init__(self, url=None, apikey=None, verify_ssl=False):
|
||||||
|
self.url = url
|
||||||
|
self.apikey = apikey
|
||||||
|
self.session = Session()
|
||||||
|
self.adapters = HTTPAdapter(max_retries=3,
|
||||||
|
pool_connections=1,
|
||||||
|
pool_maxsize=1,
|
||||||
|
pool_block=True)
|
||||||
|
self.session.mount('http://', self.adapters)
|
||||||
|
self.session.mount('https://', self.adapters)
|
||||||
|
|
||||||
|
# Ignore verifying the SSL certificate
|
||||||
|
if verify_ssl is False:
|
||||||
|
self.session.verify = False
|
||||||
|
# Disable the warning that the request is insecure, we know that...
|
||||||
|
import urllib3
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
|
||||||
|
class Library(object):
|
||||||
|
def __init__(self, data=None):
|
||||||
|
d = data or {}
|
||||||
|
self.title = d['section_name']
|
||||||
|
self.key = d['section_id']
|
||||||
|
|
||||||
|
|
||||||
|
class Metadata(object):
|
||||||
|
def __init__(self, data=None):
|
||||||
|
d = data or {}
|
||||||
|
self.type = d['media_type']
|
||||||
|
self.grandparentTitle = d['grandparent_title']
|
||||||
|
self.parentIndex = d['parent_media_index']
|
||||||
|
self.index = d['media_index']
|
||||||
|
if self.type == 'episode':
|
||||||
|
ep_name = d['full_title'].partition('-')[-1]
|
||||||
|
self.title = ep_name.lstrip()
|
||||||
|
else:
|
||||||
|
self.title = d['full_title']
|
||||||
|
|
||||||
|
# For History
|
||||||
|
try:
|
||||||
|
if d['watched_status']:
|
||||||
|
self.watched_status = d['watched_status']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
# For Metadata
|
||||||
|
try:
|
||||||
|
if d["library_name"]:
|
||||||
|
self.libraryName = d['library_name']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Tautulli:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
def _call_api(self, cmd, payload, method='GET'):
|
||||||
|
payload['cmd'] = cmd
|
||||||
|
payload['apikey'] = self.connection.apikey
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.connection.session.request(method, self.connection.url + '/api/v2', params=payload)
|
||||||
|
except RequestException as e:
|
||||||
|
print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL? Error: {}".format(cmd, e))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_json = response.json()
|
||||||
|
except ValueError:
|
||||||
|
print("Failed to parse json response for Tautulli API cmd '{}'".format(cmd))
|
||||||
|
return
|
||||||
|
|
||||||
|
if response_json['response']['result'] == 'success':
|
||||||
|
return response_json['response']['data']
|
||||||
|
else:
|
||||||
|
error_msg = response_json['response']['message']
|
||||||
|
print("Tautulli API cmd '{}' failed: {}".format(cmd, error_msg))
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_watched_history(self, user, section_id, start, length):
|
||||||
|
"""Call Tautulli's get_history api endpoint"""
|
||||||
|
payload = {"user": user,
|
||||||
|
"section_id": section_id,
|
||||||
|
'start': start,
|
||||||
|
'length': length,
|
||||||
|
'order_column': 'full_title',
|
||||||
|
'order_dir': 'asc'}
|
||||||
|
|
||||||
|
history = self._call_api('get_history', payload)
|
||||||
|
|
||||||
|
return [d for d in history['data'] if d['watched_status'] == 1]
|
||||||
|
|
||||||
|
def get_metadata(self, rating_key):
|
||||||
|
"""Call Tautulli's get_metadata api endpoint"""
|
||||||
|
|
||||||
|
payload = {"rating_key": rating_key}
|
||||||
|
return self._call_api('get_metadata', payload)
|
||||||
|
|
||||||
|
def get_libraries(self):
|
||||||
|
"""Call Tautulli's get_libraries api endpoint"""
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
return self._call_api('get_libraries', payload)
|
||||||
|
|
||||||
|
|
||||||
|
class Plex:
|
||||||
|
def __init__(self, token, url=None):
|
||||||
|
if token and not url:
|
||||||
|
self.account = MyPlexAccount(token)
|
||||||
|
if token and url:
|
||||||
|
session = Connection().session
|
||||||
|
self.server = PlexServer(baseurl=url, token=token, session=session)
|
||||||
|
|
||||||
|
def admin_servers(self):
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
data: dict
|
||||||
|
"""
|
||||||
|
resources = {}
|
||||||
|
for resource in self.account.resources():
|
||||||
|
if 'server' in [resource.provides] and resource.owned == True:
|
||||||
|
resources[resource.name] = resource
|
||||||
|
|
||||||
|
return resources
|
||||||
|
|
||||||
|
def all_users(self):
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
data: dict
|
||||||
|
"""
|
||||||
|
users = {self.account.title: self.account}
|
||||||
|
for user in self.account.users():
|
||||||
|
users[user.title] = user
|
||||||
|
|
||||||
|
return users
|
||||||
|
|
||||||
|
def all_sections(self):
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
data: dict
|
||||||
|
"""
|
||||||
|
data = {}
|
||||||
|
servers = self.admin_servers()
|
||||||
|
print('Connecting to admin server(s) for section info...')
|
||||||
|
for name, server in servers.items():
|
||||||
|
connect = server.connect()
|
||||||
|
sections = {section.title: section for section in connect.library.sections()}
|
||||||
|
data[name] = sections
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def users_access(self):
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
data: dict
|
||||||
|
"""
|
||||||
|
all_users = self.all_users().values()
|
||||||
|
admin_servers = self.admin_servers()
|
||||||
|
all_sections = self.all_sections()
|
||||||
|
|
||||||
|
data = {self.account.title: {"account": self.account}}
|
||||||
|
|
||||||
|
for user in all_users:
|
||||||
|
if not data.get(user.title):
|
||||||
|
servers = []
|
||||||
|
for server in user.servers:
|
||||||
|
if admin_servers.get(server.name):
|
||||||
|
access = {}
|
||||||
|
sections = {section.title: section for section in server.sections()
|
||||||
|
if section.shared == True}
|
||||||
|
access['server'] = {server.name: admin_servers.get(server.name)}
|
||||||
|
access['sections'] = sections
|
||||||
|
servers += [access]
|
||||||
|
data[user.title] = {'account': user,
|
||||||
|
'access': servers}
|
||||||
|
else:
|
||||||
|
# Admin account
|
||||||
|
servers = []
|
||||||
|
for name, server in admin_servers.items():
|
||||||
|
access = {}
|
||||||
|
sections = all_sections.get(name)
|
||||||
|
access['server'] = {name: server}
|
||||||
|
access['sections'] = sections
|
||||||
|
servers += [access]
|
||||||
|
data[user.title] = {'account': user,
|
||||||
|
'access': servers}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def connect_to_server(server_obj, user_account):
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
server_obj: class
|
||||||
|
user_account: class
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
user_connection.server: class
|
||||||
|
"""
|
||||||
|
server_name = server_obj.name
|
||||||
|
user = user_account.title
|
||||||
|
|
||||||
|
print('Connecting {} to {}...'.format(user, server_name))
|
||||||
|
server_connection = server_obj.connect()
|
||||||
|
baseurl = server_connection._baseurl.split('.')
|
||||||
|
url = ''.join([baseurl[0].replace('-', '.'),
|
||||||
|
baseurl[-1].replace('direct', '')])
|
||||||
|
if user_account.title == Plex(PLEX_TOKEN).account.title:
|
||||||
|
token = PLEX_TOKEN
|
||||||
|
else:
|
||||||
|
token = user_account.get_token(server_connection.machineIdentifier)
|
||||||
|
|
||||||
|
user_connection = Plex(url=url, token=token)
|
||||||
|
|
||||||
|
return user_connection.server
|
||||||
|
|
||||||
|
|
||||||
|
def check_users_access(user, server_name, libraries=None):
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
user: str
|
user: str
|
||||||
User's name
|
server_name: str
|
||||||
server: str
|
libraries: list
|
||||||
Server's name
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
User server class
|
server_connection: class
|
||||||
|
|
||||||
"""
|
"""
|
||||||
print('Checking {} on {}'.format(user, server))
|
try:
|
||||||
if user_server_dict['data'][user]['servers'].get(server):
|
_user = plex_admin.users_access().get(user)
|
||||||
user_server = user_server_dict['data'][user]['servers'].get(server)
|
for access in _user['access']:
|
||||||
baseurl = user_server._baseurl.split('.')
|
server = access.get("server")
|
||||||
url = ''.join([baseurl[0].replace('-', '.'),
|
# Check user access to server
|
||||||
baseurl[-1].replace('direct', '')])
|
if server.get(server_name):
|
||||||
if user == MyPlexAccount(PLEX_TOKEN).title:
|
server_obj = server.get(server_name)
|
||||||
token = PLEX_TOKEN
|
# If syncing by libraries, check library access
|
||||||
else:
|
if libraries:
|
||||||
userAccount = user_server.myPlexAccount().user(user)
|
library_check = any(lib.title in access.get("sections").keys() for lib in libraries)
|
||||||
token = userAccount.get_token(user_server.machineIdentifier)
|
# Check user access to library
|
||||||
account = PlexServer(baseurl=url, token=token, session=sess)
|
if library_check:
|
||||||
return account
|
server_connection = connect_to_server(server_obj, _user['account'])
|
||||||
else:
|
return server_connection
|
||||||
print('{} is not shared to {}'.format(user, server))
|
|
||||||
|
elif not library_check:
|
||||||
|
print("User does not have access to this library.")
|
||||||
|
# Not syncing by libraries
|
||||||
|
else:
|
||||||
|
server_connection = connect_to_server(server_obj, _user['account'])
|
||||||
|
return server_connection
|
||||||
|
# else:
|
||||||
|
# print("User does not have access to this server: {}.".format(server_name))
|
||||||
|
except KeyError:
|
||||||
|
print('User name is incorrect.')
|
||||||
|
print(", ".join(plex_admin.all_users().keys()))
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
|
||||||
def mark_watached(sectionFrom, accountTo, userTo):
|
def sync_watch_status(watched, section, accountTo, userTo):
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
sectionFrom: class
|
watched: list
|
||||||
|
section: class
|
||||||
Section class of sync from server
|
Section class of sync from server
|
||||||
accountTo: class
|
userTo: str
|
||||||
User's server class of sync to user
|
User's server class of sync to user
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Check sections for watched items
|
# Check sections for watched items
|
||||||
print('Marking watched...')
|
print('Marking watched...')
|
||||||
sectionTo = accountTo.library.section(sectionFrom.title)
|
sectionTo = accountTo.library.section(section)
|
||||||
for item in sectionFrom.search(unwatched=False):
|
for item in watched:
|
||||||
title = item.title.encode('utf-8')
|
|
||||||
try:
|
try:
|
||||||
# Check movie media type
|
# .get retrieves a partial object
|
||||||
if item.type == 'movie':
|
if item.type == 'episode':
|
||||||
watch_check = sectionTo.get(item.title)
|
show_name = item.grandparentTitle
|
||||||
fetch_check = sectionTo.fetchItem(watch_check.key)
|
ep_name = item.title
|
||||||
if not fetch_check.isWatched:
|
title = "{} {}".format(show_name, ep_name)
|
||||||
fetch_check.markWatched()
|
show = sectionTo.get(show_name)
|
||||||
print('Synced watch status of {} to {}\'s account.'.format(title, userTo))
|
watch_check = show.episode(season=item.parentIndex, episode=item.index)
|
||||||
# Check show media type
|
else:
|
||||||
elif item.type == 'show':
|
title = item.title
|
||||||
for episode in sectionFrom.searchEpisodes(unwatched=False, title=title):
|
watch_check = sectionTo.get(title)
|
||||||
ep_title = episode.title.encode('utf-8')
|
# .fetchItem retrieves a full object
|
||||||
watch_check = sectionTo.get(item.title)
|
fetch_check = sectionTo.fetchItem(watch_check.key)
|
||||||
fetch_check = sectionTo.fetchItem(watch_check.key)
|
# If item is already watched ignore
|
||||||
if not fetch_check.isWatched:
|
if not fetch_check.isWatched:
|
||||||
fetch_check.markWatched()
|
# todo-me should watched count be synced?
|
||||||
print('Synced watch status of {} - {} to {}\'s account.'.format(title, ep_title, userTo))
|
fetch_check.markWatched()
|
||||||
except Exception:
|
print("Synced watched status of {} to account {}...".format(title, userTo))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Sync watch status from one user to others.",
|
parser = argparse.ArgumentParser(description="Sync watch status from one user to others.",
|
||||||
formatter_class=argparse.RawTextHelpFormatter)
|
formatter_class=argparse.RawTextHelpFormatter)
|
||||||
requiredNamed = parser.add_argument_group('required named arguments')
|
parser.add_argument('--libraries', nargs='*', metavar='library',
|
||||||
parser.add_argument('--libraries', nargs='*', choices=sections_lst, metavar='library',
|
help='Libraries to scan for watched content.')
|
||||||
help='Space separated list of case sensitive names to process. Allowed names are: \n'
|
parser.add_argument('--ratingKey', nargs="?", type=str,
|
||||||
'(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.')
|
help='Rating key of item whose watch status is to be synced.')
|
||||||
|
requiredNamed = parser.add_argument_group('required named arguments')
|
||||||
requiredNamed.add_argument('--userFrom', metavar='user=server', required=True,
|
requiredNamed.add_argument('--userFrom', metavar='user=server', required=True,
|
||||||
type=lambda kv: kv.split("="),
|
type=lambda kv: kv.split("="), default=["", ""],
|
||||||
help='Select user and server to sync from')
|
help='Select user and server to sync from')
|
||||||
requiredNamed.add_argument('--userTo', nargs='*', metavar='user=server', required=True,
|
requiredNamed.add_argument('--userTo', nargs='*', metavar='user=server', required=True,
|
||||||
type=lambda kv: kv.split("="),
|
type=lambda kv: kv.split("="),
|
||||||
help='Select user and server to sync to.')
|
help='Select user and server to sync to.')
|
||||||
|
|
||||||
opts = parser.parse_args()
|
opts = parser.parse_args()
|
||||||
|
# print(opts)
|
||||||
|
tautulli_server = ''
|
||||||
|
|
||||||
|
libraries = []
|
||||||
|
all_sections = {}
|
||||||
|
watchedFrom = ''
|
||||||
|
count = 25
|
||||||
|
start = 0
|
||||||
|
plex_admin = Plex(PLEX_TOKEN)
|
||||||
|
|
||||||
|
userFrom, serverFrom = opts.userFrom
|
||||||
|
|
||||||
|
if serverFrom == "Tautulli":
|
||||||
|
# Create a Tautulli instance
|
||||||
|
tautulli_server = Tautulli(Connection(url=TAUTULLI_URL.rstrip('/'),
|
||||||
|
apikey=TAUTULLI_APIKEY,
|
||||||
|
verify_ssl=VERIFY_SSL))
|
||||||
|
|
||||||
|
if serverFrom == "Tautulli" and opts.libraries:
|
||||||
|
_sections = {}
|
||||||
|
# Pull all libraries from Tautulli
|
||||||
|
tautulli_sections = tautulli_server.get_libraries()
|
||||||
|
for section in tautulli_sections:
|
||||||
|
section_obj = Library(section)
|
||||||
|
_sections[section_obj.title] = section_obj
|
||||||
|
all_sections[serverFrom] = _sections
|
||||||
|
elif serverFrom != "Tautulli" and opts.libraries:
|
||||||
|
all_sections = plex_admin.all_sections()
|
||||||
|
|
||||||
# Defining libraries
|
# Defining libraries
|
||||||
libraries = ''
|
if opts.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:
|
for library in opts.libraries:
|
||||||
sections_lst.remove(library)
|
if all_sections.get(serverFrom):
|
||||||
libraries = sections_lst
|
if all_sections.get(serverFrom).get(library):
|
||||||
|
libraries.append(all_sections.get(serverFrom).get(library))
|
||||||
# Create Sync-From user account
|
else:
|
||||||
plexFrom = get_account(opts.userFrom[0], opts.userFrom[1])
|
print("No matching library name '{}'".format(library))
|
||||||
|
exit()
|
||||||
# Go through list of users
|
|
||||||
for user in opts.userTo:
|
else:
|
||||||
plexTo = get_account(user[0], user[1])
|
print("No matching server name '{}'".format(serverFrom))
|
||||||
if libraries:
|
exit()
|
||||||
# Go through Libraries
|
|
||||||
for library in libraries:
|
# If userFrom is Plex Admin
|
||||||
try:
|
# if userFrom == plex_admin.account.title and serverFrom != "Tautulli" and opts.libraries:
|
||||||
print('Checking library: {}'.format(library))
|
# resource = plex_admin.admin_servers().get(serverFrom)
|
||||||
# Check library for watched items
|
# print('Connecting {} to {}...'.format(userFrom, serverFrom))
|
||||||
section = plexFrom.library.section(library)
|
# server_connection = resource.connect()
|
||||||
mark_watached(section, plexTo, user[0])
|
# baseurl = server_connection._baseurl.split('.')
|
||||||
except Exception as e:
|
# url = ''.join([baseurl[0].replace('-', '.'),
|
||||||
if str(e).startswith('Unknown'):
|
# baseurl[-1].replace('direct', '')])
|
||||||
print('Library ({}) does not have a watch status.'.format(library))
|
#
|
||||||
elif str(e).startswith('(404)'):
|
# token = PLEX_TOKEN
|
||||||
print('Library ({}) not shared to user: {}.'.format(library, user))
|
# admin_connection = Plex(url=url, token=token)
|
||||||
else:
|
# watchedFrom = admin_connection.server
|
||||||
print(e)
|
if serverFrom != "Tautulli" and opts.libraries:
|
||||||
pass
|
watchedFrom = check_users_access(userFrom, serverFrom, libraries)
|
||||||
# Check rating key from Tautulli
|
|
||||||
elif opts.ratingKey:
|
if libraries:
|
||||||
for key in opts.ratingKey:
|
print("Finding watched items in libraries...")
|
||||||
item = plexTo.fetchItem(int(key))
|
plexTo = []
|
||||||
title = item.title.encode('utf-8')
|
|
||||||
print('Syncing watch status of {} to {}\'s account.'.format(title, user[0]))
|
for user, server_name in opts.userTo:
|
||||||
item.markWatched()
|
plexTo.append([user, check_users_access(user, server_name, libraries)])
|
||||||
else:
|
|
||||||
print('No libraries or rating key provided.')
|
for _library in libraries:
|
||||||
|
watched_lst = []
|
||||||
|
print("Checking {}'s library: '{}' watch statuses...".format(userFrom, _library.title))
|
||||||
|
if tautulli_server:
|
||||||
|
while True:
|
||||||
|
# Getting all watched history for userFrom
|
||||||
|
tt_watched = tautulli_server.get_watched_history(userFrom, _library.key, start, count)
|
||||||
|
if all([tt_watched]):
|
||||||
|
start += count
|
||||||
|
for item in tt_watched:
|
||||||
|
watched_lst.append(Metadata(item))
|
||||||
|
continue
|
||||||
|
elif not all([tt_watched]):
|
||||||
|
break
|
||||||
|
start += count
|
||||||
|
else:
|
||||||
|
# Check library for watched items
|
||||||
|
sectionFrom = watchedFrom.library.section(_library.title)
|
||||||
|
if _library.type == 'show':
|
||||||
|
for episode in sectionFrom.searchEpisodes(unwatched=False):
|
||||||
|
watched_lst.append(episode)
|
||||||
|
else:
|
||||||
|
for item in sectionFrom.search(unwatched=False):
|
||||||
|
watched_lst.append(item)
|
||||||
|
|
||||||
|
for user in plexTo:
|
||||||
|
username, server = user
|
||||||
|
sync_watch_status(watched_lst, _library.title, server, username)
|
||||||
|
|
||||||
|
elif opts.ratingKey and userFrom == "Tautulli" and serverFrom == "Tautulli":
|
||||||
|
print('Request from Tautulli notification agent to update watch status')
|
||||||
|
plexTo = []
|
||||||
|
|
||||||
|
for user, server_name in opts.userTo:
|
||||||
|
# Check access and connect
|
||||||
|
plexTo.append([user, check_users_access(user, server_name, libraries)])
|
||||||
|
|
||||||
|
for user in plexTo:
|
||||||
|
username, server = user
|
||||||
|
item = Metadata(tautulli_server.get_metadata(opts.ratingKey))
|
||||||
|
sync_watch_status([item], item.libraryName, server, username)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("You aren't using this script correctly... bye!")
|
Loading…
x
Reference in New Issue
Block a user