# This file is part of PlexPy.
#
# PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PlexPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see
' + l
continue
if search_value == '':
filtered = filt
else:
filtered = [row for row in filt for column in row if search_value.lower() in column.lower()]
if order_column == '1':
sortcolumn = 2
elif order_column == '2':
sortcolumn = 1
filtered.sort(key=lambda x: x[sortcolumn])
if order_dir == 'desc':
filtered = filtered[::-1]
rows = filtered[start:(start + length)]
return json.dumps({
'recordsFiltered': len(filtered),
'recordsTotal': len(filt),
'data': rows,
})
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_plex_log(self, **kwargs):
""" Get the PMS logs.
```
Required parameters:
None
Optional parameters:
window (int): The number of tail lines to return
log_type (str): "server" or "scanner"
Returns:
json:
[["May 08, 2016 09:35:37",
"DEBUG",
"Auth: Came in with a super-token, authorization succeeded."
],
[...],
[...]
]
```
"""
window = int(kwargs.get('window', plexpy.CONFIG.PMS_LOGS_LINE_CAP))
log_lines = []
log_type = kwargs.get('log_type', 'server')
try:
log_lines = {'data': log_reader.get_log_tail(window=window, parsed=True, log_type=log_type)}
except:
logger.warn(u"Unable to retrieve Plex Logs.")
return log_lines
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_notification_log(self, **kwargs):
""" Get the data on the PlexPy notification logs table.
```
Required parameters:
None
Optional parameters:
order_column (str): "timestamp", "agent_name", "notify_action",
"subject_text", "body_text", "script_args"
order_dir (str): "desc" or "asc"
start (int): Row to start from, 0
length (int): Number of items to return, 25
search (str): A string to search for, "Telegram"
Returns:
json:
{"draw": 1,
"recordsTotal": 1039,
"recordsFiltered": 163,
"data":
[{"agent_id": 13,
"agent_name": "Telegram",
"body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].",
"id": 1000,
"notify_action": "play",
"poster_url": "http://i.imgur.com/ZSqS8Ri.jpg",
"rating_key": 153037,
"script_args": "[]",
"session_key": 147,
"subject_text": "PlexPy (Winterfell-Server)",
"timestamp": 1462253821,
"user": "DanyKhaleesi69",
"user_id": 8008135
},
{...},
{...}
]
}
```
"""
# Check if datatables json_data was received.
# If not, then build the minimal amount of json data for a query
if not kwargs.get('json_data'):
# TODO: Find some one way to automatically get the columns
dt_columns = [("timestamp", True, True),
("agent_name", True, True),
("notify_action", True, True),
("subject_text", True, True),
("body_text", True, True),
("script_args", True, True)]
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "timestamp")
data_factory = datafactory.DataFactory()
notifications = data_factory.get_notification_log(kwargs=kwargs)
return notifications
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def delete_notification_log(self, **kwargs):
""" Delete the PlexPy notification logs.
```
Required paramters:
None
Optional parameters:
None
Returns:
None
```
"""
data_factory = datafactory.DataFactory()
result = data_factory.delete_notification_log()
res = 'success' if result else 'error'
msg = 'Cleared notification logs.' if result else 'Failed to clear notification logs.'
return {'result': res, 'message': msg}
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def delete_login_log(self, **kwargs):
""" Delete the PlexPy login logs.
```
Required paramters:
None
Optional parameters:
None
Returns:
None
```
"""
user_data = users.Users()
result = user_data.delete_login_log()
res = 'success' if result else 'error'
msg = 'Cleared login logs.' if result else 'Failed to clear login logs.'
return {'result': res, 'message': msg}
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def delete_logs(self):
log_file = logger.FILENAME
try:
open(os.path.join(plexpy.CONFIG.LOG_DIR, log_file), 'w').close()
result = 'success'
msg = 'Cleared the %s file.' % log_file
logger.info(msg)
except Exception as e:
result = 'error'
msg = 'Failed to clear the %s file.' % log_file
logger.exception(u'Failed to clear the %s file: %s.' % (log_file, e))
return {'result': result, 'message': msg}
@cherrypy.expose
@requireAuth(member_of("admin"))
def toggleVerbose(self):
plexpy.VERBOSE = not plexpy.VERBOSE
logger.initLogger(console=not plexpy.QUIET,
log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE)
logger.info(u"Verbose toggled, set to %s", plexpy.VERBOSE)
logger.debug(u"If you read this message, debug logging is available")
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "logs")
@cherrypy.expose
@requireAuth()
def log_js_errors(self, page, message, file, line):
""" Logs javascript errors from the web interface. """
logger.error(u"WebUI :: /%s : %s. (%s:%s)" % (page.rpartition('/')[-1],
message,
file.rpartition('/')[-1].partition('?')[0],
line))
return "js error logged."
@cherrypy.expose
@requireAuth(member_of("admin"))
def logFile(self):
try:
with open(os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME), 'r') as f:
return '
%s' % f.read() except IOError as e: return "Log file not found." ##### Settings ##### @cherrypy.expose @requireAuth(member_of("admin")) def settings(self): interface_dir = os.path.join(plexpy.PROG_DIR, 'data/interfaces/') interface_list = [name for name in os.listdir(interface_dir) if os.path.isdir(os.path.join(interface_dir, name))] # Initialise blank passwords so we do not expose them in the html forms # but users are still able to clear them if plexpy.CONFIG.HTTP_PASSWORD != '': http_password = ' ' else: http_password = '' config = { "allow_guest_access": checked(plexpy.CONFIG.ALLOW_GUEST_ACCESS), "http_basic_auth": checked(plexpy.CONFIG.HTTP_BASIC_AUTH), "http_hash_password": checked(plexpy.CONFIG.HTTP_HASH_PASSWORD), "http_hashed_password": plexpy.CONFIG.HTTP_HASHED_PASSWORD, "http_host": plexpy.CONFIG.HTTP_HOST, "http_username": plexpy.CONFIG.HTTP_USERNAME, "http_port": plexpy.CONFIG.HTTP_PORT, "http_password": http_password, "http_root": plexpy.CONFIG.HTTP_ROOT, "http_proxy": checked(plexpy.CONFIG.HTTP_PROXY), "launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER), "enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS), "https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT), "https_cert": plexpy.CONFIG.HTTPS_CERT, "https_key": plexpy.CONFIG.HTTPS_KEY, "https_domain": plexpy.CONFIG.HTTPS_DOMAIN, "https_ip": plexpy.CONFIG.HTTPS_IP, "anon_redirect": plexpy.CONFIG.ANON_REDIRECT, "api_enabled": checked(plexpy.CONFIG.API_ENABLED), "api_key": plexpy.CONFIG.API_KEY, "update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL, "freeze_db": checked(plexpy.CONFIG.FREEZE_DB), "backup_dir": plexpy.CONFIG.BACKUP_DIR, "cache_dir": plexpy.CONFIG.CACHE_DIR, "log_dir": plexpy.CONFIG.LOG_DIR, "log_blacklist": checked(plexpy.CONFIG.LOG_BLACKLIST), "check_github": checked(plexpy.CONFIG.CHECK_GITHUB), "interface_list": interface_list, "cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB, "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, "pms_ip": plexpy.CONFIG.PMS_IP, "pms_logs_folder": plexpy.CONFIG.PMS_LOGS_FOLDER, "pms_port": plexpy.CONFIG.PMS_PORT, "pms_token": plexpy.CONFIG.PMS_TOKEN, "pms_ssl": checked(plexpy.CONFIG.PMS_SSL), "pms_use_bif": checked(plexpy.CONFIG.PMS_USE_BIF), "pms_uuid": plexpy.CONFIG.PMS_UUID, "date_format": plexpy.CONFIG.DATE_FORMAT, "time_format": plexpy.CONFIG.TIME_FORMAT, "get_file_sizes": checked(plexpy.CONFIG.GET_FILE_SIZES), "grouping_global_history": checked(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY), "grouping_user_history": checked(plexpy.CONFIG.GROUPING_USER_HISTORY), "grouping_charts": checked(plexpy.CONFIG.GROUPING_CHARTS), "movie_notify_enable": checked(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE), "tv_notify_enable": checked(plexpy.CONFIG.TV_NOTIFY_ENABLE), "music_notify_enable": checked(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE), "monitor_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES), "monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS), "monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL, "monitoring_use_websocket": checked(plexpy.CONFIG.MONITORING_USE_WEBSOCKET), "refresh_libraries_interval": plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL, "refresh_libraries_on_startup": checked(plexpy.CONFIG.REFRESH_LIBRARIES_ON_STARTUP), "refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL, "refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP), "ip_logging_enable": checked(plexpy.CONFIG.IP_LOGGING_ENABLE), "movie_logging_enable": checked(plexpy.CONFIG.MOVIE_LOGGING_ENABLE), "tv_logging_enable": checked(plexpy.CONFIG.TV_LOGGING_ENABLE), "music_logging_enable": checked(plexpy.CONFIG.MUSIC_LOGGING_ENABLE), "logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL, "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE), "notify_consecutive": checked(plexpy.CONFIG.NOTIFY_CONSECUTIVE), "notify_upload_posters": checked(plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS), "notify_recently_added": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED), "notify_recently_added_grandparent": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT), "notify_recently_added_delay": plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY, "notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT, "notify_on_start_subject_text": plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, "notify_on_start_body_text": plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT, "notify_on_stop_subject_text": plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT, "notify_on_stop_body_text": plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT, "notify_on_pause_subject_text": plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT, "notify_on_pause_body_text": plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT, "notify_on_resume_subject_text": plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT, "notify_on_resume_body_text": plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT, "notify_on_buffer_subject_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT, "notify_on_buffer_body_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT, "notify_on_watched_subject_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT, "notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, "notify_on_created_subject_text": plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT, "notify_on_created_body_text": plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT, "notify_on_extdown_subject_text": plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT, "notify_on_extdown_body_text": plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT, "notify_on_intdown_subject_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT, "notify_on_intdown_body_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT, "notify_on_extup_subject_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT, "notify_on_extup_body_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, "notify_on_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, "notify_on_intup_body_text": plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, "notify_on_pmsupdate_subject_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT, "notify_on_pmsupdate_body_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT, "notify_scripts_args_text": plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, "home_sections": json.dumps(plexpy.CONFIG.HOME_SECTIONS), "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, "home_stats_cards": json.dumps(plexpy.CONFIG.HOME_STATS_CARDS), "home_library_cards": json.dumps(plexpy.CONFIG.HOME_LIBRARY_CARDS), "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD, "buffer_wait": plexpy.CONFIG.BUFFER_WAIT, "group_history_tables": checked(plexpy.CONFIG.GROUP_HISTORY_TABLES), "git_token": plexpy.CONFIG.GIT_TOKEN, "imgur_client_id": plexpy.CONFIG.IMGUR_CLIENT_ID, "cache_images": checked(plexpy.CONFIG.CACHE_IMAGES) } return serve_template(templatename="settings.html", title="Settings", config=config) @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def configUpdate(self, **kwargs): # Handle the variable config options. Note - keys with False values aren't getting passed checked_configs = [ "launch_browser", "enable_https", "https_create_cert", "api_enabled", "freeze_db", "check_github", "grouping_global_history", "grouping_user_history", "grouping_charts", "group_history_tables", "pms_use_bif", "pms_ssl", "pms_is_remote", "home_stats_type", "movie_notify_enable", "tv_notify_enable", "music_notify_enable", "monitoring_use_websocket", "refresh_libraries_on_startup", "refresh_users_on_startup", "ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable", "notify_consecutive", "notify_upload_posters", "notify_recently_added", "notify_recently_added_grandparent", "monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password", "allow_guest_access", "cache_images", "http_proxy", "http_basic_auth" ] for checked_config in checked_configs: if checked_config not in kwargs: # checked items should be zero or one. if they were not sent then the item was not checked kwargs[checked_config] = 0 else: kwargs[checked_config] = 1 # If http password exists in config, do not overwrite when blank value received if kwargs.get('http_password'): if kwargs['http_password'] == ' ' and plexpy.CONFIG.HTTP_PASSWORD != '': if kwargs.get('http_hash_password') and not plexpy.CONFIG.HTTP_HASHED_PASSWORD: kwargs['http_password'] = make_hash(plexpy.CONFIG.HTTP_PASSWORD) kwargs['http_hashed_password'] = 1 else: kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD elif kwargs['http_password'] and kwargs.get('http_hash_password'): kwargs['http_password'] = make_hash(kwargs['http_password']) kwargs['http_hashed_password'] = 1 elif not kwargs.get('http_hash_password'): kwargs['http_hashed_password'] = 0 else: kwargs['http_hashed_password'] = 0 for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]: # the use prefix is fairly nice in the html, but does not match the actual config kwargs[plain_config] = kwargs[use_config] del kwargs[use_config] # Check if we should refresh our data server_changed = False reschedule = False https_changed = False refresh_libraries = False refresh_users = False # If we change any monitoring settings, make sure we reschedule tasks. if kwargs.get('check_github') != plexpy.CONFIG.CHECK_GITHUB or \ kwargs.get('monitoring_interval') != str(plexpy.CONFIG.MONITORING_INTERVAL) or \ kwargs.get('refresh_libraries_interval') != str(plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL) or \ kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \ kwargs.get('notify_recently_added') != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED or \ kwargs.get('monitor_pms_updates') != plexpy.CONFIG.MONITOR_PMS_UPDATES or \ kwargs.get('monitor_remote_access') != plexpy.CONFIG.MONITOR_REMOTE_ACCESS: reschedule = True # If we change the SSL setting for PMS or PMS remote setting, make sure we grab the new url. if kwargs.get('pms_ssl') != plexpy.CONFIG.PMS_SSL or \ kwargs.get('pms_is_remote') != plexpy.CONFIG.PMS_IS_REMOTE: server_changed = True # If we change the HTTPS setting, make sure we generate a new certificate. if kwargs.get('enable_https') and kwargs.get('https_create_cert'): if kwargs.get('https_domain') != plexpy.CONFIG.HTTPS_DOMAIN or \ kwargs.get('https_ip') != plexpy.CONFIG.HTTPS_IP or \ kwargs.get('https_cert') != plexpy.CONFIG.HTTPS_CERT or \ kwargs.get('https_key') != plexpy.CONFIG.HTTPS_KEY: https_changed = True # Remove config with 'hsec-' prefix and change home_sections to list if kwargs.get('home_sections'): for k in kwargs.keys(): if k.startswith('hsec-'): del kwargs[k] kwargs['home_sections'] = kwargs['home_sections'].split(',') # Remove config with 'hscard-' prefix and change home_stats_cards to list if kwargs.get('home_stats_cards'): for k in kwargs.keys(): if k.startswith('hscard-'): del kwargs[k] kwargs['home_stats_cards'] = kwargs['home_stats_cards'].split(',') if kwargs['home_stats_cards'] == ['first_run_wizard']: kwargs['home_stats_cards'] = plexpy.CONFIG.HOME_STATS_CARDS # Remove config with 'hlcard-' prefix and change home_library_cards to list if kwargs.get('home_library_cards'): for k in kwargs.keys(): if k.startswith('hlcard-'): del kwargs[k] kwargs['home_library_cards'] = kwargs['home_library_cards'].split(',') if kwargs['home_library_cards'] == ['first_run_wizard']: refresh_libraries = True # If we change the server, make sure we grab the new url and refresh libraries and users lists. if kwargs.get('server_changed'): del kwargs['server_changed'] server_changed = True refresh_users = True refresh_libraries = True plexpy.CONFIG.process_kwargs(kwargs) # Write the config plexpy.CONFIG.write() # Get new server URLs for SSL communications and get new server friendly name if server_changed: plextv.get_real_pms_url() pmsconnect.get_server_friendly_name() web_socket.reconnect() # Reconfigure scheduler if intervals changed if reschedule: plexpy.initialize_scheduler() # Generate a new HTTPS certificate if https_changed: create_https_certificates(plexpy.CONFIG.HTTPS_CERT, plexpy.CONFIG.HTTPS_KEY) # Refresh users table if our server IP changes. if refresh_libraries: threading.Thread(target=pmsconnect.refresh_libraries).start() # Refresh users table if our server IP changes. if refresh_users: threading.Thread(target=plextv.refresh_users).start() return {'result': 'success', 'message': 'Settings saved.'} @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def backup_config(self): """ Creates a manual backup of the plexpy.db file """ result = config.make_backup() if result: return {'result': 'success', 'message': 'Config backup successful.'} else: return {'result': 'error', 'message': 'Config backup failed.'} @cherrypy.expose @requireAuth(member_of("admin")) def get_scheduler_table(self, **kwargs): return serve_template(templatename="scheduler_table.html") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def backup_db(self): """ Creates a manual backup of the plexpy.db file """ result = database.make_backup() if result: return {'result': 'success', 'message': 'Database backup successful.'} else: return {'result': 'error', 'message': 'Database backup failed.'} @cherrypy.expose @requireAuth(member_of("admin")) def get_notification_agent_config(self, agent_id, **kwargs): if agent_id.isdigit(): config = notifiers.get_notification_agent_config(agent_id=agent_id) agents = notifiers.available_notification_agents() for agent in agents: if int(agent_id) == agent['id']: this_agent = agent break else: this_agent = None else: return None checkboxes = {'email_tls': checked(plexpy.CONFIG.EMAIL_TLS)} return serve_template(templatename="notification_config.html", title="Notification Configuration", agent=this_agent, data=config, checkboxes=checkboxes) @cherrypy.expose @requireAuth(member_of("admin")) def get_notification_agent_triggers(self, agent_id, **kwargs): if agent_id.isdigit(): agents = notifiers.available_notification_agents() for agent in agents: if int(agent_id) == agent['id']: this_agent = agent break else: this_agent = None else: return None return serve_template(templatename="notification_triggers_modal.html", title="Notification Triggers", data=this_agent) @cherrypy.expose @requireAuth(member_of("admin")) @addtoapi("notify") def send_notification(self, agent_id=None, subject='PlexPy', body='Test notification', notify_action=None, **kwargs): """ Send a notification using PlexPy. ``` Required parameters: agent_id(str): The id of the notification agent to use 9 # Boxcar2 17 # Browser 10 # Email 16 # Facebook 0 # Growl 12 # IFTTT 18 # Join 4 # NotifyMyAndroid 3 # Plex Home Theater 1 # Prowl 5 # Pushalot 6 # Pushbullet 7 # Pushover 15 # Scripts 14 # Slack 13 # Telegram 11 # Twitter 2 # XBMC subject(str): The subject of the message body(str): The body of the message Optional parameters: None Returns: None ``` """ cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" test = 'test ' if notify_action == 'test' else '' if agent_id.isdigit(): agents = notifiers.available_notification_agents() for agent in agents: if int(agent_id) == agent['id']: this_agent = agent break else: this_agent = None if this_agent: logger.debug(u"Sending %s%s notification." % (test, this_agent['name'])) if notifiers.send_notification(this_agent['id'], subject, body, notify_action, **kwargs): return "Notification sent." else: return "Notification failed." else: logger.debug(u"Unable to send %snotification, invalid notification agent id %s." % (test, agent_id)) return "Invalid notification agent id %s." % agent_id else: logger.debug(u"Unable to send %snotification, no notification agent id received." % test) return "No notification agent id received." @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def get_browser_notifications(self, **kwargs): browser = notifiers.Browser() result = browser.get_notifications() if result: notifications = result['notifications'] if notifications: return notifications else: return None else: logger.warn('Unable to retrieve browser notifications.') return None @cherrypy.expose @requireAuth(member_of("admin")) def facebookStep1(self): cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" facebook = notifiers.FacebookNotifier() return facebook._get_authorization() @cherrypy.expose @requireAuth(member_of("admin")) def facebookStep2(self, code): cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" facebook = notifiers.FacebookNotifier() result = facebook._get_credentials(code) # logger.info(u"result: " + str(result)) if result: return "Key verification successful, PlexPy can send notification to Facebook. You may close this page now." else: return "Unable to verify key" @cherrypy.expose @requireAuth(member_of("admin")) def osxnotifyregister(self, app): cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" from osxnotify import registerapp as osxnotify result, msg = osxnotify.registerapp(app) if result: osx_notify = notifiers.OSX_NOTIFY() osx_notify.notify('Registered', result, 'Success :-)') # logger.info(u"Registered %s, to re-register a different app, delete this app first" % result) else: logger.warn(msg) return msg @cherrypy.expose @requireAuth(member_of("admin")) def set_notification_config(self, **kwargs): for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]: # the use prefix is fairly nice in the html, but does not match the actual config kwargs[plain_config] = kwargs[use_config] del kwargs[use_config] plexpy.CONFIG.process_kwargs(kwargs) # Write the config plexpy.CONFIG.write() cherrypy.response.status = 200 @cherrypy.expose @requireAuth(member_of("admin")) @addtoapi() def import_database(self, app=None, database_path=None, table_name=None, import_ignore_interval=0, **kwargs): """ Import a PlexWatch or Plexivity database into PlexPy. ``` Required parameters: app (str): "plexwatch" or "plexivity" database_path (str): The full path to the plexwatch database file table_name (str): "processed" or "grouped" Optional parameters: import_ignore_interval (int): The minimum number of seconds for a stream to import Returns: None ``` """ if not app: return 'No app specified for import' if app.lower() == 'plexwatch': db_check_msg = plexwatch_import.validate_database(database=database_path, table_name=table_name) if db_check_msg == 'success': threading.Thread(target=plexwatch_import.import_from_plexwatch, kwargs={'database': database_path, 'table_name': table_name, 'import_ignore_interval': import_ignore_interval}).start() return 'Import has started. Check the PlexPy logs to monitor any problems.' else: return db_check_msg elif app.lower() == 'plexivity': db_check_msg = plexivity_import.validate_database(database=database_path, table_name=table_name) if db_check_msg == 'success': threading.Thread(target=plexivity_import.import_from_plexivity, kwargs={'database': database_path, 'table_name': table_name, 'import_ignore_interval': import_ignore_interval}).start() return 'Import has started. Check the PlexPy logs to monitor any problems.' else: return db_check_msg else: return 'App not recognized for import' @cherrypy.expose @requireAuth(member_of("admin")) def import_database_tool(self, app=None, **kwargs): if app == 'plexwatch': return serve_template(templatename="app_import.html", title="Import PlexWatch Database", app="PlexWatch") elif app == 'plexivity': return serve_template(templatename="app_import.html", title="Import Plexivity Database", app="Plexivity") logger.warn(u"No app specified for import.") return @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_pms_token(self, username=None, password=None, **kwargs): """ Get the user's Plex token used for PlexPy. ``` Required parameters: username (str): The Plex.tv username password (str): The Plex.tv password Optional parameters: None Returns: string: The Plex token used for PlexPy ``` """ if not username and not password: return None token = plextv.PlexTV(username=username, password=password) result = token.get_token() if result: return result['auth_token'] else: logger.warn(u"Unable to retrieve Plex.tv token.") return None @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, **kwargs): """ Get the PMS server identifier. ``` Required parameters: hostname (str): 'localhost' or '192.160.0.10' port (int): 32400 Optional parameters: ssl (int): 0 or 1 remote (int): 0 or 1 Returns: string: The unique PMS identifier ``` """ # Attempt to get the pms_identifier from plex.tv if the server is published # Works for all PMS SSL settings if not identifier and hostname and port: plex_tv = plextv.PlexTV() servers = plex_tv.discover() ip_address = get_ip(hostname) for server in servers: if (server['ip'] == hostname or server['ip'] == ip_address) and server['port'] == port: identifier = server['clientIdentifier'] break # Fallback to checking /identity endpoint is server is unpublished # Cannot set SSL settings on the PMS if unpublished so 'http' is okay if not identifier: request_handler = http_handler.HTTPHandler(host=hostname, port=port, token=None) uri = '/identity' request = request_handler.make_request(uri=uri, proto='http', request_type='GET', output_format='xml', no_token=True, timeout=10) if request: xml_head = request.getElementsByTagName('MediaContainer')[0] identifier = xml_head.getAttribute('machineIdentifier') if identifier: return identifier else: logger.warn('Unable to retrieve the PMS identifier.') return None @cherrypy.expose @requireAuth(member_of("admin")) @addtoapi() def get_server_pref(self, pref=None, **kwargs): """ Get a specified PMS server preference. ``` Required parameters: pref (str): Name of preference Returns: string: Value of preference ``` """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_server_pref(pref=pref) if result: return result else: logger.warn(u"Unable to retrieve data for get_server_pref.") @cherrypy.expose @requireAuth(member_of("admin")) def generateAPI(self): apikey = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32] logger.info(u"New API key generated.") return apikey @cherrypy.expose @requireAuth(member_of("admin")) def checkGithub(self): versioncheck.checkGithub() raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home") @cherrypy.expose @requireAuth(member_of("admin")) def do_state_change(self, signal, title, timer): message = title quote = self.random_arnold_quotes() plexpy.SIGNAL = signal return serve_template(templatename="shutdown.html", title=title, message=message, timer=timer, quote=quote) @cherrypy.expose @requireAuth(member_of("admin")) def shutdown(self): return self.do_state_change('shutdown', 'Shutting Down', 15) @cherrypy.expose @requireAuth(member_of("admin")) def restart(self): return self.do_state_change('restart', 'Restarting', 30) @cherrypy.expose @requireAuth(member_of("admin")) def update(self): return self.do_state_change('update', 'Updating', 120) ##### Info ##### @cherrypy.expose @requireAuth() def info(self, rating_key=None, source=None, query=None, **kwargs): metadata = None config = { "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER } if source == 'history': data_factory = datafactory.DataFactory() result = data_factory.get_metadata_details(rating_key=rating_key) if result: metadata = result['metadata'] poster_url = data_factory.get_poster_url(metadata=metadata) metadata['poster_url'] = poster_url else: pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=True) if result: metadata = result['metadata'] data_factory = datafactory.DataFactory() poster_url = data_factory.get_poster_url(metadata=metadata) metadata['poster_url'] = poster_url if metadata: if metadata['section_id'] and not allow_session_library(metadata['section_id']): raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source) else: if get_session_user_id(): raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) else: return self.update_metadata(rating_key, query) @cherrypy.expose @requireAuth() def get_item_children(self, rating_key='', **kwargs): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_item_children(rating_key) if result: return serve_template(templatename="info_children_list.html", data=result, title="Children List") else: logger.warn(u"Unable to retrieve data for get_item_children.") return serve_template(templatename="info_children_list.html", data=None, title="Children List") @cherrypy.expose @requireAuth() def pms_image_proxy(self, img='', rating_key=None, width='0', height='0', fallback=None, refresh=False, **kwargs): """ Gets an image from the PMS and saves it to the image cache directory. """ if not img and not rating_key: logger.error('No image input received.') return refresh = True if refresh == 'true' else False if rating_key and not img: img = '/library/metadata/%s/thumb/1337' % rating_key img_string = img.rsplit('/', 1)[0] if '/library/metadata' in img else img img_string += '%s%s' % (width, height) fp = hashlib.md5(img_string).hexdigest() fp += '.jpg' # we want to be able to preview the thumbs c_dir = os.path.join(plexpy.CONFIG.CACHE_DIR, 'images') ffp = os.path.join(c_dir, fp) if not os.path.exists(c_dir): os.mkdir(c_dir) try: if not plexpy.CONFIG.CACHE_IMAGES or refresh or 'indexes' in img: raise NotFound return serve_file(path=ffp, content_type='image/jpeg') except NotFound: # the image does not exist, download it from pms try: pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_image(img, width, height) if result and result[0]: cherrypy.response.headers['Content-type'] = result[1] if plexpy.CONFIG.CACHE_IMAGES and 'indexes' not in img: with open(ffp, 'wb') as f: f.write(result[0]) return result[0] else: raise Exception(u'PMS image request failed') except Exception as e: logger.warn(u'Failed to get image %s, falling back to %s.' % (img, fallback)) fbi = None if fallback == 'poster': fbi = common.DEFAULT_POSTER_THUMB elif fallback == 'cover': fbi = common.DEFAULT_COVER_THUMB elif fallback == 'art': fbi = common.DEFAULT_ART if fbi: fp = os.path.join(plexpy.PROG_DIR, 'data', fbi) return serve_file(path=fp, content_type='image/png') @cherrypy.expose @requireAuth(member_of("admin")) @addtoapi() def download_log(self): """ Download the PlexPy log file. """ log_file = logger.FILENAME try: logger.logger.flush() except: pass return serve_download(os.path.join(plexpy.CONFIG.LOG_DIR, log_file), name=log_file) @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def delete_image_cache(self): """ Delete and recreate the image cache directory. """ return self.delete_cache(folder='images') @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def delete_cache(self, folder=''): """ Delete and recreate the cache directory. """ cache_dir = os.path.join(plexpy.CONFIG.CACHE_DIR, folder) result = 'success' msg = 'Cleared the %scache.' % (folder + ' ' if folder else '') try: shutil.rmtree(cache_dir, ignore_errors=True) except OSError as e: result = 'error' msg = 'Failed to delete %s.' % cache_dir logger.exception(u'Failed to delete %s: %s.' % (cache_dir, e)) return {'result': result, 'message': msg} try: os.makedirs(cache_dir) except OSError as e: result = 'error' msg = 'Failed to make %s.' % cache_dir logger.exception(u'Failed to create %s: %s.' % (cache_dir, e)) return {'result': result, 'message': msg} logger.info(msg) return {'result': result, 'message': msg} @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def delete_poster_url(self, poster_url=''): if poster_url: data_factory = datafactory.DataFactory() result = data_factory.delete_poster_url(poster_url=poster_url) else: result = None if result: return {'message': result} else: return {'message': 'no data received'} ##### Search ##### @cherrypy.expose @requireAuth() def search(self, query=''): return serve_template(templatename="search.html", title="Search", query=query) @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi('search') def search_results(self, query, **kwargs): """ Get search results from the PMS. ``` Required parameters: query (str): The query string to search for Returns: json: {"results_count": 69, "results_list": {"movie": [{...}, {...}, ] }, {"episode": [{...}, {...}, ] }, {...} } ``` """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_search_results(query) if result: return result else: logger.warn(u"Unable to retrieve data for search_results.") @cherrypy.expose @requireAuth() def get_search_results_children(self, query, media_type=None, season_index=None, **kwargs): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_search_results(query) if media_type: result['results_list'] = {media_type: result['results_list'][media_type]} if media_type == 'season' and season_index: result['results_list']['season'] = [season for season in result['results_list']['season'] if season['media_index'] == season_index] if result: return serve_template(templatename="info_search_results_list.html", data=result, title="Search Result List") else: logger.warn(u"Unable to retrieve data for get_search_results_children.") return serve_template(templatename="info_search_results_list.html", data=None, title="Search Result List") ##### Update Metadata ##### @cherrypy.expose @requireAuth(member_of("admin")) def update_metadata(self, rating_key=None, query=None, update=False, **kwargs): query_string = query update = True if update == 'True' else False data_factory = datafactory.DataFactory() query = data_factory.get_search_query(rating_key=rating_key) if query and query_string: query['query_string'] = query_string if query: return serve_template(templatename="update_metadata.html", query=query, update=update, title="Info") else: logger.warn(u"Unable to retrieve data for update_metadata.") return serve_template(templatename="update_metadata.html", query=query, update=update, title="Info") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def update_metadata_details(self, old_rating_key, new_rating_key, media_type, **kwargs): """ Update the metadata in the PlexPy database by matching rating keys. Also updates all parents or children of the media item if it is a show/season/episode or artist/album/track. ``` Required parameters: old_rating_key (str): 12345 new_rating_key (str): 54321 media_type (str): "movie", "show", "season", "episode", "artist", "album", "track" Optional parameters: None Returns: None ``` """ if new_rating_key: data_factory = datafactory.DataFactory() pms_connect = pmsconnect.PmsConnect() old_key_list = data_factory.get_rating_keys_list(rating_key=old_rating_key, media_type=media_type) new_key_list = pms_connect.get_rating_keys_list(rating_key=new_rating_key, media_type=media_type) result = data_factory.update_metadata(old_key_list=old_key_list, new_key_list=new_key_list, media_type=media_type) if result: return {'message': result} else: return {'message': 'no data received'} # test code @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_new_rating_keys(self, rating_key='', media_type='', **kwargs): """ Get a list of new rating keys for the PMS of all of the item's parent/children. ``` Required parameters: rating_key (str): '12345' media_type (str): "movie", "show", "season", "episode", "artist", "album", "track" Optional parameters: None Returns: json: {} ``` """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_rating_keys_list(rating_key=rating_key, media_type=media_type) if result: return result else: logger.warn(u"Unable to retrieve data for get_new_rating_keys.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_old_rating_keys(self, rating_key='', media_type='', **kwargs): """ Get a list of old rating keys from the PlexPy database for all of the item's parent/children. ``` Required parameters: rating_key (str): '12345' media_type (str): "movie", "show", "season", "episode", "artist", "album", "track" Optional parameters: None Returns: json: {} ``` """ data_factory = datafactory.DataFactory() result = data_factory.get_rating_keys_list(rating_key=rating_key, media_type=media_type) if result: return result else: logger.warn(u"Unable to retrieve data for get_old_rating_keys.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def get_pms_sessions_json(self, **kwargs): """ Get all the current sessions. """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_sessions('json') if result: return result else: logger.warn(u"Unable to retrieve data for get_pms_sessions_json.") return False @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi("get_metadata") def get_metadata_details(self, rating_key='', media_info=False, **kwargs): """ Get the metadata for a media item. ``` Required parameters: rating_key (str): Rating key of the item media_info (bool): True or False wheter to get media info Optional parameters: None Returns: json: {"metadata": {"actors": [ "Kit Harington", "Emilia Clarke", "Isaac Hempstead-Wright", "Maisie Williams", "Liam Cunningham", ], "added_at": "1461572396", "art": "/library/metadata/1219/art/1462175063", "content_rating": "TV-MA", "directors": [ "Jeremy Podeswa" ], "duration": "2998290", "genres": [ "Adventure", "Drama", "Fantasy" ], "grandparent_rating_key": "1219", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_title": "Game of Thrones", "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en", "labels": [], "last_viewed_at": "1462165717", "library_name": "TV Shows", "media_index": "1", "media_type": "episode", "originally_available_at": "2016-04-24", "parent_media_index": "6", "parent_rating_key": "153036", "parent_thumb": "/library/metadata/153036/thumb/1462175062", "parent_title": "", "rating": "7.8", "rating_key": "153037", "section_id": "2", "studio": "HBO", "summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.", "tagline": "", "thumb": "/library/metadata/153037/thumb/1462175060", "title": "The Red Woman", "updated_at": "1462175060", "writers": [ "David Benioff", "D. B. Weiss" ], "year": "2016" } } ``` """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=media_info) if result: return result else: logger.warn(u"Unable to retrieve data for get_metadata_details.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi("get_recently_added") def get_recently_added_details(self, start='0', count='0', section_id='', **kwargs): """ Get all items that where recelty added to plex. ``` Required parameters: count (str): Number of items to return Optional parameters: start (str): The item number to start at section_id (str): The id of the Plex library section Returns: json: {"recently_added": [{"added_at": "1461572396", "grandparent_rating_key": "1219", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_title": "Game of Thrones", "library_name": "", "media_index": "1", "media_type": "episode", "parent_media_index": "6", "parent_rating_key": "153036", "parent_thumb": "/library/metadata/153036/thumb/1462175062", "parent_title": "", "rating_key": "153037", "section_id": "2", "thumb": "/library/metadata/153037/thumb/1462175060", "title": "The Red Woman", "year": "2016" }, {...}, {...} ] } ``` """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_recently_added_details(start=start, count=count, section_id=section_id) if result: return result else: logger.warn(u"Unable to retrieve data for get_recently_added_details.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def get_friends_list(self, **kwargs): """ Get the friends list of the server owner for Plex.tv. """ plex_tv = plextv.PlexTV() result = plex_tv.get_plextv_friends('json') if result: return result else: logger.warn(u"Unable to retrieve data for get_friends_list.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def get_user_details(self, **kwargs): """ Get all details about a the server's owner from Plex.tv. """ plex_tv = plextv.PlexTV() result = plex_tv.get_plextv_user_details('json') if result: return result else: logger.warn(u"Unable to retrieve data for get_user_details.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def get_server_list(self, **kwargs): """ Find all servers published on Plex.tv """ plex_tv = plextv.PlexTV() result = plex_tv.get_plextv_server_list('json') if result: return result else: logger.warn(u"Unable to retrieve data for get_server_list.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def get_sync_lists(self, machine_id='', **kwargs): """ Get all items that are currently synced from the PMS. """ plex_tv = plextv.PlexTV() result = plex_tv.get_plextv_sync_lists(machine_id=machine_id, output_format='json') if result: return result else: logger.warn(u"Unable to retrieve data for get_sync_lists.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def get_servers(self, **kwargs): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_server_list(output_format='json') if result: return result else: logger.warn(u"Unable to retrieve data for get_servers.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_servers_info(self, **kwargs): """ Get info about the PMS. ``` Required parameters: None Optional parameters: None Returns: json: [{"port": "32400", "host": "10.0.0.97", "version": "0.9.15.2.1663-7efd046", "name": "Winterfell-Server", "machine_identifier": "ds48g4r354a8v9byrrtr697g3g79w" } ] ``` """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_servers_info() if result: return result else: logger.warn(u"Unable to retrieve data for get_servers_info.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_server_identity(self, **kwargs): """ Get info about the local server. ``` Required parameters: None Optional parameters: None Returns: json: [{"machine_identifier": "ds48g4r354a8v9byrrtr697g3g79w", "version": "0.9.15.x.xxx-xxxxxxx" } ] ``` """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_server_identity() if result: return result else: logger.warn(u"Unable to retrieve data for get_server_identity.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_server_friendly_name(self, **kwargs): """ Get the name of the PMS. ``` Required parameters: None Optional parameters: None Returns: string: "Winterfell-Server" ``` """ result = pmsconnect.get_server_friendly_name() if result: return result else: logger.warn(u"Unable to retrieve data for get_server_friendly_name.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() @addtoapi() def get_activity(self, **kwargs): """ Get the current activity on the PMS. ``` Required parameters: None Optional parameters: None Returns: json: {"stream_count": 3, "session": [{"art": "/library/metadata/1219/art/1462175063", "aspect_ratio": "1.78", "audio_channels": "6", "audio_codec": "ac3", "audio_decision": "transcode", "bif_thumb": "/library/parts/274169/indexes/sd/", "bitrate": "10617", "container": "mkv", "content_rating": "TV-MA", "duration": "2998290", "friendly_name": "Mother of Dragons", "grandparent_rating_key": "1219", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_title": "Game of Thrones", "height": "1078", "indexes": 1, "ip_address": "xxx.xxx.xxx.xxx", "labels": [], "machine_id": "83f189w617623ccs6a1lqpby", "media_index": "1", "media_type": "episode", "parent_media_index": "6", "parent_rating_key": "153036", "parent_thumb": "/library/metadata/153036/thumb/1462175062", "parent_title": "", "platform": "Chrome", "player": "Plex Web (Chrome)", "progress_percent": "0", "rating_key": "153037", "section_id": "2", "session_key": "291", "state": "playing", "throttled": "1", "thumb": "/library/metadata/153037/thumb/1462175060", "title": "The Red Woman", "transcode_audio_channels": "2", "transcode_audio_codec": "aac", "transcode_container": "mkv", "transcode_height": "1078", "transcode_key": "tiv5p524wcupe8nxegc26s9k9", "transcode_progress": 2, "transcode_protocol": "http", "transcode_speed": "0.0", "transcode_video_codec": "h264", "transcode_width": "1920", "user": "DanyKhaleesi69", "user_id": 8008135, "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", "video_codec": "h264", "video_decision": "copy", "video_framerate": "24p", "video_resolution": "1080", "view_offset": "", "width": "1920", "year": "2016" }, {...}, {...} ] } ``` """ try: pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN) result = pms_connect.get_current_activity() if result: data_factory = datafactory.DataFactory() for session in result['sessions']: if not session['ip_address']: ip_address = data_factory.get_session_ip(session['session_key']) session['ip_address'] = ip_address return result else: logger.warn(u"Unable to retrieve data for get_activity.") except Exception as e: logger.exception(u"Unable to retrieve data for get_activity: %s" % e) @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi("get_libraries") def get_full_libraries_list(self, **kwargs): """ Get a list of all libraries on your server. ``` Required parameters: None Optional parameters: None Returns: json: [{"art": "/:/resources/show-fanart.jpg", "child_count": "3745", "count": "62", "parent_count": "240", "section_id": "2", "section_name": "TV Shows", "section_type": "show", "thumb": "/:/resources/show.png" }, {...}, {...} ] ``` """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_library_details() if result: return result else: logger.warn(u"Unable to retrieve data for get_full_libraries_list.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi("get_users") def get_full_users_list(self, **kwargs): """ Get a list of all users that have access to your server. ``` Required parameters: None Optional parameters: None Returns: json: [{"email": "Jon.Snow.1337@CastleBlack.com", "filter_all": "", "filter_movies": "", "filter_music": "", "filter_photos": "", "filter_tv": "", "is_allow_sync": null, "is_home_user": "1", "is_restricted": "0", "thumb": "https://plex.tv/users/k10w42309cynaopq/avatar", "user_id": "133788", "username": "Jon Snow" }, {...}, {...} ] ``` """ plex_tv = plextv.PlexTV() result = plex_tv.get_full_users_list() if result: return result else: logger.warn(u"Unable to retrieve data for get_full_users_list.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_synced_items(self, machine_id='', user_id='', **kwargs): """ Get a list of synced items on the PMS. ``` Required parameters: machine_id (str): The PMS identifier Optional parameters: user_id (str): The id of the Plex user Returns: json: [{"content_type": "video", "device_name": "Tyrion's iPad", "failure": "", "friendly_name": "Tyrion Lannister", "item_complete_count": "0", "item_count": "1", "item_downloaded_count": "0", "item_downloaded_percent_complete": 0, "metadata_type": "movie", "music_bitrate": "192", "photo_quality": "74", "platform": "iOS", "rating_key": "154092", "root_title": "Deadpool", "state": "pending", "sync_id": "11617019", "title": "Deadpool", "total_size": "0", "user_id": "696969", "username": "DrukenDwarfMan", "video_quality": "60" }, {...}, {...} ] ``` """ plex_tv = plextv.PlexTV() result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id) if result: return result else: logger.warn(u"Unable to retrieve data for get_synced_items.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def get_sync_transcode_queue(self, **kwargs): """ Return details for currently syncing items. """ pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_sync_transcode_queue(output_format='json') if result: return result else: logger.warn(u"Unable to retrieve data for get_sync_transcode_queue.") @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() def get_home_stats(self, grouping=0, time_range='30', stats_type=0, stats_count='5', **kwargs): """ Get the homepage watch statistics. ``` Required parameters: None Optional parameters: grouping (int): 0 or 1 time_range (str): The time range to calculate statistics, '30' stats_type (int): 0 for plays, 1 for duration stats_count (str): The number of top items to list, '5' Returns: json: [{"stat_id": "top_movies", "stat_type": "total_plays", "rows": [{...}] }, {"stat_id": "popular_movies", "rows": [{...}] }, {"stat_id": "top_tv", "stat_type": "total_plays", "rows": [{"content_rating": "TV-MA", "friendly_name": "", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "labels": [], "last_play": 1462380698, "media_type": "episode", "platform": "", "platform_type": "", "rating_key": 1219, "row_id": 1116, "section_id": 2, "thumb": "", "title": "Game of Thrones", "total_duration": 213302, "total_plays": 69, "user": "", "users_watched": "" }, {...}, {...} ] }, {"stat_id": "popular_tv", "rows": [{...}] }, {"stat_id": "top_music", "stat_type": "total_plays", "rows": [{...}] }, {"stat_id": "popular_music", "rows": [{...}] }, {"stat_id": "last_watched", "rows": [{...}] }, {"stat_id": "top_users", "stat_type": "total_plays", "rows": [{...}] }, {"stat_id": "top_platforms", "stat_type": "total_plays", "rows": [{...}] }, {"stat_id": "most_concurrent", "rows": [{...}] } ] ``` """ stats_cards = plexpy.CONFIG.HOME_STATS_CARDS notify_watched_percent = plexpy.CONFIG.NOTIFY_WATCHED_PERCENT data_factory = datafactory.DataFactory() result = data_factory.get_home_stats(grouping=grouping, time_range=time_range, stats_type=stats_type, stats_count=stats_count, stats_cards=stats_cards, notify_watched_percent=notify_watched_percent) if result: return result else: logger.warn(u"Unable to retrieve data for get_home_stats.") @cherrypy.expose @requireAuth(member_of("admin")) @addtoapi("arnold") def random_arnold_quotes(self, **kwargs): """ Get to the chopper! """ from random import randint quote_list = ['To crush your enemies, see them driven before you, and to hear the lamentation of their women!', 'Your clothes, give them to me, now!', 'Do it!', 'If it bleeds, we can kill it', 'See you at the party Richter!', 'Let off some steam, Bennett', 'I\'ll be back', 'Get to the chopper!', 'Hasta La Vista, Baby!', 'It\'s not a tumor!', 'Dillon, you son of a bitch!', 'Benny!! Screw you!!', 'Stop whining! You kids are soft. You lack discipline.', 'Nice night for a walk.', 'Stick around!', 'I need your clothes, your boots and your motorcycle.', 'No, it\'s not a tumor. It\'s not a tumor!', 'I LIED!', 'Are you Sarah Connor?', 'I\'m a cop you idiot!', 'Come with me if you want to live.', 'Who is your daddy and what does he do?', 'Oh, cookies! I can\'t wait to toss them.', 'Can you hurry up. My horse is getting tired.', 'What killed the dinosaurs? The Ice Age!', 'That\'s for sleeping with my wife!', 'Remember when I said I’d kill you last... I lied!', 'You want to be a farmer? Here\'s a couple of acres', 'Now, this is the plan. Get your ass to Mars.', 'I just had a terrible thought... What if this is a dream?' ] random_number = randint(0, len(quote_list) - 1) return quote_list[int(random_number)] ### API ### @cherrypy.expose def api(self, *args, **kwargs): if args and 'v2' in args[0]: return API2()._api_run(**kwargs) else: a = Api() a.checkParams(*args, **kwargs) return a.fetchData() @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def check_pms_updater(self): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_update_staus() return result