mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-03-12 04:35:40 -07:00
1023 lines
44 KiB
Python
1023 lines
44 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# This file is part of Tautulli.
|
|
#
|
|
# Tautulli 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.
|
|
#
|
|
# Tautulli 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 Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from urllib.parse import parse_qsl
|
|
|
|
import arrow
|
|
import httpagentparser
|
|
|
|
import plexpy
|
|
from plexpy import common
|
|
from plexpy import database
|
|
from plexpy import datatables
|
|
from plexpy import helpers
|
|
from plexpy import libraries
|
|
from plexpy import logger
|
|
from plexpy import plextv
|
|
from plexpy import session
|
|
|
|
|
|
def refresh_users():
|
|
logger.info("Tautulli Users :: Requesting users list refresh...")
|
|
result = plextv.PlexTV().get_full_users_list()
|
|
|
|
server_id = plexpy.CONFIG.PMS_IDENTIFIER
|
|
if not server_id:
|
|
logger.error("Tautulli Users :: No PMS identifier, cannot refresh users. Verify server in settings.")
|
|
return
|
|
|
|
if result:
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
# Keep track of user_id to update is_active status
|
|
user_ids = [0] # Local user always considered active
|
|
new_users = []
|
|
|
|
for item in result:
|
|
if item.get('shared_libraries'):
|
|
item['shared_libraries'] = ';'.join(item['shared_libraries'])
|
|
# Only append user if libraries are shared
|
|
user_ids.append(helpers.cast_to_int(item['user_id']))
|
|
elif item.get('server_token'):
|
|
libs = libraries.Libraries().get_sections()
|
|
item['shared_libraries'] = ';'.join([str(l['section_id']) for l in libs])
|
|
# Only append user if libraries are shared
|
|
user_ids.append(helpers.cast_to_int(item['user_id']))
|
|
|
|
keys_dict = {"user_id": item.pop('user_id')}
|
|
|
|
# Check if we've set a custom avatar if so don't overwrite it.
|
|
if keys_dict['user_id']:
|
|
avatar_urls = monitor_db.select("SELECT thumb, custom_avatar_url "
|
|
"FROM users WHERE user_id = ?",
|
|
[keys_dict['user_id']])
|
|
if avatar_urls:
|
|
if not avatar_urls[0]['custom_avatar_url'] or \
|
|
avatar_urls[0]['custom_avatar_url'] == avatar_urls[0]['thumb']:
|
|
item['custom_avatar_url'] = item['thumb']
|
|
else:
|
|
item['custom_avatar_url'] = item['thumb']
|
|
|
|
# Check if title is the same as the username
|
|
if item['title'] == item['username']:
|
|
item['title'] = None
|
|
|
|
# Check if username is blank (Managed Users)
|
|
if not item['username']:
|
|
item['username'] = item['title']
|
|
|
|
result = monitor_db.upsert('users', key_dict=keys_dict, value_dict=item)
|
|
|
|
if result == 'insert':
|
|
new_users.append(item['username'])
|
|
|
|
query = "UPDATE users SET is_active = 0 WHERE user_id NOT IN ({})".format(", ".join(["?"] * len(user_ids)))
|
|
monitor_db.action(query=query, args=user_ids)
|
|
|
|
# Add new users to logger username filter
|
|
logger.filter_usernames(new_users)
|
|
|
|
logger.info("Tautulli Users :: Users list refreshed.")
|
|
return True
|
|
else:
|
|
logger.warn("Tautulli Users :: Unable to refresh users list.")
|
|
return False
|
|
|
|
|
|
class Users(object):
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def get_datatables_list(self, kwargs=None, grouping=None):
|
|
default_return = {'recordsFiltered': 0,
|
|
'recordsTotal': 0,
|
|
'draw': 0,
|
|
'data': []}
|
|
|
|
data_tables = datatables.DataTables()
|
|
|
|
custom_where = [['users.deleted_user', 0]]
|
|
|
|
if grouping is None:
|
|
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
|
|
|
if session.get_session_user_id():
|
|
custom_where.append(['users.user_id', session.get_session_user_id()])
|
|
|
|
if kwargs.get('user_id'):
|
|
custom_where.append(['users.user_id', kwargs.get('user_id')])
|
|
|
|
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
|
|
|
columns = ["users.id AS row_id",
|
|
"users.user_id",
|
|
"users.username",
|
|
"(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = '' \
|
|
THEN users.username ELSE users.friendly_name END) AS friendly_name",
|
|
"users.title",
|
|
"users.email",
|
|
"users.thumb AS user_thumb",
|
|
"users.custom_avatar_url AS custom_thumb",
|
|
"COUNT(DISTINCT %s) AS plays" % group_by,
|
|
"SUM(CASE WHEN session_history.stopped > 0 THEN (session_history.stopped - session_history.started) \
|
|
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
|
session_history.paused_counter END) AS duration",
|
|
"MAX(session_history.started) AS last_seen",
|
|
"MAX(session_history.id) AS history_row_id",
|
|
"session_history_metadata.full_title AS last_played",
|
|
"session_history.ip_address",
|
|
"session_history.platform",
|
|
"session_history.player",
|
|
"session_history.rating_key",
|
|
"session_history_metadata.media_type",
|
|
"session_history_metadata.thumb",
|
|
"session_history_metadata.parent_thumb",
|
|
"session_history_metadata.grandparent_thumb",
|
|
"session_history_metadata.parent_title",
|
|
"session_history_metadata.year",
|
|
"session_history_metadata.media_index",
|
|
"session_history_metadata.parent_media_index",
|
|
"session_history_metadata.live",
|
|
"session_history_metadata.added_at",
|
|
"session_history_metadata.originally_available_at",
|
|
"session_history_metadata.guid",
|
|
"session_history_media_info.transcode_decision",
|
|
"users.do_notify AS do_notify",
|
|
"users.keep_history AS keep_history",
|
|
"users.allow_guest AS allow_guest",
|
|
"users.is_active AS is_active"
|
|
]
|
|
try:
|
|
query = data_tables.ssp_query(table_name='users',
|
|
columns=columns,
|
|
custom_where=custom_where,
|
|
group_by=['users.user_id'],
|
|
join_types=['LEFT OUTER JOIN',
|
|
'LEFT OUTER JOIN',
|
|
'LEFT OUTER JOIN'],
|
|
join_tables=['session_history',
|
|
'session_history_metadata',
|
|
'session_history_media_info'],
|
|
join_evals=[['session_history.user_id', 'users.user_id'],
|
|
['session_history.id', 'session_history_metadata.id'],
|
|
['session_history.id', 'session_history_media_info.id']],
|
|
kwargs=kwargs)
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_list: %s." % e)
|
|
return default_return
|
|
|
|
users = query['result']
|
|
|
|
rows = []
|
|
for item in users:
|
|
if item['media_type'] == 'episode' and item['parent_thumb']:
|
|
thumb = item['parent_thumb']
|
|
elif item['media_type'] == 'episode':
|
|
thumb = item['grandparent_thumb']
|
|
else:
|
|
thumb = item['thumb']
|
|
|
|
if item['custom_thumb'] and item['custom_thumb'] != item['user_thumb']:
|
|
user_thumb = item['custom_thumb']
|
|
elif item['user_thumb']:
|
|
user_thumb = item['user_thumb']
|
|
else:
|
|
user_thumb = common.DEFAULT_USER_THUMB
|
|
|
|
# Rename Mystery platform names
|
|
platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
|
|
|
row = {'row_id': item['row_id'],
|
|
'user_id': item['user_id'],
|
|
'username': item['username'],
|
|
'friendly_name': item['friendly_name'],
|
|
'title': item['title'],
|
|
'email': item['email'],
|
|
'user_thumb': user_thumb,
|
|
'plays': item['plays'],
|
|
'duration': item['duration'],
|
|
'last_seen': item['last_seen'],
|
|
'last_played': item['last_played'],
|
|
'history_row_id': item['history_row_id'],
|
|
'ip_address': item['ip_address'],
|
|
'platform': platform,
|
|
'player': item['player'],
|
|
'rating_key': item['rating_key'],
|
|
'media_type': item['media_type'],
|
|
'thumb': thumb,
|
|
'parent_title': item['parent_title'],
|
|
'year': item['year'],
|
|
'media_index': item['media_index'],
|
|
'parent_media_index': item['parent_media_index'],
|
|
'live': item['live'],
|
|
'originally_available_at': item['originally_available_at'],
|
|
'guid': item['guid'],
|
|
'transcode_decision': item['transcode_decision'],
|
|
'do_notify': item['do_notify'],
|
|
'keep_history': item['keep_history'],
|
|
'allow_guest': item['allow_guest'],
|
|
'is_active': item['is_active']
|
|
}
|
|
|
|
rows.append(row)
|
|
|
|
dict = {'recordsFiltered': query['filteredCount'],
|
|
'recordsTotal': query['totalCount'],
|
|
'data': session.friendly_name_to_username(rows),
|
|
'draw': query['draw']
|
|
}
|
|
|
|
return dict
|
|
|
|
def get_datatables_unique_ips(self, user_id=None, kwargs=None):
|
|
default_return = {'recordsFiltered': 0,
|
|
'recordsTotal': 0,
|
|
'draw': 0,
|
|
'data': []}
|
|
|
|
if not session.allow_session_user(user_id):
|
|
return default_return
|
|
|
|
data_tables = datatables.DataTables()
|
|
|
|
custom_where = ['users.user_id', user_id]
|
|
|
|
columns = ["session_history.id AS history_row_id",
|
|
"MIN(session_history.started) AS first_seen",
|
|
"MAX(session_history.started) AS last_seen",
|
|
"session_history.ip_address",
|
|
"COUNT(session_history.id) AS play_count",
|
|
"session_history.platform",
|
|
"session_history.player",
|
|
"session_history.rating_key",
|
|
"session_history_metadata.full_title AS last_played",
|
|
"session_history_metadata.thumb",
|
|
"session_history_metadata.parent_thumb",
|
|
"session_history_metadata.grandparent_thumb",
|
|
"session_history_metadata.media_type",
|
|
"session_history_metadata.parent_title",
|
|
"session_history_metadata.year",
|
|
"session_history_metadata.media_index",
|
|
"session_history_metadata.parent_media_index",
|
|
"session_history_metadata.live",
|
|
"session_history_metadata.added_at",
|
|
"session_history_metadata.originally_available_at",
|
|
"session_history_metadata.guid",
|
|
"session_history_media_info.transcode_decision",
|
|
"session_history.user",
|
|
"session_history.user_id as custom_user_id",
|
|
"(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = '' \
|
|
THEN users.username ELSE users.friendly_name END) AS friendly_name"
|
|
]
|
|
|
|
try:
|
|
query = data_tables.ssp_query(table_name='session_history',
|
|
columns=columns,
|
|
custom_where=[custom_where],
|
|
group_by=['ip_address'],
|
|
join_types=['JOIN',
|
|
'JOIN',
|
|
'JOIN'],
|
|
join_tables=['users',
|
|
'session_history_metadata',
|
|
'session_history_media_info'],
|
|
join_evals=[['session_history.user_id', 'users.user_id'],
|
|
['session_history.id', 'session_history_metadata.id'],
|
|
['session_history.id', 'session_history_media_info.id']],
|
|
kwargs=kwargs)
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_unique_ips: %s." % e)
|
|
return default_return
|
|
|
|
results = query['result']
|
|
|
|
rows = []
|
|
for item in results:
|
|
if item["media_type"] == 'episode' and item["parent_thumb"]:
|
|
thumb = item["parent_thumb"]
|
|
elif item["media_type"] == 'episode':
|
|
thumb = item["grandparent_thumb"]
|
|
else:
|
|
thumb = item["thumb"]
|
|
|
|
# Rename Mystery platform names
|
|
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
|
|
|
row = {'history_row_id': item['history_row_id'],
|
|
'last_seen': item['last_seen'],
|
|
'first_seen': item['first_seen'],
|
|
'ip_address': item['ip_address'],
|
|
'play_count': item['play_count'],
|
|
'platform': platform,
|
|
'player': item['player'],
|
|
'last_played': item['last_played'],
|
|
'rating_key': item['rating_key'],
|
|
'thumb': thumb,
|
|
'media_type': item['media_type'],
|
|
'parent_title': item['parent_title'],
|
|
'year': item['year'],
|
|
'media_index': item['media_index'],
|
|
'parent_media_index': item['parent_media_index'],
|
|
'live': item['live'],
|
|
'originally_available_at': item['originally_available_at'],
|
|
'guid': item['guid'],
|
|
'transcode_decision': item['transcode_decision'],
|
|
'friendly_name': item['friendly_name'],
|
|
'user_id': item['custom_user_id']
|
|
}
|
|
|
|
rows.append(row)
|
|
|
|
dict = {'recordsFiltered': query['filteredCount'],
|
|
'recordsTotal': query['totalCount'],
|
|
'data': session.friendly_name_to_username(rows),
|
|
'draw': query['draw']
|
|
}
|
|
|
|
return dict
|
|
|
|
def set_config(self, user_id=None, friendly_name='', custom_thumb='', do_notify=1, keep_history=1, allow_guest=1):
|
|
if str(user_id).isdigit():
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
user = monitor_db.select_single('SELECT username FROM users WHERE user_id = ?', [user_id])
|
|
if user.get('username') == friendly_name:
|
|
friendly_name = None
|
|
|
|
key_dict = {'user_id': user_id}
|
|
value_dict = {'friendly_name': friendly_name,
|
|
'custom_avatar_url': custom_thumb,
|
|
'do_notify': do_notify,
|
|
'keep_history': keep_history,
|
|
'allow_guest': allow_guest
|
|
}
|
|
try:
|
|
monitor_db.upsert('users', value_dict, key_dict)
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for set_config: %s." % e)
|
|
|
|
def get_details(self, user_id=None, user=None, email=None, include_last_seen=False):
|
|
default_return = {'row_id': 0,
|
|
'user_id': 0,
|
|
'username': 'Local',
|
|
'friendly_name': 'Local',
|
|
'user_thumb': common.DEFAULT_USER_THUMB,
|
|
'email': '',
|
|
'is_active': 1,
|
|
'is_admin': '',
|
|
'is_home_user': 0,
|
|
'is_allow_sync': 0,
|
|
'is_restricted': 0,
|
|
'do_notify': 0,
|
|
'keep_history': 1,
|
|
'allow_guest': 0,
|
|
'deleted_user': 0,
|
|
'shared_libraries': (),
|
|
'last_seen': None
|
|
}
|
|
|
|
if user_id in (None, '') and not user and not email:
|
|
return default_return
|
|
|
|
user_details = self.get_user_details(user_id=user_id, user=user, email=email,
|
|
include_last_seen=include_last_seen)
|
|
|
|
if user_details:
|
|
return user_details
|
|
|
|
else:
|
|
logger.warn("Tautulli Users :: Unable to retrieve user %s from database. Requesting user list refresh."
|
|
% user_id if user_id else user)
|
|
# Let's first refresh the user list to make sure the user isn't newly added and not in the db yet
|
|
refresh_users()
|
|
|
|
user_details = self.get_user_details(user_id=user_id, user=user, email=email,
|
|
include_last_seen=include_last_seen)
|
|
|
|
if user_details:
|
|
return user_details
|
|
|
|
else:
|
|
logger.warn("Tautulli Users :: Unable to retrieve user %s from database. Returning 'Local' user."
|
|
% user_id if user_id else user)
|
|
# If there is no user data we must return something
|
|
# Use "Local" user to retain compatibility with PlexWatch database value
|
|
return default_return
|
|
|
|
def get_user_details(self, user_id=None, user=None, email=None, include_last_seen=False):
|
|
last_seen = 'NULL'
|
|
join = ''
|
|
if include_last_seen:
|
|
last_seen = "MAX(session_history.started)"
|
|
join = "LEFT OUTER JOIN session_history ON users.user_id = session_history.user_id"
|
|
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
try:
|
|
if str(user_id).isdigit():
|
|
where = "users.user_id = ?"
|
|
args = [user_id]
|
|
elif user:
|
|
where = "users.username = ?"
|
|
args = [user]
|
|
elif email:
|
|
where = "users.email = ?"
|
|
args = [email]
|
|
else:
|
|
raise Exception("Missing user_id, username, or email")
|
|
|
|
query = "SELECT users.id AS row_id, users.user_id, username, friendly_name, " \
|
|
"thumb AS user_thumb, custom_avatar_url AS custom_thumb, " \
|
|
"email, is_active, is_admin, is_home_user, is_allow_sync, is_restricted, " \
|
|
"do_notify, keep_history, deleted_user, " \
|
|
"allow_guest, shared_libraries, %s AS last_seen " \
|
|
"FROM users %s " \
|
|
"WHERE %s COLLATE NOCASE" % (last_seen, join, where)
|
|
result = monitor_db.select(query, args=args)
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_user_details: %s." % e)
|
|
result = []
|
|
|
|
user_details = {}
|
|
if result:
|
|
for item in result:
|
|
if session.get_session_user_id():
|
|
friendly_name = session.get_session_user()
|
|
elif item['friendly_name']:
|
|
friendly_name = item['friendly_name']
|
|
else:
|
|
friendly_name = item['username']
|
|
|
|
if item['custom_thumb'] and item['custom_thumb'] != item['user_thumb']:
|
|
user_thumb = item['custom_thumb']
|
|
elif item['user_thumb']:
|
|
user_thumb = item['user_thumb']
|
|
else:
|
|
user_thumb = common.DEFAULT_USER_THUMB
|
|
|
|
shared_libraries = tuple(item['shared_libraries'].split(';')) if item['shared_libraries'] else ()
|
|
|
|
user_details = {'row_id': item['row_id'],
|
|
'user_id': item['user_id'],
|
|
'username': item['username'],
|
|
'friendly_name': friendly_name,
|
|
'user_thumb': user_thumb,
|
|
'email': item['email'],
|
|
'is_active': item['is_active'],
|
|
'is_admin': item['is_admin'],
|
|
'is_home_user': item['is_home_user'],
|
|
'is_allow_sync': item['is_allow_sync'],
|
|
'is_restricted': item['is_restricted'],
|
|
'do_notify': item['do_notify'],
|
|
'keep_history': item['keep_history'],
|
|
'deleted_user': item['deleted_user'],
|
|
'allow_guest': item['allow_guest'],
|
|
'shared_libraries': shared_libraries,
|
|
'last_seen': item['last_seen']
|
|
}
|
|
return user_details
|
|
|
|
def get_watch_time_stats(self, user_id=None, grouping=None, query_days=None):
|
|
if not session.allow_session_user(user_id):
|
|
return []
|
|
|
|
if grouping is None:
|
|
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
|
|
|
if query_days and query_days is not None:
|
|
query_days = map(helpers.cast_to_int, str(query_days).split(','))
|
|
else:
|
|
query_days = [1, 7, 30, 0]
|
|
|
|
timestamp = helpers.timestamp()
|
|
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
user_watch_time_stats = []
|
|
|
|
group_by = 'reference_id' if grouping else 'id'
|
|
|
|
for days in query_days:
|
|
timestamp_query = timestamp - days * 24 * 60 * 60
|
|
|
|
try:
|
|
if days > 0:
|
|
if str(user_id).isdigit():
|
|
query = "SELECT (SUM(stopped - started) - " \
|
|
" SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END)) AS total_time, " \
|
|
"COUNT(DISTINCT %s) AS total_plays " \
|
|
"FROM session_history " \
|
|
"WHERE stopped >= %s " \
|
|
"AND user_id = ? " % (group_by, timestamp_query)
|
|
result = monitor_db.select(query, args=[user_id])
|
|
else:
|
|
result = []
|
|
else:
|
|
if str(user_id).isdigit():
|
|
query = "SELECT (SUM(stopped - started) - " \
|
|
" SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END)) AS total_time, " \
|
|
"COUNT(DISTINCT %s) AS total_plays " \
|
|
"FROM session_history " \
|
|
"WHERE user_id = ? " % group_by
|
|
result = monitor_db.select(query, args=[user_id])
|
|
else:
|
|
result = []
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_watch_time_stats: %s." % e)
|
|
result = []
|
|
|
|
for item in result:
|
|
if item['total_time']:
|
|
total_time = item['total_time']
|
|
total_plays = item['total_plays']
|
|
else:
|
|
total_time = 0
|
|
total_plays = 0
|
|
|
|
row = {'query_days': days,
|
|
'total_time': total_time,
|
|
'total_plays': total_plays
|
|
}
|
|
|
|
user_watch_time_stats.append(row)
|
|
|
|
return user_watch_time_stats
|
|
|
|
def get_player_stats(self, user_id=None, grouping=None):
|
|
if not session.allow_session_user(user_id):
|
|
return []
|
|
|
|
if grouping is None:
|
|
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
|
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
player_stats = []
|
|
result_id = 0
|
|
|
|
group_by = 'reference_id' if grouping else 'id'
|
|
|
|
try:
|
|
if str(user_id).isdigit():
|
|
query = "SELECT player, COUNT(DISTINCT %s) as total_plays, (SUM(stopped - started) - " \
|
|
"SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END)) AS total_time, " \
|
|
"platform " \
|
|
"FROM session_history " \
|
|
"WHERE user_id = ? " \
|
|
"GROUP BY player " \
|
|
"ORDER BY total_plays DESC, total_time DESC" % group_by
|
|
result = monitor_db.select(query, args=[user_id])
|
|
else:
|
|
result = []
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_player_stats: %s." % e)
|
|
result = []
|
|
|
|
for item in result:
|
|
# Rename Mystery platform names
|
|
platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
|
platform_name = next((v for k, v in common.PLATFORM_NAMES.items() if k in platform.lower()), 'default')
|
|
|
|
row = {'player_name': item['player'],
|
|
'platform': platform,
|
|
'platform_name': platform_name,
|
|
'total_plays': item['total_plays'],
|
|
'total_time': item['total_time'],
|
|
'result_id': result_id
|
|
}
|
|
player_stats.append(row)
|
|
result_id += 1
|
|
|
|
return player_stats
|
|
|
|
def get_recently_watched(self, user_id=None, limit='10'):
|
|
if not session.allow_session_user(user_id):
|
|
return []
|
|
|
|
monitor_db = database.MonitorDatabase()
|
|
recently_watched = []
|
|
|
|
if not limit.isdigit():
|
|
limit = '10'
|
|
|
|
try:
|
|
if str(user_id).isdigit():
|
|
query = "SELECT session_history.id, session_history.media_type, guid, " \
|
|
"session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, " \
|
|
"title, parent_title, grandparent_title, original_title, " \
|
|
"thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, " \
|
|
"year, originally_available_at, added_at, live, started, user " \
|
|
"FROM session_history_metadata " \
|
|
"JOIN session_history ON session_history_metadata.id = session_history.id " \
|
|
"WHERE user_id = ? " \
|
|
"GROUP BY (CASE WHEN session_history.media_type = 'track' THEN session_history.parent_rating_key " \
|
|
" ELSE session_history.rating_key END) " \
|
|
"ORDER BY MAX(started) DESC LIMIT ?"
|
|
result = monitor_db.select(query, args=[user_id, limit])
|
|
else:
|
|
result = []
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_recently_watched: %s." % e)
|
|
result = []
|
|
|
|
for row in result:
|
|
if row['media_type'] == 'episode' and row['parent_thumb']:
|
|
thumb = row['parent_thumb']
|
|
elif row['media_type'] == 'episode':
|
|
thumb = row['grandparent_thumb']
|
|
else:
|
|
thumb = row['thumb']
|
|
|
|
recent_output = {'row_id': row['id'],
|
|
'media_type': row['media_type'],
|
|
'rating_key': row['rating_key'],
|
|
'parent_rating_key': row['parent_rating_key'],
|
|
'grandparent_rating_key': row['grandparent_rating_key'],
|
|
'title': row['title'],
|
|
'parent_title': row['parent_title'],
|
|
'grandparent_title': row['grandparent_title'],
|
|
'original_title': row['original_title'],
|
|
'thumb': thumb,
|
|
'media_index': row['media_index'],
|
|
'parent_media_index': row['parent_media_index'],
|
|
'year': row['year'],
|
|
'originally_available_at': row['originally_available_at'],
|
|
'live': row['live'],
|
|
'guid': row['guid'],
|
|
'time': row['started'],
|
|
'user': row['user']
|
|
}
|
|
recently_watched.append(recent_output)
|
|
|
|
return recently_watched
|
|
|
|
def get_users(self, include_deleted=False):
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
where = '' if include_deleted else 'WHERE deleted_user = 0'
|
|
|
|
try:
|
|
query = "SELECT id AS row_id, user_id, username, friendly_name, thumb, custom_avatar_url, email, " \
|
|
"is_active, is_admin, is_home_user, is_allow_sync, is_restricted, " \
|
|
"do_notify, keep_history, allow_guest, shared_libraries, " \
|
|
"filter_all, filter_movies, filter_tv, filter_music, filter_photos " \
|
|
"FROM users %s" % where
|
|
result = monitor_db.select(query=query)
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_users: %s." % e)
|
|
return []
|
|
|
|
users = []
|
|
for item in result:
|
|
shared_libraries = tuple(item['shared_libraries'].split(';')) if item['shared_libraries'] else ()
|
|
|
|
user = {'row_id': item['row_id'],
|
|
'user_id': item['user_id'],
|
|
'username': item['username'],
|
|
'friendly_name': item['friendly_name'] or item['username'],
|
|
'thumb': item['custom_avatar_url'] or item['thumb'],
|
|
'email': item['email'],
|
|
'is_active': item['is_active'],
|
|
'is_admin': item['is_admin'],
|
|
'is_home_user': item['is_home_user'],
|
|
'is_allow_sync': item['is_allow_sync'],
|
|
'is_restricted': item['is_restricted'],
|
|
'do_notify': item['do_notify'],
|
|
'keep_history': item['keep_history'],
|
|
'allow_guest': item['allow_guest'],
|
|
'shared_libraries': shared_libraries,
|
|
'filter_all': item['filter_all'],
|
|
'filter_movies': item['filter_movies'],
|
|
'filter_tv': item['filter_tv'],
|
|
'filter_music': item['filter_music'],
|
|
'filter_photos': item['filter_photos'],
|
|
}
|
|
users.append(user)
|
|
|
|
return users
|
|
|
|
def delete(self, user_id=None, row_ids=None, purge_only=False):
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
if row_ids and row_ids is not None:
|
|
row_ids = list(map(helpers.cast_to_int, row_ids.split(',')))
|
|
|
|
# Get the user_ids corresponding to the row_ids
|
|
result = monitor_db.select("SELECT user_id FROM users "
|
|
"WHERE id IN ({})".format(",".join(["?"] * len(row_ids))), row_ids)
|
|
|
|
success = []
|
|
for user in result:
|
|
success.append(self.delete(user_id=user['user_id'],
|
|
purge_only=purge_only))
|
|
return all(success)
|
|
|
|
elif str(user_id).isdigit():
|
|
delete_success = database.delete_user_history(user_id=user_id)
|
|
|
|
if purge_only:
|
|
return delete_success
|
|
else:
|
|
logger.info("Tautulli Users :: Deleting user with user_id %s from database."
|
|
% user_id)
|
|
try:
|
|
monitor_db.action("UPDATE users "
|
|
"SET deleted_user = 1, keep_history = 0, do_notify = 0 "
|
|
"WHERE user_id = ?", [user_id])
|
|
return delete_success
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for delete: %s." % e)
|
|
|
|
else:
|
|
return False
|
|
|
|
def undelete(self, user_id=None, username=None):
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
try:
|
|
if user_id and str(user_id).isdigit():
|
|
query = "SELECT * FROM users WHERE user_id = ?"
|
|
result = monitor_db.select(query=query, args=[user_id])
|
|
if result:
|
|
logger.info("Tautulli Users :: Re-adding user with id %s to database." % user_id)
|
|
monitor_db.action("UPDATE users "
|
|
"SET deleted_user = 0, keep_history = 1, do_notify = 1 "
|
|
"WHERE user_id = ?", [user_id])
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
elif username:
|
|
query = "SELECT * FROM users WHERE username = ?"
|
|
result = monitor_db.select(query=query, args=[username])
|
|
if result:
|
|
logger.info("Tautulli Users :: Re-adding user with username %s to database." % username)
|
|
monitor_db.action("UPDATE users "
|
|
"SET deleted_user = 0, keep_history = 1, do_notify = 1 "
|
|
"WHERE username = ?", [username])
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for undelete: %s." % e)
|
|
|
|
# Keep method for PlexWatch/Plexivity import
|
|
def get_user_id(self, user=None):
|
|
if user:
|
|
try:
|
|
monitor_db = database.MonitorDatabase()
|
|
query = "SELECT user_id FROM users WHERE username = ?"
|
|
result = monitor_db.select_single(query, args=[user])
|
|
if result:
|
|
return result['user_id']
|
|
else:
|
|
return None
|
|
except:
|
|
return None
|
|
|
|
return None
|
|
|
|
def get_user_names(self, kwargs=None):
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
user_cond = ''
|
|
if session.get_session_user_id():
|
|
user_cond = "AND user_id = %s " % session.get_session_user_id()
|
|
|
|
try:
|
|
query = "SELECT user_id, " \
|
|
"(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = '' \
|
|
THEN users.username ELSE users.friendly_name END) AS friendly_name " \
|
|
"FROM users " \
|
|
"WHERE deleted_user = 0 %s" % user_cond
|
|
|
|
result = monitor_db.select(query)
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_user_names: %s." % e)
|
|
return None
|
|
|
|
return session.friendly_name_to_username(result)
|
|
|
|
def get_tokens(self, user_id=None):
|
|
tokens = {
|
|
'allow_guest': 0,
|
|
'user_token': '',
|
|
'server_token': ''
|
|
}
|
|
|
|
if user_id:
|
|
try:
|
|
monitor_db = database.MonitorDatabase()
|
|
query = "SELECT allow_guest, user_token, server_token FROM users " \
|
|
"WHERE user_id = ? AND deleted_user = 0"
|
|
result = monitor_db.select_single(query, args=[user_id])
|
|
if result:
|
|
tokens = {'allow_guest': result['allow_guest'],
|
|
'user_token': result['user_token'],
|
|
'server_token': result['server_token']
|
|
}
|
|
return tokens
|
|
else:
|
|
return tokens
|
|
except:
|
|
return tokens
|
|
|
|
return tokens
|
|
|
|
def get_filters(self, user_id=None):
|
|
if not user_id:
|
|
return {}
|
|
|
|
try:
|
|
monitor_db = database.MonitorDatabase()
|
|
query = "SELECT filter_all, filter_movies, filter_tv, filter_music, filter_photos FROM users " \
|
|
"WHERE user_id = ?"
|
|
result = monitor_db.select_single(query, args=[user_id])
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_filters: %s." % e)
|
|
result = {}
|
|
|
|
filters_list = {}
|
|
for k, v in result.items():
|
|
filters = {}
|
|
|
|
for f in v.split('|'):
|
|
if 'contentRating=' in f or 'label=' in f:
|
|
filters.update(dict(parse_qsl(f)))
|
|
|
|
filters['content_rating'] = tuple(f for f in filters.pop('contentRating', '').split(',') if f)
|
|
filters['labels'] = tuple(f for f in filters.pop('label', '').split(',') if f)
|
|
|
|
filters_list[k] = filters
|
|
|
|
return filters_list
|
|
|
|
def set_user_login(self, user_id=None, user=None, user_group=None, ip_address=None, host=None,
|
|
user_agent=None, success=0, expiry=None, jwt_token=None):
|
|
|
|
if user_id is None or str(user_id).isdigit():
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
if expiry is not None:
|
|
expiry = helpers.datetime_to_iso(expiry)
|
|
|
|
keys = {'timestamp': helpers.timestamp(),
|
|
'user_id': user_id}
|
|
|
|
values = {'user': user,
|
|
'user_group': user_group,
|
|
'ip_address': ip_address,
|
|
'host': host,
|
|
'user_agent': user_agent,
|
|
'success': success,
|
|
'expiry': expiry,
|
|
'jwt_token': jwt_token}
|
|
|
|
try:
|
|
monitor_db.upsert(table_name='user_login', key_dict=keys, value_dict=values)
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for set_login_log: %s." % e)
|
|
|
|
def get_user_login(self, jwt_token):
|
|
monitor_db = database.MonitorDatabase()
|
|
result = monitor_db.select_single("SELECT * FROM user_login "
|
|
"WHERE jwt_token = ?",
|
|
[jwt_token])
|
|
return result
|
|
|
|
def clear_user_login_token(self, jwt_token=None, row_ids=None):
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
if jwt_token:
|
|
logger.debug("Tautulli Users :: Clearing user JWT token.")
|
|
try:
|
|
monitor_db.action("UPDATE user_login SET jwt_token = NULL "
|
|
"WHERE jwt_token = ?",
|
|
[jwt_token])
|
|
except Exception as e:
|
|
logger.error("Tautulli Users :: Unable to clear user JWT token: %s.", e)
|
|
return False
|
|
|
|
elif row_ids and row_ids is not None:
|
|
row_ids = list(map(helpers.cast_to_int, row_ids.split(',')))
|
|
logger.debug("Tautulli Users :: Clearing JWT tokens for row_ids %s.", row_ids)
|
|
try:
|
|
monitor_db.action("UPDATE user_login SET jwt_token = NULL "
|
|
"WHERE id in ({})".format(",".join(["?"] * len(row_ids))),
|
|
row_ids)
|
|
except Exception as e:
|
|
logger.error("Tautulli Users :: Unable to clear JWT tokens: %s.", e)
|
|
return False
|
|
|
|
return True
|
|
|
|
def get_datatables_user_login(self, user_id=None, jwt_token=None, kwargs=None):
|
|
default_return = {'recordsFiltered': 0,
|
|
'recordsTotal': 0,
|
|
'draw': 0,
|
|
'data': []}
|
|
|
|
if not session.allow_session_user(user_id):
|
|
return default_return
|
|
|
|
data_tables = datatables.DataTables()
|
|
|
|
if session.get_session_user_id():
|
|
custom_where = [['user_login.user_id', session.get_session_user_id()]]
|
|
else:
|
|
custom_where = [['user_login.user_id', user_id]] if user_id else []
|
|
|
|
columns = ["user_login.id AS row_id",
|
|
"user_login.timestamp",
|
|
"user_login.user_id",
|
|
"user_login.user",
|
|
"user_login.user_group",
|
|
"user_login.ip_address",
|
|
"user_login.host",
|
|
"user_login.user_agent",
|
|
"user_login.success",
|
|
"user_login.expiry",
|
|
"user_login.jwt_token",
|
|
"(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = '' \
|
|
THEN users.username ELSE users.friendly_name END) AS friendly_name"
|
|
]
|
|
|
|
try:
|
|
query = data_tables.ssp_query(table_name='user_login',
|
|
columns=columns,
|
|
custom_where=custom_where,
|
|
group_by=[],
|
|
join_types=['LEFT OUTER JOIN'],
|
|
join_tables=['users'],
|
|
join_evals=[['user_login.user_id', 'users.user_id']],
|
|
kwargs=kwargs)
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for get_datatables_user_login: %s." % e)
|
|
return default_return
|
|
|
|
results = query['result']
|
|
|
|
rows = []
|
|
for item in results:
|
|
(os, browser) = httpagentparser.simple_detect(item['user_agent'])
|
|
|
|
expiry = None
|
|
current = False
|
|
if item['jwt_token'] and item['expiry']:
|
|
_expiry = helpers.iso_to_datetime(item['expiry'])
|
|
if _expiry > arrow.now():
|
|
expiry = _expiry.strftime('%Y-%m-%d %H:%M:%S')
|
|
current = (item['jwt_token'] == jwt_token)
|
|
|
|
row = {'row_id': item['row_id'],
|
|
'timestamp': item['timestamp'],
|
|
'user_id': item['user_id'],
|
|
'user_group': item['user_group'],
|
|
'ip_address': item['ip_address'],
|
|
'host': item['host'],
|
|
'user_agent': item['user_agent'],
|
|
'os': os,
|
|
'browser': browser,
|
|
'success': item['success'],
|
|
'expiry': expiry,
|
|
'current': current,
|
|
'friendly_name': item['friendly_name'] or item['user']
|
|
}
|
|
|
|
rows.append(row)
|
|
|
|
dict = {'recordsFiltered': query['filteredCount'],
|
|
'recordsTotal': query['totalCount'],
|
|
'data': session.friendly_name_to_username(rows),
|
|
'draw': query['draw']
|
|
}
|
|
|
|
return dict
|
|
|
|
def delete_login_log(self):
|
|
monitor_db = database.MonitorDatabase()
|
|
|
|
try:
|
|
logger.info("Tautulli Users :: Clearing login logs from database.")
|
|
monitor_db.action('DELETE FROM user_login')
|
|
monitor_db.action('VACUUM')
|
|
return True
|
|
except Exception as e:
|
|
logger.warn("Tautulli Users :: Unable to execute database query for delete_login_log: %s." % e)
|
|
return False |