plexpy/plexpy/api.py
2015-10-04 20:38:37 +02:00

499 lines
20 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 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 <http://www.gnu.org/licenses/>.
from plexpy import versioncheck, logger, plextv, pmsconnect, datafactory, graphs, users
import os
import plexpy
import json
import traceback
import cherrypy
import re
import hashlib
import random
import xmltodict
cmd_list = ['getLogs', 'getVersion', 'checkGithub', 'shutdown',
'getSettings', 'restart', 'update', 'getApikey', 'getHistory',
'getMetadata', 'getUserips', 'getPlayby', 'getSync']
class Api(object):
def __init__(self, out='json'):
self.apikey = None
self.authenticated = False
self.cmd = None
self.kwargs = None
# For the responses
self.data = None
self.msg = None
self.result_type = 'error'
# Possible general params
self.callback = None
self.out_type = out
self.debug = None
def checkParams(self, *args, **kwargs):
if not plexpy.CONFIG.API_ENABLED:
self.msg = 'API not enabled'
elif not plexpy.CONFIG.API_KEY:
self.msg = 'API key not generated'
elif len(plexpy.CONFIG.API_KEY) != 32:
self.msg = 'API key not generated correctly'
elif 'apikey' not in kwargs:
self.msg = 'Parameter apikey is required'
elif kwargs.get('apikey', '') != plexpy.CONFIG.API_KEY:
self.msg = 'Invalid apikey'
elif 'cmd' not in kwargs:
self.msg = 'Parameter %s required. possible commands are: %s' % ', '.join(cmd_list)
elif 'cmd' in kwargs and kwargs.get('cmd') not in cmd_list:
self.msg = 'Unknown command, %s possible commands are: %s' % (kwargs.get('cmd', ''), ', '.join(cmd_list))
# Set default values or remove them from kwargs
self.callback = kwargs.pop('callback', None)
self.apikey = kwargs.pop('apikey', None)
self.cmd = kwargs.pop('cmd', None)
self.debug = kwargs.pop('debug', False)
# Allow override for the api.
self.out_type = kwargs.pop('out_type', 'json')
if self.apikey == plexpy.CONFIG.API_KEY and plexpy.CONFIG.API_ENABLED and self.cmd in cmd_list:
self.authenticated = True
self.msg = None
elif self.cmd == 'getApikey' and plexpy.CONFIG.API_ENABLED:
self.authenticated = True
# Remove the old error msg
self.msg = None
self.kwargs = kwargs
def _responds(self, result_type='success', data=None, msg=''):
if data is None:
data = {}
return {"response": {"result": result_type, "message": msg, "data": data}}
def _out_as(self, out):
if self.out_type == 'json':
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
try:
out = json.dumps(out, indent=4, sort_keys=True)
if self.callback is not None:
cherrypy.response.headers['Content-Type'] = 'application/javascript'
# wrap with JSONP call if requested
out = self.callback + '(' + out + ');'
# if we fail to generate the output fake an error
except Exception as e:
logger.info(u"API :: " + traceback.format_exc())
out['message'] = traceback.format_exc()
out['result'] = 'error'
if self.out_type == 'xml':
cherrypy.response.headers['Content-Type'] = 'application/xml'
try:
out = xmltodict.unparse(out, pretty=True)
except ValueError as e:
logger.error('Failed to parse xml result')
try:
out['message'] = e
out['result'] = 'error'
out = xmltodict.unparse(out, pretty=True)
except Exception as e:
logger.error('Failed to parse xml result error message')
out = '''<?xml version="1.0" encoding="utf-8"?>
<response>
<message>%s</message>
<data></data>
<result>error</result>
</response>
''' % e
return out
def fetchData(self):
logger.info('Recieved API command: %s' % self.cmd)
if self.cmd and self.authenticated:
methodtocall = getattr(self, "_" + self.cmd)
# Let the traceback hit cherrypy so we can
# see the traceback there
if self.debug:
methodtocall(**self.kwargs)
else:
try:
methodtocall(**self.kwargs)
except Exception as e:
logger.error(traceback.format_exc())
# Im just lazy, fix me plx
if self.data or isinstance(self.data, (dict, list)):
if len(self.data):
self.result_type = 'success'
return self._out_as(self._responds(result_type=self.result_type, msg=self.msg, data=self.data))
def _dic_from_query(self, query):
myDB = database.DBConnection()
rows = myDB.select(query)
rows_as_dic = []
for row in rows:
row_as_dic = dict(zip(row.keys(), row))
rows_as_dic.append(row_as_dic)
return rows_as_dic
def _getApikey(self, username='', password=''):
""" Returns api key, requires username and password is active """
apikey = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
if plexpy.CONFIG.HTTP_USERNAME and plexpy.CONFIG.HTTP_PASSWORD:
if username == plexpy.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
if plexpy.CONFIG.API_KEY:
self.data = plexpy.CONFIG.API_KEY
else:
self.data = apikey
plexpy.CONFIG.API_KEY = apikey
plexpy.CONFIG.write()
else:
self.msg = 'Authentication is enabled, please add the correct username and password to the parameters'
else:
if plexpy.CONFIG.API_KEY:
self.data = plexpy.CONFIG.API_KEY
else:
# Make a apikey if the doesn't exist
self.data = apikey
plexpy.CONFIG.API_KEY = apikey
plexpy.CONFIG.write()
return self.data
def _getLogs(self, sort='', search='', order='desc', regex='', **kwargs):
"""
Returns the log
Returns [{"response":
{"msg": "Hey",
"result": "success"},
"data": [{"time": "29-sept.2015",
"thread: "MainThread",
"msg: "Called x from y",
"loglevel": "DEBUG"
}
]
}
]
"""
logfile = os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log')
templog = []
start = int(kwargs.get('start', 0))
end = int(kwargs.get('end', 0))
if regex:
logger.debug('Filtering log using regex %s' % regex)
reg = re.compile('u' + regex, flags=re.I)
for line in open(logfile, 'r').readlines():
temp_loglevel_and_time = None
try:
temp_loglevel_and_time = line.split('- ')
loglvl = temp_loglevel_and_time[1].split(' :')[0].strip()
tl_tread = line.split(' :: ')
if loglvl is None:
msg = line.replace('\n', '')
else:
msg = line.split(' : ')[1].replace('\n', '')
thread = tl_tread[1].split(' : ')[0]
except IndexError:
# We assume this is a traceback
tl = (len(templog) - 1)
templog[tl]['msg'] += line.replace('\n', '')
continue
if len(line) > 1 and temp_loglevel_and_time is not None and loglvl in line:
d = {
'time': temp_loglevel_and_time[0],
'loglevel': loglvl,
'msg': msg.replace('\n', ''),
'thread': thread
}
templog.append(d)
if end > 0:
logger.debug('Slicing the log from %s to %s' % (start, end))
templog = templog[start:end]
if sort:
logger.debug('Sorting log based on %s' % sort)
templog = sorted(templog, key=lambda k: k[sort])
if search:
logger.debug('Searching log values for %s' % search)
tt = [d for d in templog for k, v in d.items() if search.lower() in v.lower()]
if len(tt):
templog = tt
if regex:
tt = []
for l in templog:
stringdict = ' '.join('{}{}'.format(k, v) for k, v in l.items())
if reg.search(stringdict):
tt.append(l)
if len(tt):
templog = tt
if order == 'desc':
templog = templog[::-1]
self.data = templog
return templog
def _getVersion(self, **kwargs):
self.data = {
'git_path': plexpy.CONFIG.GIT_PATH,
'install_type': plexpy.INSTALL_TYPE,
'current_version': plexpy.CURRENT_VERSION,
'latest_version': plexpy.LATEST_VERSION,
'commits_behind': plexpy.COMMITS_BEHIND,
}
self.result_type = 'success'
def _checkGithub(self, **kwargs):
versioncheck.checkGithub()
self._getVersion()
def _shutdown(self, **kwargs):
plexpy.SIGNAL = 'shutdown'
self.msg = 'Shutting down plexpy'
self.result_type = 'success'
def _restart(self, **kwargs):
plexpy.SIGNAL = 'restart'
self.msg = 'Restarting plexpy'
self.result_type = 'success'
def _update(self, **kwargs):
plexpy.SIGNAL = 'update'
self.msg = 'Updating plexpy'
self.result_type = 'success'
def _getHistory(self, user=None, user_id=None, rating_key='', parent_rating_key='', grandparent_rating_key='', start_date='', **kwargs):
custom_where = []
if user_id:
custom_where = [['user_id', user_id]]
elif user:
custom_where = [['user', user]]
if 'rating_key' in kwargs:
rating_key = kwargs.get('rating_key', "")
custom_where = [['rating_key', rating_key]]
if 'parent_rating_key' in kwargs:
rating_key = kwargs.get('parent_rating_key', "")
custom_where = [['parent_rating_key', rating_key]]
if 'grandparent_rating_key' in kwargs:
rating_key = kwargs.get('grandparent_rating_key', "")
custom_where = [['grandparent_rating_key', rating_key]]
if 'start_date' in kwargs:
start_date = kwargs.get('start_date', "")
custom_where = [['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date]]
data_factory = datafactory.DataFactory()
history = data_factory.get_history(kwargs=kwargs, custom_where=custom_where)
self.data = history
return self.data
def _getSync(self, machine_id=None, user_id=None, **kwargs):
pms_connect = pmsconnect.PmsConnect()
server_id = pms_connect.get_server_identity()
plex_tv = plextv.PlexTV()
if not machine_id:
result = plex_tv.get_synced_items(machine_id=server_id['machine_identifier'], user_id=user_id)
else:
result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id)
if result:
self.data = result
return result
else:
self.msg = 'Unable to retrieve sync data for user'
logger.warn('Unable to retrieve sync data for user.')
def _getMetadata(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata(rating_key, 'dict')
if result:
self.data = result
return result
else:
self.msg = 'Unable to retrive metadata %s' % rating_key
logger.warn('Unable to retrieve data.')
def _getSettings(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))]
config = {
"http_host": plexpy.CONFIG.HTTP_HOST,
"http_username": plexpy.CONFIG.HTTP_USERNAME,
"http_port": plexpy.CONFIG.HTTP_PORT,
"http_password": plexpy.CONFIG.HTTP_PASSWORD,
"launch_browser": bool(plexpy.CONFIG.LAUNCH_BROWSER),
"enable_https": bool(plexpy.CONFIG.ENABLE_HTTPS),
"https_cert": plexpy.CONFIG.HTTPS_CERT,
"https_key": plexpy.CONFIG.HTTPS_KEY,
"api_enabled": plexpy.CONFIG.API_ENABLED,
"api_key": plexpy.CONFIG.API_KEY,
"update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
"freeze_db": bool(plexpy.CONFIG.FREEZE_DB),
"log_dir": plexpy.CONFIG.LOG_DIR,
"cache_dir": plexpy.CONFIG.CACHE_DIR,
"check_github": bool(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": bool(plexpy.CONFIG.PMS_SSL),
"pms_use_bif": bool(plexpy.CONFIG.PMS_USE_BIF),
"pms_uuid": plexpy.CONFIG.PMS_UUID,
"date_format": plexpy.CONFIG.DATE_FORMAT,
"time_format": plexpy.CONFIG.TIME_FORMAT,
"grouping_global_history": bool(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY),
"grouping_user_history": bool(plexpy.CONFIG.GROUPING_USER_HISTORY),
"grouping_charts": bool(plexpy.CONFIG.GROUPING_CHARTS),
"tv_notify_enable": bool(plexpy.CONFIG.TV_NOTIFY_ENABLE),
"movie_notify_enable": bool(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE),
"music_notify_enable": bool(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE),
"tv_notify_on_start": bool(plexpy.CONFIG.TV_NOTIFY_ON_START),
"movie_notify_on_start": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_START),
"music_notify_on_start": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_START),
"tv_notify_on_stop": bool(plexpy.CONFIG.TV_NOTIFY_ON_STOP),
"movie_notify_on_stop": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_STOP),
"music_notify_on_stop": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_STOP),
"tv_notify_on_pause": bool(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE),
"movie_notify_on_pause": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE),
"music_notify_on_pause": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE),
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
"refresh_users_on_startup": bool(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
"ip_logging_enable": bool(plexpy.CONFIG.IP_LOGGING_ENABLE),
"video_logging_enable": bool(plexpy.CONFIG.VIDEO_LOGGING_ENABLE),
"music_logging_enable": bool(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
"pms_is_remote": bool(plexpy.CONFIG.PMS_IS_REMOTE),
"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,
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": bool(plexpy.CONFIG.HOME_STATS_TYPE),
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
"home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
"buffer_wait": plexpy.CONFIG.BUFFER_WAIT
}
self.data = config
return config
def _getUserips(self, user_id=None, user=None, **kwargs):
custom_where = []
if user_id:
custom_where = [['user_id', user_id]]
elif user:
custom_where = [['user', user]]
user_data = users.Users()
history = user_data.get_user_unique_ips(kwargs=kwargs,
custom_where=custom_where)
if history:
self.data = history
return history
else:
self.msg = 'Failed to find users ips'
def _getPlayby(self, time_range='30', y_axis='plays', playtype='total_plays_per_month', **kwargs):
graph = graphs.Graphs()
if playtype == 'total_plays_per_month':
result = graph.get_total_plays_per_month(y_axis=y_axis)
elif playtype == 'total_plays_per_day':
result = graph.get_total_plays_per_day(time_range=time_range, y_axis=y_axis)
elif playtype == 'total_plays_per_hourofday':
result = graph.get_total_plays_per_hourofday(time_range=time_range, y_axis=y_axis)
elif playtype == 'total_plays_per_dayofweek':
result = graph.get_total_plays_per_dayofweek(time_range=time_range, y_axis=y_axis)
elif playtype == 'stream_type_by_top_10_users':
result = graph.get_stream_type_by_top_10_users(time_range=time_range, y_axis=y_axis)
elif playtype == 'stream_type_by_top_10_platforms':
result = graph.get_stream_type_by_top_10_platforms(time_range=time_range, y_axis=y_axis)
elif playtype == 'total_plays_by_stream_resolution':
result = graph.get_total_plays_by_stream_resolution(time_range=time_range, y_axis=y_axis)
elif playtype == 'total_plays_by_source_resolution':
result = graph.get_total_plays_by_source_resolution(time_range=time_range, y_axis=y_axis)
elif playtype == 'total_plays_per_stream_type':
result = graph.get_total_plays_per_stream_type(time_range=time_range, y_axis=y_axis)
elif playtype == 'total_plays_by_top_10_users':
result = graph.get_total_plays_by_top_10_users(time_range=time_range, y_axis=y_axis)
elif playtype == 'total_plays_by_top_10_platforms':
result = graph.get_total_plays_by_top_10_platforms(time_range=time_range, y_axis=y_axis)
if result:
self.data = result
return result
else:
logger.warn('Unable to retrieve %s from db' % playtype)