diff --git a/Tautulli.py b/Tautulli.py
index 3a1c0a8f..b1fc79af 100755
--- a/Tautulli.py
+++ b/Tautulli.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
-from __future__ import unicode_literals
+from builtins import str
import os
import sys
diff --git a/plexpy/__init__.py b/plexpy/__init__.py
index 47bb3137..abfe1848 100644
--- a/plexpy/__init__.py
+++ b/plexpy/__init__.py
@@ -13,13 +13,18 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import range
+
+import datetime
import os
-from Queue import Queue
+import queue
import sqlite3
import sys
import subprocess
import threading
-import datetime
import uuid
# Some cut down versions of Python may not include this module and it's not critical for us
@@ -34,24 +39,25 @@ from apscheduler.triggers.interval import IntervalTrigger
from UniversalAnalytics import Tracker
import pytz
-import activity_handler
-import activity_pinger
-import common
-import database
-import datafactory
-import libraries
-import logger
-import mobile_app
-import newsletters
-import newsletter_handler
-import notification_handler
-import notifiers
-import plextv
-import users
-import versioncheck
-import web_socket
-import webstart
-import plexpy.config
+from plexpy import activity_handler
+from plexpy import activity_pinger
+from plexpy import common
+from plexpy import database
+from plexpy import datafactory
+from plexpy import libraries
+from plexpy import logger
+from plexpy import mobile_app
+from plexpy import newsletters
+from plexpy import newsletter_handler
+from plexpy import notification_handler
+from plexpy import notifiers
+from plexpy import plextv
+from plexpy import users
+from plexpy import versioncheck
+from plexpy import web_socket
+from plexpy import webstart
+from plexpy import config
+
PROG_DIR = None
FULL_PATH = None
@@ -74,7 +80,7 @@ DOCKER = False
SCHED = None
SCHED_LOCK = threading.Lock()
-NOTIFY_QUEUE = Queue()
+NOTIFY_QUEUE = queue.Queue()
INIT_LOCK = threading.Lock()
_INITIALIZED = False
@@ -128,7 +134,7 @@ def initialize(config_file):
global UMASK
global _UPDATE
- CONFIG = plexpy.config.Config(config_file)
+ CONFIG = config.Config(config_file)
CONFIG_FILE = config_file
assert CONFIG is not None
@@ -137,7 +143,7 @@ def initialize(config_file):
return False
if CONFIG.HTTP_PORT < 21 or CONFIG.HTTP_PORT > 65535:
- plexpy.logger.warn("HTTP_PORT out of bounds: 21 < %s < 65535", CONFIG.HTTP_PORT)
+ logger.warn("HTTP_PORT out of bounds: 21 < %s < 65535", CONFIG.HTTP_PORT)
CONFIG.HTTP_PORT = 8181
if not CONFIG.HTTPS_CERT:
@@ -162,7 +168,7 @@ def initialize(config_file):
' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else ''
))
logger.info("{} (UTC{})".format(
- plexpy.SYS_TIMEZONE.zone, plexpy.SYS_UTC_OFFSET
+ SYS_TIMEZONE.zone, SYS_UTC_OFFSET
))
logger.info("Python {}".format(
sys.version
@@ -379,29 +385,29 @@ def win_system_tray():
from systray import SysTrayIcon
def tray_open(sysTrayIcon):
- launch_browser(plexpy.CONFIG.HTTP_HOST, plexpy.HTTP_PORT, plexpy.HTTP_ROOT)
+ launch_browser(CONFIG.HTTP_HOST, HTTP_PORT, HTTP_ROOT)
def tray_check_update(sysTrayIcon):
versioncheck.check_update()
def tray_update(sysTrayIcon):
- if plexpy.UPDATE_AVAILABLE:
- plexpy.SIGNAL = 'update'
+ if UPDATE_AVAILABLE:
+ SIGNAL = 'update'
else:
hover_text = common.PRODUCT + ' - No Update Available'
- plexpy.WIN_SYS_TRAY_ICON.update(hover_text=hover_text)
+ WIN_SYS_TRAY_ICON.update(hover_text=hover_text)
def tray_restart(sysTrayIcon):
- plexpy.SIGNAL = 'restart'
+ SIGNAL = 'restart'
def tray_quit(sysTrayIcon):
- plexpy.SIGNAL = 'shutdown'
+ SIGNAL = 'shutdown'
- if plexpy.UPDATE_AVAILABLE:
- icon = os.path.join(plexpy.PROG_DIR, 'data/interfaces/', plexpy.CONFIG.INTERFACE, 'images/logo_tray-update.ico')
+ if UPDATE_AVAILABLE:
+ icon = os.path.join(PROG_DIR, 'data/interfaces/', CONFIG.INTERFACE, 'images/logo_tray-update.ico')
hover_text = common.PRODUCT + ' - Update Available!'
else:
- icon = os.path.join(plexpy.PROG_DIR, 'data/interfaces/', plexpy.CONFIG.INTERFACE, 'images/logo_tray.ico')
+ icon = os.path.join(PROG_DIR, 'data/interfaces/', CONFIG.INTERFACE, 'images/logo_tray.ico')
hover_text = common.PRODUCT
menu_options = (('Open Tautulli', None, tray_open, 'default'),
@@ -413,11 +419,11 @@ def win_system_tray():
logger.info("Launching system tray icon.")
try:
- plexpy.WIN_SYS_TRAY_ICON = SysTrayIcon(icon, hover_text, menu_options, on_quit=tray_quit)
- plexpy.WIN_SYS_TRAY_ICON.start()
+ WIN_SYS_TRAY_ICON = SysTrayIcon(icon, hover_text, menu_options, on_quit=tray_quit)
+ WIN_SYS_TRAY_ICON.start()
except Exception as e:
logger.error("Unable to launch system tray icon: %s." % e)
- plexpy.WIN_SYS_TRAY_ICON = None
+ WIN_SYS_TRAY_ICON = None
def initialize_scheduler():
@@ -2055,12 +2061,12 @@ def initialize_tracker():
'dataSource': 'server',
'appName': common.PRODUCT,
'appVersion': common.RELEASE,
- 'appId': plexpy.INSTALL_TYPE,
- 'appInstallerId': plexpy.CONFIG.GIT_BRANCH,
+ 'appId': INSTALL_TYPE,
+ 'appInstallerId': CONFIG.GIT_BRANCH,
'dimension1': '{} {}'.format(common.PLATFORM, common.PLATFORM_RELEASE), # App Platform
'dimension2': common.PLATFORM_LINUX_DISTRO, # Linux Distro
- 'userLanguage': plexpy.SYS_LANGUAGE,
- 'documentEncoding': plexpy.SYS_ENCODING,
+ 'userLanguage': SYS_LANGUAGE,
+ 'documentEncoding': SYS_ENCODING,
'noninteractive': True
}
diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py
index 09811a7a..5cd1c928 100644
--- a/plexpy/activity_handler.py
+++ b/plexpy/activity_handler.py
@@ -13,6 +13,10 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+from builtins import object
+
import datetime
import os
import time
@@ -21,12 +25,12 @@ from apscheduler.triggers.date import DateTrigger
import pytz
import plexpy
-import activity_processor
-import datafactory
-import helpers
-import logger
-import notification_handler
-import pmsconnect
+from plexpy import activity_processor
+from plexpy import datafactory
+from plexpy import helpers
+from plexpy import logger
+from plexpy import notification_handler
+from plexpy import pmsconnect
ACTIVITY_SCHED = None
diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py
index 21616f76..104da978 100644
--- a/plexpy/activity_pinger.py
+++ b/plexpy/activity_pinger.py
@@ -13,20 +13,23 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+
import threading
import time
import plexpy
-import activity_handler
-import activity_processor
-import database
-import helpers
-import libraries
-import logger
-import notification_handler
-import plextv
-import pmsconnect
-import web_socket
+from plexpy import activity_handler
+from plexpy import activity_processor
+from plexpy import database
+from plexpy import helpers
+from plexpy import libraries
+from plexpy import logger
+from plexpy import notification_handler
+from plexpy import plextv
+from plexpy import pmsconnect
+from plexpy import web_socket
monitor_lock = threading.Lock()
@@ -223,7 +226,7 @@ def check_recently_added():
continue
metadata = []
-
+
if 0 < time_threshold - int(item['added_at']) <= time_interval:
if item['media_type'] == 'movie':
metadata = pms_connect.get_metadata_details(item['rating_key'])
@@ -250,7 +253,7 @@ def check_recently_added():
logger.debug("Tautulli Monitor :: Library item %s added to Plex." % str(item['rating_key']))
plexpy.NOTIFY_QUEUE.put({'timeline_data': item.copy(), 'notify_action': 'on_created'})
-
+
else:
item = max(metadata, key=lambda x:x['added_at'])
diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py
index 935894e8..9858be06 100644
--- a/plexpy/activity_processor.py
+++ b/plexpy/activity_processor.py
@@ -13,17 +13,21 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+from builtins import object
+
from collections import defaultdict
import json
import time
import plexpy
-import database
-import helpers
-import libraries
-import logger
-import pmsconnect
-import users
+from plexpy import database
+from plexpy import helpers
+from plexpy import libraries
+from plexpy import logger
+from plexpy import pmsconnect
+from plexpy import users
class ActivityProcessor(object):
@@ -498,7 +502,7 @@ class ActivityProcessor(object):
if state:
values['state'] = state
- for k, v in kwargs.iteritems():
+ for k, v in kwargs.items():
values[k] = v
keys = {'session_key': session_key}
diff --git a/plexpy/api2.py b/plexpy/api2.py
index 27843a85..e4c48da3 100644
--- a/plexpy/api2.py
+++ b/plexpy/api2.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of Tautulli.
@@ -17,6 +16,10 @@
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+from builtins import object
+
import hashlib
import inspect
import json
@@ -30,22 +33,22 @@ import cherrypy
import xmltodict
import plexpy
-import config
-import database
-import helpers
-import libraries
-import logger
-import mobile_app
-import notification_handler
-import notifiers
-import newsletter_handler
-import newsletters
-import users
+from plexpy import config
+from plexpy import database
+from plexpy import helpers
+from plexpy import libraries
+from plexpy import logger
+from plexpy import mobile_app
+from plexpy import notification_handler
+from plexpy import notifiers
+from plexpy import newsletter_handler
+from plexpy import newsletters
+from plexpy import users
-class API2:
+class API2(object):
def __init__(self, **kwargs):
- self._api_valid_methods = self._api_docs().keys()
+ self._api_valid_methods = list(self._api_docs().keys())
self._api_authenticated = False
self._api_out_type = 'json' # default
self._api_msg = None
@@ -201,7 +204,7 @@ class API2:
except IndexError:
# We assume this is a traceback
tl = (len(templog) - 1)
- templog[tl]['msg'] += helpers.sanitize(unicode(line.replace('\n', ''), 'utf-8'))
+ templog[tl]['msg'] += helpers.sanitize(str(line.replace('\n', ''), 'utf-8'))
continue
if len(line) > 1 and temp_loglevel_and_time is not None and loglvl in line:
@@ -209,7 +212,7 @@ class API2:
d = {
'time': temp_loglevel_and_time[0],
'loglevel': loglvl,
- 'msg': helpers.sanitize(unicode(msg.replace('\n', ''), 'utf-8')),
+ 'msg': helpers.sanitize(str(msg.replace('\n', ''), 'utf-8')),
'thread': thread
}
templog.append(d)
@@ -227,7 +230,7 @@ class API2:
if search:
logger.api_debug("Tautulli APIv2 :: Searching log values for '%s'" % search)
- tt = [d for d in templog for k, v in d.items() if search.lower() in v.lower()]
+ tt = [d for d in templog for k, v in list(d.items()) if search.lower() in v.lower()]
if len(tt):
templog = tt
@@ -235,7 +238,7 @@ class API2:
if regex:
tt = []
for l in templog:
- stringdict = ' '.join('{}{}'.format(k, v) for k, v in l.items())
+ stringdict = ' '.join('{}{}'.format(k, v) for k, v in list(l.items()))
if reg.search(stringdict):
tt.append(l)
@@ -271,10 +274,10 @@ class API2:
config = {}
# Truthify the dict
- for k, v in conf.iteritems():
+ for k, v in conf.items():
if isinstance(v, dict):
d = {}
- for kk, vv in v.iteritems():
+ for kk, vv in v.items():
if vv == '0' or vv == '1':
d[kk] = bool(vv)
else:
diff --git a/plexpy/classes.py b/plexpy/classes.py
index 1d288355..33cfb708 100644
--- a/plexpy/classes.py
+++ b/plexpy/classes.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
@@ -18,12 +20,16 @@
#########################################
-import urllib
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
-from common import USER_AGENT
+import urllib.request, urllib.parse, urllib.error
+
+from plexpy.common import USER_AGENT
-class PlexPyURLopener(urllib.FancyURLopener):
+class PlexPyURLopener(urllib.request.FancyURLopener):
version = USER_AGENT
@@ -44,7 +50,7 @@ class AuthURLOpener(PlexPyURLopener):
self.numTries = 0
# call the base class
- urllib.FancyURLopener.__init__(self)
+ urllib.request.FancyURLopener.__init__(self)
def prompt_user_passwd(self, host, realm):
"""
diff --git a/plexpy/common.py b/plexpy/common.py
index fcd80536..b277d3dd 100644
--- a/plexpy/common.py
+++ b/plexpy/common.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
@@ -13,17 +15,21 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+
+import distro
import platform
from collections import OrderedDict
-import version
+from plexpy import version
+
# Identify Our Application
PRODUCT = 'Tautulli'
PLATFORM = platform.system()
PLATFORM_RELEASE = platform.release()
PLATFORM_VERSION = platform.version()
-PLATFORM_LINUX_DISTRO = ' '.join(x for x in platform.linux_distribution() if x)
+PLATFORM_LINUX_DISTRO = ' '.join(x for x in distro.linux_distribution() if x)
PLATFORM_DEVICE_NAME = platform.node()
BRANCH = version.PLEXPY_BRANCH
RELEASE = version.PLEXPY_RELEASE_VERSION
@@ -98,7 +104,7 @@ PLATFORM_NAMES = {
'xbmc': 'xbmc',
'xbox': 'xbox'
}
-PLATFORM_NAMES = OrderedDict(sorted(PLATFORM_NAMES.items(), key=lambda k: k[0], reverse=True))
+PLATFORM_NAMES = OrderedDict(sorted(list(PLATFORM_NAMES.items()), key=lambda k: k[0], reverse=True))
MEDIA_FLAGS_AUDIO = {
'ac.?3': 'dolby_digital',
@@ -147,7 +153,7 @@ VIDEO_QUALITY_PROFILES = {
96: '0.096 Mbps',
64: '0.064 Mbps'
}
-VIDEO_QUALITY_PROFILES = OrderedDict(sorted(VIDEO_QUALITY_PROFILES.items(), key=lambda k: k[0], reverse=True))
+VIDEO_QUALITY_PROFILES = OrderedDict(sorted(list(VIDEO_QUALITY_PROFILES.items()), key=lambda k: k[0], reverse=True))
AUDIO_QUALITY_PROFILES = {
512: '512 kbps',
@@ -157,7 +163,7 @@ AUDIO_QUALITY_PROFILES = {
128: '128 kbps',
96: '96 kbps'
}
-AUDIO_QUALITY_PROFILES = OrderedDict(sorted(AUDIO_QUALITY_PROFILES.items(), key=lambda k: k[0], reverse=True))
+AUDIO_QUALITY_PROFILES = OrderedDict(sorted(list(AUDIO_QUALITY_PROFILES.items()), key=lambda k: k[0], reverse=True))
HW_DECODERS = [
'dxva2',
diff --git a/plexpy/config.py b/plexpy/config.py
index 11f57bae..18abece8 100644
--- a/plexpy/config.py
+++ b/plexpy/config.py
@@ -13,6 +13,10 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from past.builtins import basestring
+from builtins import object
+
import arrow
import os
import re
@@ -22,7 +26,7 @@ import time
from configobj import ConfigObj
import plexpy
-import logger
+from plexpy import logger
def bool_int(value):
@@ -49,7 +53,7 @@ _CONFIG_DEFINITIONS = {
'PMS_IS_REMOTE': (int, 'PMS', 0),
'PMS_LOGS_FOLDER': (str, 'PMS', ''),
'PMS_LOGS_LINE_CAP': (int, 'PMS', 1000),
- 'PMS_NAME': (unicode, 'PMS', ''),
+ 'PMS_NAME': (str, 'PMS', ''),
'PMS_PORT': (int, 'PMS', 32400),
'PMS_TOKEN': (str, 'PMS', ''),
'PMS_SSL': (int, 'PMS', 0),
@@ -345,35 +349,35 @@ _CONFIG_DEFINITIONS = {
'NOTIFY_CONCURRENT_BY_IP': (int, 'Monitoring', 0),
'NOTIFY_CONCURRENT_THRESHOLD': (int, 'Monitoring', 2),
'NOTIFY_WATCHED_PERCENT': (int, 'Monitoring', 85),
- 'NOTIFY_ON_START_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_START_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) started playing {title}.'),
- 'NOTIFY_ON_STOP_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_STOP_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has stopped {title}.'),
- 'NOTIFY_ON_PAUSE_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_PAUSE_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has paused {title}.'),
- 'NOTIFY_ON_RESUME_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_RESUME_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has resumed {title}.'),
- 'NOTIFY_ON_BUFFER_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_BUFFER_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) is buffering {title}.'),
- 'NOTIFY_ON_WATCHED_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_WATCHED_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has watched {title}.'),
- 'NOTIFY_ON_CREATED_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_CREATED_BODY_TEXT': (unicode, 'Monitoring', '{title} was recently added to Plex.'),
- 'NOTIFY_ON_EXTDOWN_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_EXTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is down.'),
- 'NOTIFY_ON_INTDOWN_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_INTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is down.'),
- 'NOTIFY_ON_EXTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_EXTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is back up.'),
- 'NOTIFY_ON_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_INTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is back up.'),
- 'NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_PMSUPDATE_BODY_TEXT': (unicode, 'Monitoring', 'An update is available for the Plex Media Server (version {update_version}).'),
- 'NOTIFY_ON_CONCURRENT_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_CONCURRENT_BODY_TEXT': (unicode, 'Monitoring', '{user} has {user_streams} concurrent streams.'),
- 'NOTIFY_ON_NEWDEVICE_SUBJECT_TEXT': (unicode, 'Monitoring', 'Tautulli ({server_name})'),
- 'NOTIFY_ON_NEWDEVICE_BODY_TEXT': (unicode, 'Monitoring', '{user} is streaming from a new device: {player}.'),
- 'NOTIFY_SCRIPTS_ARGS_TEXT': (unicode, 'Monitoring', ''),
+ 'NOTIFY_ON_START_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_START_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) started playing {title}.'),
+ 'NOTIFY_ON_STOP_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_STOP_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has stopped {title}.'),
+ 'NOTIFY_ON_PAUSE_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_PAUSE_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has paused {title}.'),
+ 'NOTIFY_ON_RESUME_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_RESUME_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has resumed {title}.'),
+ 'NOTIFY_ON_BUFFER_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_BUFFER_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) is buffering {title}.'),
+ 'NOTIFY_ON_WATCHED_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_WATCHED_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has watched {title}.'),
+ 'NOTIFY_ON_CREATED_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_CREATED_BODY_TEXT': (str, 'Monitoring', '{title} was recently added to Plex.'),
+ 'NOTIFY_ON_EXTDOWN_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_EXTDOWN_BODY_TEXT': (str, 'Monitoring', 'The Plex Media Server remote access is down.'),
+ 'NOTIFY_ON_INTDOWN_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_INTDOWN_BODY_TEXT': (str, 'Monitoring', 'The Plex Media Server is down.'),
+ 'NOTIFY_ON_EXTUP_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_EXTUP_BODY_TEXT': (str, 'Monitoring', 'The Plex Media Server remote access is back up.'),
+ 'NOTIFY_ON_INTUP_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_INTUP_BODY_TEXT': (str, 'Monitoring', 'The Plex Media Server is back up.'),
+ 'NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_PMSUPDATE_BODY_TEXT': (str, 'Monitoring', 'An update is available for the Plex Media Server (version {update_version}).'),
+ 'NOTIFY_ON_CONCURRENT_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_CONCURRENT_BODY_TEXT': (str, 'Monitoring', '{user} has {user_streams} concurrent streams.'),
+ 'NOTIFY_ON_NEWDEVICE_SUBJECT_TEXT': (str, 'Monitoring', 'Tautulli ({server_name})'),
+ 'NOTIFY_ON_NEWDEVICE_BODY_TEXT': (str, 'Monitoring', '{user} is streaming from a new device: {player}.'),
+ 'NOTIFY_SCRIPTS_ARGS_TEXT': (str, 'Monitoring', ''),
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/Tautulli'),
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_PLAY': (int, 'OSX_Notify', 0),
@@ -512,7 +516,7 @@ _CONFIG_DEFINITIONS = {
'SLACK_ON_CONCURRENT': (int, 'Slack', 0),
'SLACK_ON_NEWDEVICE': (int, 'Slack', 0),
'SCRIPTS_ENABLED': (int, 'Scripts', 0),
- 'SCRIPTS_FOLDER': (unicode, 'Scripts', ''),
+ 'SCRIPTS_FOLDER': (str, 'Scripts', ''),
'SCRIPTS_TIMEOUT': (int, 'Scripts', 30),
'SCRIPTS_ON_PLAY': (int, 'Scripts', 0),
'SCRIPTS_ON_STOP': (int, 'Scripts', 0),
@@ -528,20 +532,20 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_PMSUPDATE': (int, 'Scripts', 0),
'SCRIPTS_ON_CONCURRENT': (int, 'Scripts', 0),
'SCRIPTS_ON_NEWDEVICE': (int, 'Scripts', 0),
- 'SCRIPTS_ON_PLAY_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_STOP_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_PAUSE_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_RESUME_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_BUFFER_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_WATCHED_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_CREATED_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_EXTDOWN_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_EXTUP_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_INTDOWN_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_INTUP_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_CONCURRENT_SCRIPT': (unicode, 'Scripts', ''),
- 'SCRIPTS_ON_NEWDEVICE_SCRIPT': (unicode, 'Scripts', ''),
+ 'SCRIPTS_ON_PLAY_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_STOP_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_PAUSE_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_RESUME_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_BUFFER_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_WATCHED_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_CREATED_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_EXTDOWN_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_EXTUP_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_INTDOWN_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_INTUP_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_PMSUPDATE_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_CONCURRENT_SCRIPT': (str, 'Scripts', ''),
+ 'SCRIPTS_ON_NEWDEVICE_SCRIPT': (str, 'Scripts', ''),
'SYNCHRONOUS_MODE': (str, 'Advanced', 'NORMAL'),
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
@@ -680,7 +684,7 @@ class Config(object):
""" Initialize the config with values from a file """
self._config_file = config_file
self._config = ConfigObj(self._config_file, encoding='utf-8')
- for key in _CONFIG_DEFINITIONS.keys():
+ for key in list(_CONFIG_DEFINITIONS.keys()):
self.check_setting(key)
self._upgrade()
self._blacklist()
@@ -689,8 +693,8 @@ class Config(object):
""" Add tokens and passwords to blacklisted words in logger """
blacklist = set()
- for key, subkeys in self._config.iteritems():
- for subkey, value in subkeys.iteritems():
+ for key, subkeys in self._config.items():
+ for subkey, value in subkeys.items():
if isinstance(value, basestring) and len(value.strip()) > 5 and \
subkey.upper() not in _WHITELIST_KEYS and any(bk in subkey.upper() for bk in _BLACKLIST_KEYS):
blacklist.add(value.strip())
@@ -733,14 +737,14 @@ class Config(object):
# first copy over everything from the old config, even if it is not
# correctly defined to keep from losing data
- for key, subkeys in self._config.items():
+ for key, subkeys in list(self._config.items()):
if key not in new_config:
new_config[key] = {}
- for subkey, value in subkeys.items():
+ for subkey, value in list(subkeys.items()):
new_config[key][subkey] = value
# next make sure that everything we expect to have defined is so
- for key in _CONFIG_DEFINITIONS.keys():
+ for key in list(_CONFIG_DEFINITIONS.keys()):
key, definition_type, section, ini_key, default = self._define(key)
self.check_setting(key)
if section not in new_config:
@@ -784,7 +788,7 @@ class Config(object):
"""
Given a big bunch of key value pairs, apply them to the ini.
"""
- for name, value in kwargs.items():
+ for name, value in list(kwargs.items()):
key, definition_type, section, ini_key, default = self._define(name)
self._config[section][ini_key] = definition_type(value)
diff --git a/plexpy/database.py b/plexpy/database.py
index 1e1f23d5..967adbbf 100644
--- a/plexpy/database.py
+++ b/plexpy/database.py
@@ -13,6 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import object
+
import arrow
import os
import sqlite3
@@ -21,7 +24,8 @@ import threading
import time
import plexpy
-import logger
+from plexpy import logger
+
FILENAME = "tautulli.db"
db_lock = threading.Lock()
@@ -198,21 +202,21 @@ class MonitorDatabase(object):
trans_type = 'update'
changes_before = self.connection.total_changes
- gen_params = lambda my_dict: [x + " = ?" for x in my_dict.keys()]
+ gen_params = lambda my_dict: [x + " = ?" for x in list(my_dict.keys())]
update_query = "UPDATE " + table_name + " SET " + ", ".join(gen_params(value_dict)) + \
" WHERE " + " AND ".join(gen_params(key_dict))
- self.action(update_query, value_dict.values() + key_dict.values())
+ self.action(update_query, list(value_dict.values()) + list(key_dict.values()))
if self.connection.total_changes == changes_before:
trans_type = 'insert'
insert_query = (
- "INSERT INTO " + table_name + " (" + ", ".join(value_dict.keys() + key_dict.keys()) + ")" +
- " VALUES (" + ", ".join(["?"] * len(value_dict.keys() + key_dict.keys())) + ")"
+ "INSERT INTO " + table_name + " (" + ", ".join(list(value_dict.keys()) + list(key_dict.keys())) + ")" +
+ " VALUES (" + ", ".join(["?"] * len(list(value_dict.keys()) + list(key_dict.keys()))) + ")"
)
try:
- self.action(insert_query, value_dict.values() + key_dict.values())
+ self.action(insert_query, list(value_dict.values()) + list(key_dict.values()))
except sqlite3.IntegrityError:
logger.info("Tautulli Database :: Queries failed: %s and %s", update_query, insert_query)
diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py
index f847410b..7b882d35 100644
--- a/plexpy/datafactory.py
+++ b/plexpy/datafactory.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,17 +15,24 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import division
+from __future__ import absolute_import
+from builtins import next
+from builtins import str
+from builtins import object
+from past.utils import old_div
+
import json
from itertools import groupby
import plexpy
-import common
-import database
-import datatables
-import helpers
-import logger
-import pmsconnect
-import session
+from plexpy import common
+from plexpy import database
+from plexpy import datatables
+from plexpy import helpers
+from plexpy import logger
+from plexpy import pmsconnect
+from plexpy import session
class DataFactory(object):
@@ -208,7 +217,7 @@ class DataFactory(object):
if item['percent_complete'] >= watched_percent[item['media_type']]:
watched_status = 1
- elif item['percent_complete'] >= watched_percent[item['media_type']]/2:
+ elif item['percent_complete'] >= old_div(watched_percent[item['media_type']],2):
watched_status = 0.5
else:
watched_status = 0
@@ -655,7 +664,7 @@ class DataFactory(object):
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.iteritems() if k in platform.lower()), 'default')
+ platform_name = next((v for k, v in common.PLATFORM_NAMES.items() if k in platform.lower()), 'default')
row = {'total_plays': item['total_plays'],
'total_duration': item['total_duration'],
@@ -750,7 +759,7 @@ class DataFactory(object):
for item in result:
times.append({'time': str(item['started']) + 'B', 'count': 1})
times.append({'time': str(item['stopped']) + 'A', 'count': -1})
- times = sorted(times, key=lambda k: k['time'])
+ times = sorted(times, key=lambda k: k['time'])
count = 0
last_count = 0
@@ -985,7 +994,7 @@ class DataFactory(object):
'pre_tautulli': pre_tautulli
}
- stream_output = {k: v or '' for k, v in stream_output.iteritems()}
+ stream_output = {k: v or '' for k, v in stream_output.items()}
return stream_output
def get_metadata_details(self, rating_key):
@@ -1079,7 +1088,7 @@ class DataFactory(object):
metadata_list.append(metadata)
filtered_metadata_list = session.filter_session_info(metadata_list, filter_key='section_id')
-
+
if filtered_metadata_list:
return filtered_metadata_list[0]
else:
@@ -1093,7 +1102,7 @@ class DataFactory(object):
where = 'WHERE ' + ' AND '.join([w[0] + ' = "' + w[1] + '"' for w in custom_where])
else:
where = ''
-
+
try:
query = 'SELECT SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - ' \
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \
@@ -1518,7 +1527,7 @@ class DataFactory(object):
# function to map rating keys pairs
def get_pairs(old, new):
pairs = {}
- for k, v in old.iteritems():
+ for k, v in old.items():
if k in new:
pairs.update({v['rating_key']: new[k]['rating_key']})
if 'children' in old[k]:
@@ -1533,27 +1542,27 @@ class DataFactory(object):
if mapping:
logger.info("Tautulli DataFactory :: Updating metadata in the database.")
- for old_key, new_key in mapping.iteritems():
+ for old_key, new_key in mapping.items():
metadata = pms_connect.get_metadata_details(new_key)
if metadata:
if metadata['media_type'] == 'show' or metadata['media_type'] == 'artist':
# check grandparent_rating_key (2 tables)
- monitor_db.action('UPDATE session_history SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?',
+ monitor_db.action('UPDATE session_history SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?',
[new_key, old_key])
- monitor_db.action('UPDATE session_history_metadata SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?',
+ monitor_db.action('UPDATE session_history_metadata SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?',
[new_key, old_key])
elif metadata['media_type'] == 'season' or metadata['media_type'] == 'album':
# check parent_rating_key (2 tables)
- monitor_db.action('UPDATE session_history SET parent_rating_key = ? WHERE parent_rating_key = ?',
+ monitor_db.action('UPDATE session_history SET parent_rating_key = ? WHERE parent_rating_key = ?',
[new_key, old_key])
- monitor_db.action('UPDATE session_history_metadata SET parent_rating_key = ? WHERE parent_rating_key = ?',
+ monitor_db.action('UPDATE session_history_metadata SET parent_rating_key = ? WHERE parent_rating_key = ?',
[new_key, old_key])
else:
# check rating_key (2 tables)
- monitor_db.action('UPDATE session_history SET rating_key = ? WHERE rating_key = ?',
+ monitor_db.action('UPDATE session_history SET rating_key = ? WHERE rating_key = ?',
[new_key, old_key])
- monitor_db.action('UPDATE session_history_media_info SET rating_key = ? WHERE rating_key = ?',
+ monitor_db.action('UPDATE session_history_media_info SET rating_key = ? WHERE rating_key = ?',
[new_key, old_key])
# update session_history_metadata table
@@ -1601,7 +1610,7 @@ class DataFactory(object):
metadata['media_index'], metadata['parent_media_index'], metadata['section_id'], metadata['thumb'],
metadata['parent_thumb'], metadata['grandparent_thumb'], metadata['art'], metadata['media_type'],
metadata['year'], metadata['originally_available_at'], metadata['added_at'], metadata['updated_at'],
- metadata['last_viewed_at'], metadata['content_rating'], metadata['summary'], metadata['tagline'],
+ metadata['last_viewed_at'], metadata['content_rating'], metadata['summary'], metadata['tagline'],
metadata['rating'], metadata['duration'], metadata['guid'], directors, writers, actors, genres,
metadata['studio'], labels,
old_rating_key]
diff --git a/plexpy/datatables.py b/plexpy/datatables.py
index db1f9309..99f989af 100644
--- a/plexpy/datatables.py
+++ b/plexpy/datatables.py
@@ -13,11 +13,14 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import object
+
import re
-import database
-import helpers
-import logger
+from plexpy import database
+from plexpy import helpers
+from plexpy import logger
class DataTables(object):
@@ -90,7 +93,7 @@ class DataTables(object):
filtered = self.ssp_db.select(query, args=args)
# Remove NULL rows
- filtered = [row for row in filtered if not all(v is None for v in row.values())]
+ filtered = [row for row in filtered if not all(v is None for v in list(row.values()))]
# Build grand totals
totalcount = self.ssp_db.select('SELECT COUNT(id) as total_count from %s' % table_name)[0]['total_count']
diff --git a/plexpy/graphs.py b/plexpy/graphs.py
index 8ac8a973..5fa91641 100644
--- a/plexpy/graphs.py
+++ b/plexpy/graphs.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,13 +15,18 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+from builtins import range
+from builtins import object
+
import datetime
import plexpy
-import common
-import database
-import logger
-import session
+from plexpy import common
+from plexpy import database
+from plexpy import logger
+from plexpy import session
class Graphs(object):
@@ -32,7 +39,7 @@ class Graphs(object):
if not time_range.isdigit():
time_range = '30'
-
+
user_cond = ''
if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()):
user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id()
@@ -44,7 +51,7 @@ class Graphs(object):
group_by = 'reference_id' if grouping else 'id'
- try:
+ try:
if y_axis == 'plays':
query = 'SELECT date(started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
diff --git a/plexpy/helpers.py b/plexpy/helpers.py
index 10cef9bd..fd2ea818 100644
--- a/plexpy/helpers.py
+++ b/plexpy/helpers.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,6 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import division
+from __future__ import absolute_import
+from past.builtins import cmp
+from future import standard_library
+standard_library.install_aliases()
+from builtins import zip
+from builtins import str
+from past.builtins import basestring
+from past.utils import old_div
+
import base64
import cloudinary
from cloudinary.api import delete_resources_by_tag
@@ -20,12 +32,15 @@ from cloudinary.uploader import upload
from cloudinary.utils import cloudinary_url
import datetime
from functools import wraps
-import geoip2.database, geoip2.errors
+import geoip2.database
+import geoip2.errors
import gzip
import hashlib
import imghdr
-from itertools import izip_longest
-import ipwhois, ipwhois.exceptions, ipwhois.utils
+from itertools import zip_longest
+import ipwhois
+import ipwhois.exceptions
+import ipwhois.utils
from IPy import IP
import json
import math
@@ -38,13 +53,18 @@ import socket
import sys
import time
import unicodedata
-import urllib, urllib2
+import urllib.request
+import urllib.parse
+import urllib.error
+import urllib.request
+import urllib.error
+import urllib.parse
from xml.dom import minidom
import xmltodict
import plexpy
-import logger
-import request
+from plexpy import logger
+from plexpy import request
from plexpy.api2 import API2
@@ -158,7 +178,7 @@ def latinToAscii(unicrap):
def convert_milliseconds(ms):
- seconds = ms / 1000
+ seconds = old_div(ms, 1000)
gmtime = time.gmtime(seconds)
if seconds > 3600:
minutes = time.strftime("%H:%M:%S", gmtime)
@@ -172,7 +192,7 @@ def convert_milliseconds_to_minutes(ms):
if str(ms).isdigit():
seconds = float(ms) / 1000
- minutes = round(seconds / 60, 0)
+ minutes = round(old_div(seconds, 60), 0)
return math.trunc(minutes)
@@ -224,9 +244,9 @@ def human_duration(s, sig='dhms'):
hd = ''
if str(s).isdigit() and s > 0:
- d = int(s / 86400)
- h = int((s % 86400) / 3600)
- m = int(((s % 86400) % 3600) / 60)
+ d = int(old_div(s, 86400))
+ h = int(old_div((s % 86400), 3600))
+ m = int(old_div(((s % 86400) % 3600), 60))
s = int(((s % 86400) % 3600) % 60)
hd_list = []
@@ -277,7 +297,7 @@ def get_age(date):
def bytes_to_mb(bytes):
- mb = int(bytes) / 1048576
+ mb = old_div(int(bytes), 1048576)
size = '%.1f MB' % mb
return size
@@ -318,7 +338,7 @@ def replace_all(text, dic, normalize=False):
if not text:
return ''
- for i, j in dic.iteritems():
+ for i, j in dic.items():
if normalize:
try:
if sys.platform == 'darwin':
@@ -476,7 +496,7 @@ def get_percent(value1, value2):
value2 = cast_to_float(value2)
if value1 != 0 and value2 != 0:
- percent = (value1 / value2) * 100
+ percent = (old_div(value1, value2)) * 100
else:
percent = 0
@@ -543,11 +563,11 @@ def sanitize_out(*dargs, **dkwargs):
def sanitize(obj):
if isinstance(obj, basestring):
- return unicode(obj).replace('<', '<').replace('>', '>')
+ return str(obj).replace('<', '<').replace('>', '>')
elif isinstance(obj, list):
return [sanitize(o) for o in obj]
elif isinstance(obj, dict):
- return {k: sanitize(v) for k, v in obj.iteritems()}
+ return {k: sanitize(v) for k, v in obj.items()}
elif isinstance(obj, tuple):
return tuple(sanitize(list(obj)))
else:
@@ -596,9 +616,9 @@ def install_geoip_db():
# Retrieve the GeoLite2 gzip file
logger.debug("Tautulli Helpers :: Downloading GeoLite2 gzip file from MaxMind...")
try:
- maxmind = urllib.URLopener()
+ maxmind = urllib.request.URLopener()
maxmind.retrieve(maxmind_url + geolite2_gz, temp_gz)
- md5_checksum = urllib2.urlopen(maxmind_url + geolite2_md5).read()
+ md5_checksum = urllib.request.urlopen(maxmind_url + geolite2_md5).read()
except Exception as e:
logger.error("Tautulli Helpers :: Failed to download GeoLite2 gzip file from MaxMind: %s" % e)
return False
@@ -1151,7 +1171,7 @@ def grouper(iterable, n, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
args = [iter(iterable)] * n
- return izip_longest(fillvalue=fillvalue, *args)
+ return zip_longest(fillvalue=fillvalue, *args)
def traverse_map(obj, func):
@@ -1162,7 +1182,7 @@ def traverse_map(obj, func):
elif isinstance(obj, dict):
new_obj = {}
- for k, v in obj.iteritems():
+ for k, v in obj.items():
new_obj[traverse_map(k, func)] = traverse_map(v, func)
else:
@@ -1187,7 +1207,7 @@ def mask_config_passwords(config):
cfg['value'] = ' '
elif isinstance(config, dict):
- for cfg, val in config.iteritems():
+ for cfg, val in config.items():
# Check for a password config keys and if the password is not blank
if 'password' in cfg and val != '':
# Set the password to blank so it is not exposed in the HTML form
diff --git a/plexpy/http_handler.py b/plexpy/http_handler.py
index a1c8ba04..6b792065 100644
--- a/plexpy/http_handler.py
+++ b/plexpy/http_handler.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of PlexPy.
@@ -16,16 +15,22 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see .
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from past.builtins import basestring
+from builtins import object
+
from functools import partial
from multiprocessing.dummy import Pool as ThreadPool
-from urlparse import urljoin
+from urllib.parse import urljoin
import certifi
import urllib3
import plexpy
-import helpers
-import logger
+from plexpy import helpers
+from plexpy import logger
class HTTPHandler(object):
@@ -78,7 +83,7 @@ class HTTPHandler(object):
Output: list
"""
- self.uri = uri.encode('utf-8')
+ self.uri = uri
self.request_type = request_type.upper()
self.output_format = output_format.lower()
self.return_type = return_type
diff --git a/plexpy/libraries.py b/plexpy/libraries.py
index fcb222bc..6420e98a 100644
--- a/plexpy/libraries.py
+++ b/plexpy/libraries.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,18 +15,23 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+from builtins import next
+from builtins import object
+
import json
import os
import plexpy
-import common
-import database
-import datatables
-import helpers
-import logger
-import plextv
-import pmsconnect
-import session
+from plexpy import common
+from plexpy import database
+from plexpy import datatables
+from plexpy import helpers
+from plexpy import logger
+from plexpy import plextv
+from plexpy import pmsconnect
+from plexpy import session
def refresh_libraries():
@@ -121,7 +128,7 @@ def update_section_ids():
for library in library_results:
section_id = library['section_id']
section_type = library['section_type']
-
+
if section_type != 'photo':
library_children = pms_connect.get_library_children_details(section_id=section_id,
section_type=section_type)
@@ -135,7 +142,7 @@ def update_section_ids():
for item in history_results:
rating_key = item['grandparent_rating_key'] if item['media_type'] != 'movie' else item['rating_key']
section_id = key_mappings.get(str(rating_key), None)
-
+
if section_id:
try:
section_keys = {'id': item['id']}
@@ -187,7 +194,7 @@ def update_labels():
for library in library_results:
section_id = library['section_id']
section_type = library['section_type']
-
+
if section_type != 'photo':
library_children = []
library_labels = pms_connect.get_library_label_details(section_id=section_id)
@@ -213,7 +220,7 @@ def update_labels():
% section_id)
error_keys = set()
- for rating_key, labels in key_mappings.iteritems():
+ for rating_key, labels in key_mappings.items():
try:
labels = ';'.join(labels)
monitor_db.action('UPDATE session_history_metadata SET labels = ? '
@@ -309,7 +316,7 @@ class Libraries(object):
return default_return
result = query['result']
-
+
rows = []
for item in result:
if item['media_type'] == 'episode' and item['parent_thumb']:
@@ -354,13 +361,13 @@ class Libraries(object):
}
rows.append(row)
-
+
dict = {'recordsFiltered': query['filteredCount'],
'recordsTotal': query['totalCount'],
'data': session.mask_session_info(rows),
'draw': query['draw']
}
-
+
return dict
def get_datatables_media_info(self, section_id=None, section_type=None, rating_key=None, refresh=False, kwargs=None):
@@ -372,7 +379,7 @@ class Libraries(object):
if not session.allow_session_library(section_id):
return default_return
-
+
if section_id and not str(section_id).isdigit():
logger.warn("Tautulli Libraries :: Datatable media info called but invalid section_id provided.")
return default_return
@@ -466,7 +473,7 @@ class Libraries(object):
else:
logger.warn("Tautulli Libraries :: Unable to get a list of library items.")
return default_return
-
+
new_rows = []
for item in children_list:
## TODO: Check list of media info items, currently only grabs first item
@@ -529,8 +536,8 @@ class Libraries(object):
item['play_count'] = None
results = []
-
- # Get datatables JSON data
+
+ # Get datatables JSON data
if kwargs.get('json_data'):
json_data = helpers.process_json_kwargs(json_kwargs=kwargs.get('json_data'))
#print json_data
@@ -540,7 +547,7 @@ class Libraries(object):
if search_value:
searchable_columns = [d['data'] for d in json_data['columns'] if d['searchable']] + ['title']
for row in rows:
- for k,v in row.iteritems():
+ for k,v in row.items():
if k in searchable_columns and search_value in v.lower():
results.append(row)
break
@@ -578,13 +585,13 @@ class Libraries(object):
'filtered_file_size': filtered_file_size,
'total_file_size': total_file_size
}
-
+
return dict
def get_media_info_file_sizes(self, section_id=None, rating_key=None):
if not session.allow_session_library(section_id):
return False
-
+
if section_id and not str(section_id).isdigit():
logger.warn("Tautulli Libraries :: Datatable media info file size called but invalid section_id provided.")
return False
@@ -629,7 +636,7 @@ class Libraries(object):
for item in rows:
if item['rating_key'] and not item['file_size']:
file_size = 0
-
+
metadata = pms_connect.get_metadata_children_details(rating_key=item['rating_key'],
get_children=True)
@@ -669,7 +676,7 @@ class Libraries(object):
logger.debug("Tautulli Libraries :: File sizes updated for section_id %s." % section_id)
return True
-
+
def set_config(self, section_id=None, custom_thumb='', do_notify=1, keep_history=1, do_notify_created=1):
if section_id:
monitor_db = database.MonitorDatabase()
@@ -759,7 +766,7 @@ class Libraries(object):
if library_details:
return library_details
-
+
else:
logger.warn("Tautulli Users :: Unable to retrieve library %s from database. Returning 'Local' library."
% section_id)
@@ -856,7 +863,7 @@ class Libraries(object):
except Exception as e:
logger.warn("Tautulli Libraries :: Unable to execute database query for get_user_stats: %s." % e)
result = []
-
+
for item in result:
row = {'friendly_name': item['friendly_name'],
'user_id': item['user_id'],
@@ -864,7 +871,7 @@ class Libraries(object):
'total_plays': item['user_count']
}
user_stats.append(row)
-
+
return session.mask_session_info(user_stats, mask_metadata=False)
def get_recently_watched(self, section_id=None, limit='10'):
diff --git a/plexpy/lock.py b/plexpy/lock.py
index cd10d33c..3dc73f07 100644
--- a/plexpy/lock.py
+++ b/plexpy/lock.py
@@ -1,11 +1,28 @@
-"""
-Locking-related classes
-"""
+# -*- coding: utf-8 -*-
-import plexpy.logger
+# 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 .
+
+from __future__ import absolute_import
+from builtins import object
+
+import queue
import time
import threading
-import Queue
+
+from plexpy import logger
class TimedLock(object):
@@ -28,7 +45,7 @@ class TimedLock(object):
self.lock = threading.Lock()
self.last_used = 0
self.minimum_delta = minimum_delta
- self.queue = Queue.Queue()
+ self.queue = queue.Queue()
def __enter__(self):
"""
@@ -39,14 +56,14 @@ class TimedLock(object):
sleep_amount = self.minimum_delta - delta
if sleep_amount >= 0:
# zero sleeps give the cpu a chance to task-switch
- plexpy.logger.debug('Sleeping %s (interval)', sleep_amount)
+ logger.debug('Sleeping %s (interval)', sleep_amount)
time.sleep(sleep_amount)
while not self.queue.empty():
try:
seconds = self.queue.get(False)
- plexpy.logger.debug('Sleeping %s (queued)', seconds)
+ logger.debug('Sleeping %s (queued)', seconds)
time.sleep(seconds)
- except Queue.Empty:
+ except queue.Empty:
continue
self.queue.task_done()
@@ -65,7 +82,7 @@ class TimedLock(object):
"""
# We use a queue so that we don't have to synchronize
# across threads and with or without locks
- plexpy.logger.info('Adding %s to queue', seconds)
+ logger.info('Adding %s to queue', seconds)
self.queue.put(seconds)
diff --git a/plexpy/log_reader.py b/plexpy/log_reader.py
index c93f4f5c..1c9fa87a 100644
--- a/plexpy/log_reader.py
+++ b/plexpy/log_reader.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
@@ -13,11 +15,15 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+
import os
import plexpy
-import helpers
-import logger
+from plexpy import helpers
+from plexpy import logger
+
def get_log_tail(window=20, parsed=True, log_type="server"):
@@ -45,7 +51,7 @@ def get_log_tail(window=20, parsed=True, log_type="server"):
try:
log_time = i.split(' [')[0]
log_level = i.split('] ', 1)[1].split(' - ', 1)[0]
- log_msg = unicode(i.split('] ', 1)[1].split(' - ', 1)[1], 'utf-8')
+ log_msg = str(i.split('] ', 1)[1].split(' - ', 1)[1], 'utf-8')
full_line = [log_time, log_level, log_msg]
clean_lines.append(full_line)
except:
diff --git a/plexpy/logger.py b/plexpy/logger.py
index 2345e139..cf637cbe 100644
--- a/plexpy/logger.py
+++ b/plexpy/logger.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
@@ -13,6 +15,10 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+from past.builtins import basestring
+
from logutils.queue import QueueHandler, QueueListener
from logging import handlers
@@ -27,9 +33,10 @@ import threading
import traceback
import plexpy
-import helpers
+from plexpy.helpers import is_public_ip
from plexpy.config import _BLACKLIST_KEYS, _WHITELIST_KEYS
+
# These settings are for file logging only
FILENAME = "tautulli.log"
FILENAME_API = "tautulli_api.log"
@@ -54,7 +61,7 @@ def blacklist_config(config):
blacklist = set()
blacklist_keys = ['HOOK', 'APIKEY', 'KEY', 'PASSWORD', 'TOKEN']
- for key, value in config.iteritems():
+ for key, value in config.items():
if isinstance(value, basestring) and len(value.strip()) > 5 and \
key.upper() not in _WHITELIST_KEYS and (key.upper() in blacklist_keys or
any(bk in key.upper() for bk in _BLACKLIST_KEYS)):
@@ -113,14 +120,14 @@ class PublicIPFilter(logging.Filter):
# Currently only checking for ipv4 addresses
ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', record.msg)
for ip in ipv4:
- if helpers.is_public_ip(ip):
+ if is_public_ip(ip):
record.msg = record.msg.replace(ip, ip.partition('.')[0] + '.***.***.***')
args = []
for arg in record.args:
ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', arg) if isinstance(arg, basestring) else []
for ip in ipv4:
- if helpers.is_public_ip(ip):
+ if is_public_ip(ip):
arg = arg.replace(ip, ip.partition('.')[0] + '.***.***.***')
args.append(arg)
record.args = tuple(args)
diff --git a/plexpy/mobile_app.py b/plexpy/mobile_app.py
index de89a23d..3f9bbb96 100644
--- a/plexpy/mobile_app.py
+++ b/plexpy/mobile_app.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,12 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+
import time
-import plexpy
-import database
-import helpers
-import logger
+from plexpy import database
+from plexpy import logger
TEMP_DEVICE_TOKEN = None
diff --git a/plexpy/newsletter_handler.py b/plexpy/newsletter_handler.py
index 8fcfa2a0..5e8ccfe2 100644
--- a/plexpy/newsletter_handler.py
+++ b/plexpy/newsletter_handler.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+
import os
import time
@@ -20,9 +24,9 @@ from apscheduler.triggers.cron import CronTrigger
import email.utils
import plexpy
-import database
-import logger
-import newsletters
+from plexpy import database
+from plexpy import logger
+from plexpy import newsletters
NEWSLETTER_SCHED = None
diff --git a/plexpy/newsletters.py b/plexpy/newsletters.py
index c38cda34..ba6e670e 100644
--- a/plexpy/newsletters.py
+++ b/plexpy/newsletters.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,6 +15,11 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import next
+from builtins import str
+from builtins import object
+
import arrow
from collections import OrderedDict
import json
@@ -23,14 +30,14 @@ import os
import re
import plexpy
-import common
-import database
-import helpers
-import libraries
-import logger
-import newsletter_handler
-import pmsconnect
-from notifiers import send_notification, EMAIL
+from plexpy import common
+from plexpy import database
+from plexpy import helpers
+from plexpy import libraries
+from plexpy import logger
+from plexpy import newsletter_handler
+from plexpy import pmsconnect
+from plexpy.notifiers import send_notification, EMAIL
AGENT_IDS = {
@@ -229,11 +236,11 @@ def set_newsletter_config(newsletter_id=None, agent_id=None, **kwargs):
email_config_prefix = 'newsletter_email_'
newsletter_config = {k[len(config_prefix):]: kwargs.pop(k)
- for k in kwargs.keys() if k.startswith(config_prefix)}
+ for k in list(kwargs.keys()) if k.startswith(config_prefix)}
email_config = {k[len(email_config_prefix):]: kwargs.pop(k)
- for k in kwargs.keys() if k.startswith(email_config_prefix)}
+ for k in list(kwargs.keys()) if k.startswith(email_config_prefix)}
- for cfg, val in email_config.iteritems():
+ for cfg, val in email_config.items():
# Check for a password config keys and a blank password from the HTML form
if 'password' in cfg and val == ' ':
# Get the previous password so we don't overwrite it with a blank value
@@ -418,7 +425,7 @@ class Newsletter(object):
return default
new_config = {}
- for k, v in default.iteritems():
+ for k, v in default.items():
if isinstance(v, int):
new_config[k] = helpers.cast_to_int(config.get(k, v))
elif isinstance(v, list):
@@ -602,50 +609,50 @@ class Newsletter(object):
return parameters
def build_text(self):
- from notification_handler import CustomFormatter
+ from plexpy.notification_handler import CustomFormatter
custom_formatter = CustomFormatter()
try:
- subject = custom_formatter.format(unicode(self.subject), **self.parameters)
+ subject = custom_formatter.format(str(self.subject), **self.parameters)
except LookupError as e:
logger.error("Tautulli Newsletter :: Unable to parse parameter %s in newsletter subject. Using fallback." % e)
- subject = unicode(self._DEFAULT_SUBJECT).format(**self.parameters)
+ subject = str(self._DEFAULT_SUBJECT).format(**self.parameters)
except Exception as e:
logger.error("Tautulli Newsletter :: Unable to parse custom newsletter subject: %s. Using fallback." % e)
- subject = unicode(self._DEFAULT_SUBJECT).format(**self.parameters)
+ subject = str(self._DEFAULT_SUBJECT).format(**self.parameters)
try:
- body = custom_formatter.format(unicode(self.body), **self.parameters)
+ body = custom_formatter.format(str(self.body), **self.parameters)
except LookupError as e:
logger.error("Tautulli Newsletter :: Unable to parse parameter %s in newsletter body. Using fallback." % e)
- body = unicode(self._DEFAULT_BODY).format(**self.parameters)
+ body = str(self._DEFAULT_BODY).format(**self.parameters)
except Exception as e:
logger.error("Tautulli Newsletter :: Unable to parse custom newsletter body: %s. Using fallback." % e)
- body = unicode(self._DEFAULT_BODY).format(**self.parameters)
+ body = str(self._DEFAULT_BODY).format(**self.parameters)
try:
- message = custom_formatter.format(unicode(self.message), **self.parameters)
+ message = custom_formatter.format(str(self.message), **self.parameters)
except LookupError as e:
logger.error("Tautulli Newsletter :: Unable to parse parameter %s in newsletter message. Using fallback." % e)
- message = unicode(self._DEFAULT_MESSAGE).format(**self.parameters)
+ message = str(self._DEFAULT_MESSAGE).format(**self.parameters)
except Exception as e:
logger.error("Tautulli Newsletter :: Unable to parse custom newsletter message: %s. Using fallback." % e)
- message = unicode(self._DEFAULT_MESSAGE).format(**self.parameters)
+ message = str(self._DEFAULT_MESSAGE).format(**self.parameters)
return subject, body, message
def build_filename(self):
- from notification_handler import CustomFormatter
+ from plexpy.notification_handler import CustomFormatter
custom_formatter = CustomFormatter()
try:
- filename = custom_formatter.format(unicode(self.filename), **self.parameters)
+ filename = custom_formatter.format(str(self.filename), **self.parameters)
except LookupError as e:
logger.error("Tautulli Newsletter :: Unable to parse parameter %s in newsletter filename. Using fallback." % e)
- filename = unicode(self._DEFAULT_FILENAME).format(**self.parameters)
+ filename = str(self._DEFAULT_FILENAME).format(**self.parameters)
except Exception as e:
logger.error("Tautulli Newsletter :: Unable to parse custom newsletter subject: %s. Using fallback." % e)
- filename = unicode(self._DEFAULT_FILENAME).format(**self.parameters)
+ filename = str(self._DEFAULT_FILENAME).format(**self.parameters)
return filename
@@ -682,7 +689,7 @@ class RecentlyAdded(Newsletter):
_TEMPLATE = 'recently_added.html'
def _get_recently_added(self, media_type=None):
- from notification_handler import format_group_index
+ from plexpy.notification_handler import format_group_index
pms_connect = pmsconnect.PmsConnect()
@@ -798,7 +805,7 @@ class RecentlyAdded(Newsletter):
return recently_added
def retrieve_data(self):
- from notification_handler import get_img_info, set_hash_image_info
+ from plexpy.notification_handler import get_img_info, set_hash_image_info
if not self.config['incl_libraries']:
logger.warn("Tautulli Newsletters :: Failed to retrieve %s newsletter data: no libraries selected." % self.NAME)
diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py
index 7ff43178..d29e8cf7 100644
--- a/plexpy/notification_handler.py
+++ b/plexpy/notification_handler.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -14,6 +16,17 @@
# along with Tautulli. If not, see .
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import next
+from builtins import map
+from builtins import str
+from builtins import range
+from past.builtins import basestring
+from past.utils import old_div
+
import arrow
import bleach
from collections import Counter, defaultdict
@@ -31,19 +44,16 @@ import time
import musicbrainzngs
import plexpy
-import activity_processor
-import common
-import database
-import datafactory
-import libraries
-import logger
-import helpers
-import notifiers
-import plextv
-import pmsconnect
-import request
-import users
-from newsletter_handler import notify as notify_newsletter
+from plexpy import activity_processor
+from plexpy import common
+from plexpy import database
+from plexpy import datafactory
+from plexpy import logger
+from plexpy import helpers
+from plexpy import notifiers
+from plexpy import pmsconnect
+from plexpy import request
+from plexpy.newsletter_handler import notify as notify_newsletter
def process_queue():
@@ -63,7 +73,7 @@ def process_queue():
add_notifier_each(**params)
except Exception as e:
logger.exception("Tautulli NotificationHandler :: Notification thread exception: %s" % e)
-
+
queue.task_done()
logger.info("Tautulli NotificationHandler :: Notification thread exiting...")
@@ -174,12 +184,12 @@ def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
elif stream_data['media_type'] in ('movie', 'episode', 'clip'):
progress_percent = helpers.get_percent(stream_data['view_offset'], stream_data['duration'])
-
+
if notify_action == 'on_stop':
return (plexpy.CONFIG.NOTIFY_CONSECUTIVE or
- (stream_data['media_type'] == 'movie' and progress_percent < plexpy.CONFIG.MOVIE_WATCHED_PERCENT) or
+ (stream_data['media_type'] == 'movie' and progress_percent < plexpy.CONFIG.MOVIE_WATCHED_PERCENT) or
(stream_data['media_type'] == 'episode' and progress_percent < plexpy.CONFIG.TV_WATCHED_PERCENT))
-
+
elif notify_action == 'on_resume':
return plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99
@@ -254,7 +264,7 @@ def notify_custom_conditions(notifier_id=None, parameters=None):
# Cast the condition values to the correct type
try:
if parameter_type == 'str':
- values = [unicode(v).lower() for v in values]
+ values = [str(v).lower() for v in values]
elif parameter_type == 'int':
values = [helpers.cast_to_int(v) for v in values]
@@ -270,7 +280,7 @@ def notify_custom_conditions(notifier_id=None, parameters=None):
# Cast the parameter value to the correct type
try:
if parameter_type == 'str':
- parameter_value = unicode(parameter_value).lower()
+ parameter_value = str(parameter_value).lower()
elif parameter_type == 'int':
parameter_value = helpers.cast_to_int(parameter_value)
@@ -540,9 +550,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
transcode_decision = 'Direct Play'
if notify_action != 'on_play':
- stream_duration = int((time.time() -
- helpers.cast_to_int(session.get('started', 0)) -
- helpers.cast_to_int(session.get('paused_counter', 0))) / 60)
+ stream_duration = int(old_div((time.time() -
+ helpers.cast_to_int(session.get('started', 0)) -
+ helpers.cast_to_int(session.get('paused_counter', 0))), 60))
else:
stream_duration = 0
@@ -1137,19 +1147,19 @@ def build_notify_text(subject='', body='', notify_action=None, parameters=None,
subject = str_formatter(subject)
except LookupError as e:
logger.error("Tautulli NotificationHandler :: Unable to parse parameter %s in notification subject. Using fallback." % e)
- subject = unicode(default_subject).format(**parameters)
+ subject = str(default_subject).format(**parameters)
except Exception as e:
logger.error("Tautulli NotificationHandler :: Unable to parse custom notification subject: %s. Using fallback." % e)
- subject = unicode(default_subject).format(**parameters)
+ subject = str(default_subject).format(**parameters)
try:
body = str_formatter(body)
except LookupError as e:
logger.error("Tautulli NotificationHandler :: Unable to parse parameter %s in notification body. Using fallback." % e)
- body = unicode(default_body).format(**parameters)
+ body = str(default_body).format(**parameters)
except Exception as e:
logger.error("Tautulli NotificationHandler :: Unable to parse custom notification body: %s. Using fallback." % e)
- body = unicode(default_body).format(**parameters)
+ body = str(default_body).format(**parameters)
return subject, body, script_args
@@ -1165,7 +1175,7 @@ def strip_tag(data, agent_id=None):
'u': [],
'a': ['href'],
'font': ['color']}
- data = bleach.clean(data, tags=whitelist.keys(), attributes=whitelist, strip=True)
+ data = bleach.clean(data, tags=list(whitelist.keys()), attributes=whitelist, strip=True)
elif agent_id in (10, 14, 20):
# Don't remove tags for Email, Slack, and Discord
@@ -1178,11 +1188,11 @@ def strip_tag(data, agent_id=None):
'code': [],
'pre': [],
'a': ['href']}
- data = bleach.clean(data, tags=whitelist.keys(), attributes=whitelist, strip=True)
+ data = bleach.clean(data, tags=list(whitelist.keys()), attributes=whitelist, strip=True)
else:
whitelist = {}
- data = bleach.clean(data, tags=whitelist.keys(), attributes=whitelist, strip=True)
+ data = bleach.clean(data, tags=list(whitelist.keys()), attributes=whitelist, strip=True)
# Resubstitute temporary tokens for < and > in parameter prefix and suffix
return data.replace('%temp_lt_token%', '<').replace('%temp_gt_token%', '>')
@@ -1194,8 +1204,8 @@ def format_group_index(group_keys):
num = []
num00 = []
- for k, g in groupby(enumerate(group_keys), lambda (i, x): i-x):
- group = map(itemgetter(1), g)
+ for k, g in groupby(enumerate(group_keys), lambda i_x: i_x[0]-i_x[1]):
+ group = list(map(itemgetter(1), g))
g_min, g_max = min(group), max(group)
if g_min == g_max:
@@ -1382,7 +1392,7 @@ def lookup_tvmaze_by_id(rating_key=None, thetvdb_id=None, imdb_id=None):
imdb_id = tvmaze_json.get('externals', {}).get('imdb', '')
tvmaze_id = tvmaze_json.get('id', '')
tvmaze_url = tvmaze_json.get('url', '')
-
+
keys = {'tvmaze_id': tvmaze_id}
tvmaze_info = {'rating_key': rating_key,
'thetvdb_id': thetvdb_id,
@@ -1586,7 +1596,7 @@ def lookup_musicbrainz_info(musicbrainz_type=None, rating_key=None, artist=None,
def str_format(s, parameters):
custom_formatter = CustomFormatter()
if isinstance(s, basestring):
- return custom_formatter.format(unicode(s), **parameters)
+ return custom_formatter.format(str(s), **parameters)
return s
@@ -1602,11 +1612,11 @@ class CustomFormatter(Formatter):
elif conversion == 'r':
return repr(value)
elif conversion == 'u': # uppercase
- return unicode(value).upper()
+ return str(value).upper()
elif conversion == 'l': # lowercase
- return unicode(value).lower()
+ return str(value).lower()
elif conversion == 'c': # capitalize
- return unicode(value).title()
+ return str(value).title()
else:
return value
@@ -1616,7 +1626,7 @@ class CustomFormatter(Formatter):
match = re.match(pattern, format_spec)
if value and match:
groups = match.groupdict()
- items = [x.strip() for x in unicode(value).split(',')]
+ items = [x.strip() for x in str(value).split(',')]
start = groups['start'] or None
end = groups['end'] or None
if start is not None:
@@ -1666,7 +1676,7 @@ class CustomFormatter(Formatter):
if prefix or suffix:
real_format_string = '{' + real_format_string + '}'
- _, field_name, format_spec, conversion, _, _ = self.parse(real_format_string).next()
+ _, field_name, format_spec, conversion, _, _ = next(self.parse(real_format_string))
yield literal_text, field_name, format_spec, conversion, prefix, suffix
diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py
index 9affc7ed..c6d97398 100644
--- a/plexpy/notifiers.py
+++ b/plexpy/notifiers.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,6 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import next
+from builtins import str
+from builtins import object
+
import base64
import bleach
import json
@@ -28,8 +37,8 @@ import subprocess
import sys
import threading
import time
-from urllib import urlencode
-from urlparse import urlparse
+from urllib.parse import urlencode
+from urllib.parse import urlparse
import uuid
try:
@@ -54,14 +63,14 @@ import twitter
import pynma
import plexpy
-import common
-import database
-import helpers
-import logger
-import mobile_app
-import pmsconnect
-import request
-import users
+from plexpy import common
+from plexpy import database
+from plexpy import helpers
+from plexpy import logger
+from plexpy import mobile_app
+from plexpy import pmsconnect
+from plexpy import request
+from plexpy import users
BROWSER_NOTIFIERS = {}
@@ -438,7 +447,7 @@ def get_notifiers(notifier_id=None, notify_action=None):
% (', '.join(notify_actions), where), args=args)
for item in result:
- item['active'] = int(any([item.pop(k) for k in item.keys() if k in notify_actions]))
+ item['active'] = int(any([item.pop(k) for k in list(item.keys()) if k in notify_actions]))
return result
@@ -483,7 +492,7 @@ def get_notifier_config(notifier_id=None, mask_passwords=False):
notifier_actions = {}
notifier_text = {}
- for k in result.keys():
+ for k in list(result.keys()):
if k in notify_actions:
subject = result.pop(k + '_subject')
body = result.pop(k + '_body')
@@ -581,15 +590,15 @@ def set_notifier_config(notifier_id=None, agent_id=None, **kwargs):
config_prefix = agent['name'] + '_'
actions = {k: helpers.cast_to_int(kwargs.pop(k))
- for k in kwargs.keys() if k in notify_actions}
+ for k in list(kwargs.keys()) if k in notify_actions}
subject_text = {k: kwargs.pop(k)
- for k in kwargs.keys() if k.startswith(notify_actions) and k.endswith('_subject')}
+ for k in list(kwargs.keys()) if k.startswith(notify_actions) and k.endswith('_subject')}
body_text = {k: kwargs.pop(k)
- for k in kwargs.keys() if k.startswith(notify_actions) and k.endswith('_body')}
+ for k in list(kwargs.keys()) if k.startswith(notify_actions) and k.endswith('_body')}
notifier_config = {k[len(config_prefix):]: kwargs.pop(k)
- for k in kwargs.keys() if k.startswith(config_prefix)}
+ for k in list(kwargs.keys()) if k.startswith(config_prefix)}
- for cfg, val in notifier_config.iteritems():
+ for cfg, val in notifier_config.items():
# Check for a password config keys and a blank password from the HTML form
if 'password' in cfg and val == ' ':
# Get the previous password so we don't overwrite it with a blank value
@@ -793,7 +802,7 @@ class Notifier(object):
return default
new_config = {}
- for k, v in default.iteritems():
+ for k, v in default.items():
if isinstance(v, int):
new_config[k] = helpers.cast_to_int(config.get(k, v))
elif isinstance(v, list):
@@ -1404,9 +1413,9 @@ class EMAIL(Notifier):
user_emails_cc.update(emails)
user_emails_bcc.update(emails)
- user_emails_to = [{'value': k, 'text': v} for k, v in user_emails_to.iteritems()]
- user_emails_cc = [{'value': k, 'text': v} for k, v in user_emails_cc.iteritems()]
- user_emails_bcc = [{'value': k, 'text': v} for k, v in user_emails_bcc.iteritems()]
+ user_emails_to = [{'value': k, 'text': v} for k, v in user_emails_to.items()]
+ user_emails_cc = [{'value': k, 'text': v} for k, v in user_emails_cc.items()]
+ user_emails_bcc = [{'value': k, 'text': v} for k, v in user_emails_bcc.items()]
return user_emails_to, user_emails_cc, user_emails_bcc
@@ -2019,7 +2028,7 @@ class IFTTT(Notifier):
}
def agent_notify(self, subject='', body='', action='', **kwargs):
- event = unicode(self.config['event']).format(action=action)
+ event = str(self.config['event']).format(action=action)
data = {'value1': subject.encode('utf-8'),
'value2': body.encode('utf-8')}
@@ -3043,7 +3052,7 @@ class SCRIPTS(Notifier):
for root, dirs, files in os.walk(scriptdir):
for f in files:
name, ext = os.path.splitext(f)
- if ext in self.script_exts.keys():
+ if ext in list(self.script_exts.keys()):
rfp = os.path.join(os.path.relpath(root, scriptdir), f)
fp = os.path.join(root, f)
scripts[fp] = rfp
@@ -3187,7 +3196,7 @@ class SCRIPTS(Notifier):
def _return_config_options(self):
config_option = [{'label': 'Supported File Types',
'description': '' + \
- ', '.join(self.script_exts.keys()) + '',
+ ', '.join(list(self.script_exts.keys())) + '',
'input_type': 'help'
},
{'label': 'Script Folder',
@@ -3947,7 +3956,7 @@ def upgrade_config_to_db():
# Update the new config with the old config values
notifier_config = {}
- for conf, val in notifier_default_config.iteritems():
+ for conf, val in notifier_default_config.items():
c_key = agent_config_key + '_' + config_key_overrides.get(agent, {}).get(conf, conf)
notifier_config[agent + '_' + conf] = agent_config.get(c_key, val)
@@ -3964,15 +3973,15 @@ def upgrade_config_to_db():
# Reverse the dict to {script: [actions]}
script_actions = {}
- for k, v in action_scripts.items():
+ for k, v in list(action_scripts.items()):
if v: script_actions.setdefault(v, set()).add(k)
# Add a new script notifier for each script if the action was enabled
- for script, actions in script_actions.items():
+ for script, actions in list(script_actions.items()):
if any(agent_actions[a] for a in actions):
temp_config = notifier_config
- temp_config.update({a: 0 for a in agent_actions.keys()})
- temp_config.update({a + '_subject': '' for a in agent_actions.keys()})
+ temp_config.update({a: 0 for a in list(agent_actions.keys())})
+ temp_config.update({a + '_subject': '' for a in list(agent_actions.keys())})
for a in actions:
if agent_actions[a]:
temp_config[a] = agent_actions[a]
diff --git a/plexpy/plexivity_import.py b/plexpy/plexivity_import.py
index df02ce84..cc6ee628 100644
--- a/plexpy/plexivity_import.py
+++ b/plexpy/plexivity_import.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,17 +15,20 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+
import arrow
import sqlite3
from xml.dom import minidom
import plexpy
-import activity_pinger
-import activity_processor
-import database
-import helpers
-import logger
-import users
+from plexpy import activity_pinger
+from plexpy import activity_processor
+from plexpy import database
+from plexpy import helpers
+from plexpy import logger
+from plexpy import users
def extract_plexivity_xml(xml=None):
diff --git a/plexpy/plextv.py b/plexpy/plextv.py
index e6823b85..6e3d1635 100644
--- a/plexpy/plextv.py
+++ b/plexpy/plextv.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of Tautulli.
@@ -16,17 +15,22 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import next
+from builtins import str
+from builtins import object
+
import base64
import json
import plexpy
-import common
-import helpers
-import http_handler
-import logger
-import users
-import pmsconnect
-import session
+from plexpy import common
+from plexpy import helpers
+from plexpy import http_handler
+from plexpy import logger
+from plexpy import users
+from plexpy import pmsconnect
+from plexpy import session
def get_server_resources(return_presence=False, return_server=False, **kwargs):
@@ -155,7 +159,7 @@ class PlexTV(object):
base64string = base64.b64encode(('%s:%s' % (self.username, self.password)).encode('utf-8'))
headers = {'Content-Type': 'application/xml; charset=utf-8',
'Authorization': 'Basic %s' % base64string}
-
+
request = self.request_handler.make_request(uri=uri,
request_type='POST',
headers=headers,
@@ -199,7 +203,7 @@ class PlexTV(object):
return None
else:
logger.warn("Tautulli PlexTV :: No existing Tautulli device found.")
-
+
logger.info("Tautulli PlexTV :: Fetching a new Plex.tv token for Tautulli.")
user = self.get_token()
if user:
diff --git a/plexpy/plexwatch_import.py b/plexpy/plexwatch_import.py
index b92f042e..4e6fecba 100644
--- a/plexpy/plexwatch_import.py
+++ b/plexpy/plexwatch_import.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,16 +15,19 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+
import sqlite3
from xml.dom import minidom
import plexpy
-import activity_pinger
-import activity_processor
-import database
-import helpers
-import logger
-import users
+from plexpy import activity_pinger
+from plexpy import activity_processor
+from plexpy import database
+from plexpy import helpers
+from plexpy import logger
+from plexpy import users
def extract_plexwatch_xml(xml=None):
diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py
index 553ad312..bdd743ef 100644
--- a/plexpy/pmsconnect.py
+++ b/plexpy/pmsconnect.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,20 +15,27 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import next
+from builtins import str
+from builtins import object
+
import json
import os
import time
-import urllib
+import urllib.request, urllib.parse, urllib.error
import plexpy
-import activity_processor
-import common
-import helpers
-import http_handler
-import logger
-import plextv
-import session
-import users
+from plexpy import activity_processor
+from plexpy import common
+from plexpy import helpers
+from plexpy import http_handler
+from plexpy import logger
+from plexpy import plextv
+from plexpy import session
+from plexpy import users
def get_server_friendly_name():
@@ -101,7 +110,7 @@ class PmsConnect(object):
Output: array
"""
- uri = '/status/sessions/terminate?sessionId=%s&reason=%s' % (session_id, urllib.quote_plus(reason))
+ uri = '/status/sessions/terminate?sessionId=%s&reason=%s' % (session_id, urllib.parse.quote_plus(reason))
request = self.request_handler.make_request(uri=uri,
request_type='GET',
output_format=output_format)
@@ -352,7 +361,7 @@ class PmsConnect(object):
Output: array
"""
- uri = '/hubs/search?query=' + urllib.quote(query.encode('utf8')) + '&limit=' + limit + '&includeCollections=1'
+ uri = '/hubs/search?query=' + urllib.parse.quote(query.encode('utf8')) + '&limit=' + limit + '&includeCollections=1'
request = self.request_handler.make_request(uri=uri,
request_type='GET',
output_format=output_format)
@@ -726,7 +735,7 @@ class PmsConnect(object):
# Workaround for for duration sometimes reported in minutes for a show
duration = helpers.get_xml_attr(metadata_main, 'duration')
if duration.isdigit() and int(duration) < 1000:
- duration = unicode(int(duration) * 60 * 1000)
+ duration = str(int(duration) * 60 * 1000)
metadata = {'media_type': metadata_type,
'section_id': section_id,
@@ -1500,7 +1509,7 @@ class PmsConnect(object):
session_list.append(session_output)
session_list = sorted(session_list, key=lambda k: k['session_key'])
-
+
output = {'stream_count': helpers.get_xml_attr(xml_head[0], 'size'),
'sessions': session.mask_session_info(session_list)
}
@@ -1534,7 +1543,7 @@ class PmsConnect(object):
if not platform and helpers.get_xml_attr(player_info, 'product') == 'DLNA':
platform = 'DLNA'
- platform_name = next((v for k, v in common.PLATFORM_NAMES.iteritems() if k in platform.lower()), 'default')
+ platform_name = next((v for k, v in common.PLATFORM_NAMES.items() if k in platform.lower()), 'default')
player_details = {'ip_address': helpers.get_xml_attr(player_info, 'address').split('::ffff:')[-1],
'ip_address_public': helpers.get_xml_attr(player_info, 'remotePublicAddress').split('::ffff:')[-1],
@@ -2043,7 +2052,7 @@ class PmsConnect(object):
'user': user_details['username'], # Keep for backwards compatibility
'channel_stream': channel_stream
}
-
+
session_output.update(metadata_details)
session_output.update(source_media_details)
session_output.update(source_media_part_details)
@@ -2254,7 +2263,7 @@ class PmsConnect(object):
hub_identifier = helpers.get_xml_attr(h, 'hubIdentifier')
if size == '0' or not hub_identifier.startswith('collection.related') or \
- media_type not in children_results_list.keys():
+ media_type not in list(children_results_list.keys()):
continue
result_data = []
@@ -2280,7 +2289,7 @@ class PmsConnect(object):
}
children_results_list[media_type].append(children_output)
- output = {'results_count': sum(len(s) for s in children_results_list.items()),
+ output = {'results_count': sum(len(s) for s in list(children_results_list.items())),
'results_list': children_results_list,
}
@@ -2648,9 +2657,9 @@ class PmsConnect(object):
img = '{}/{}'.format(img.rstrip('/'), int(time.time()))
if clip:
- params = {'url': '%s&%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))}
+ params = {'url': '%s&%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))}
else:
- params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.urlencode({'X-Plex-Token': self.token}))}
+ params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urllib.parse.urlencode({'X-Plex-Token': self.token}))}
params['width'] = width
params['height'] = height
@@ -2663,7 +2672,7 @@ class PmsConnect(object):
if blur:
params['blur'] = blur
- uri = '/photo/:/transcode?%s' % urllib.urlencode(params)
+ uri = '/photo/:/transcode?%s' % urllib.parse.urlencode(params)
result = self.request_handler.make_request(uri=uri,
request_type='GET',
return_type=True)
@@ -2705,7 +2714,7 @@ class PmsConnect(object):
for h in hubs:
if helpers.get_xml_attr(h, 'size') == '0' or \
- helpers.get_xml_attr(h, 'type') not in search_results_list.keys():
+ helpers.get_xml_attr(h, 'type') not in list(search_results_list.keys()):
continue
if h.getElementsByTagName('Video'):
@@ -2737,7 +2746,7 @@ class PmsConnect(object):
metadata = self.get_metadata_details(rating_key=rating_key)
search_results_list[metadata['media_type']].append(metadata)
- output = {'results_count': sum(len(s) for s in search_results_list.values()),
+ output = {'results_count': sum(len(s) for s in list(search_results_list.values())),
'results_list': search_results_list
}
diff --git a/plexpy/request.py b/plexpy/request.py
index c2903e27..adbc9317 100644
--- a/plexpy/request.py
+++ b/plexpy/request.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
@@ -13,6 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+
from bs4 import BeautifulSoup
from xml.dom import minidom
@@ -21,13 +26,13 @@ import collections
import requests
import plexpy
-import plexpy.lock
-import logger
+from plexpy import lock
+from plexpy import logger
# Dictionary with last request times, for rate limiting.
last_requests = collections.defaultdict(int)
-fake_lock = plexpy.lock.FakeLock()
+fake_lock = lock.FakeLock()
def request_response(url, method="get", auto_raise=True,
@@ -319,7 +324,7 @@ def server_message(response, return_msg=False):
if return_msg:
try:
- return unicode(message, 'UTF-8')
+ return str(message, 'UTF-8')
except:
return message
diff --git a/plexpy/session.py b/plexpy/session.py
index a3af502c..66a586cb 100644
--- a/plexpy/session.py
+++ b/plexpy/session.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,10 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from builtins import str
+
import cherrypy
-import common
-import users
+from plexpy import common
+from plexpy import users
def get_session_info():
@@ -216,14 +221,14 @@ def mask_session_info(list_of_dicts, mask_metadata=True):
for d in list_of_dicts:
if session_user_id and not (str(d.get('user_id')) == session_user_id or d.get('user') == session_user):
- for k, v in keys_to_mask.iteritems():
+ for k, v in keys_to_mask.items():
if k in d: d[k] = keys_to_mask[k]
if not mask_metadata:
continue
if str(d.get('section_id','')) not in session_library_ids:
- for k, v in metadata_to_mask.iteritems():
+ for k, v in metadata_to_mask.items():
if k in d: d[k] = metadata_to_mask[k]
continue
@@ -247,7 +252,7 @@ def mask_session_info(list_of_dicts, mask_metadata=True):
if d_content_rating in f_content_rating or set(d_labels).intersection(set(f_labels)):
continue
- for k, v in metadata_to_mask.iteritems():
+ for k, v in metadata_to_mask.items():
if k in d: d[k] = metadata_to_mask[k]
return list_of_dicts
\ No newline at end of file
diff --git a/plexpy/users.py b/plexpy/users.py
index 4df4dcd7..05c6d894 100644
--- a/plexpy/users.py
+++ b/plexpy/users.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,18 +15,25 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import next
+from builtins import str
+from builtins import object
+
import httpagentparser
import time
import plexpy
-import common
-import database
-import datatables
-import helpers
-import libraries
-import logger
-import plextv
-import session
+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():
@@ -509,7 +518,7 @@ class Users(object):
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.iteritems() if k in platform.lower()), 'default')
+ 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,
@@ -717,7 +726,7 @@ class Users(object):
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()
@@ -733,9 +742,9 @@ class Users(object):
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):
if user_id:
try:
@@ -757,7 +766,7 @@ class Users(object):
return None
def get_filters(self, user_id=None):
- import urlparse
+ import urllib.parse
if not user_id:
return {}
@@ -772,13 +781,13 @@ class Users(object):
result = {}
filters_list = {}
- for k, v in result.iteritems():
+ for k, v in result.items():
filters = {}
-
+
for f in v.split('|'):
if 'contentRating=' in f or 'label=' in f:
- filters.update(dict(urlparse.parse_qsl(f)))
-
+ filters.update(dict(urllib.parse.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)
diff --git a/plexpy/versioncheck.py b/plexpy/versioncheck.py
index b9440c19..0d2244de 100644
--- a/plexpy/versioncheck.py
+++ b/plexpy/versioncheck.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
@@ -13,6 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import division
+from __future__ import absolute_import
+from builtins import next
+from builtins import str
+from past.utils import old_div
+
import os
import platform
import re
@@ -20,9 +28,9 @@ import subprocess
import tarfile
import plexpy
-import common
-import logger
-import request
+from plexpy import common
+from plexpy import logger
+from plexpy import request
def runGit(args):
@@ -44,7 +52,7 @@ def runGit(args):
logger.debug('Trying to execute: "' + cmd + '" with shell in ' + plexpy.PROG_DIR)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=plexpy.PROG_DIR)
output, err = p.communicate()
- output = output.strip()
+ output = output.strip().decode()
logger.debug('Git output: ' + output)
except OSError:
@@ -372,7 +380,7 @@ def read_changelog(latest_only=False, since_prev_release=False):
output[-1] += '' + header_text + ''
elif line_list_match:
- line_level = len(line_list_match.group(1)) / 2
+ line_level = old_div(len(line_list_match.group(1)), 2)
line_text = line_list_match.group(2)
if line_level > prev_level:
diff --git a/plexpy/web_socket.py b/plexpy/web_socket.py
index 8a98078b..cfc4db46 100644
--- a/plexpy/web_socket.py
+++ b/plexpy/web_socket.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -15,6 +17,11 @@
# Mostly borrowed from https://github.com/trakt/Plex-Trakt-Scrobbler
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+
import json
import threading
import time
@@ -22,11 +29,12 @@ import time
import websocket
import plexpy
-import activity_handler
-import activity_pinger
-import activity_processor
-import database
-import logger
+from plexpy import activity_handler
+from plexpy import activity_pinger
+from plexpy import activity_processor
+from plexpy import database
+from plexpy import logger
+
name = 'websocket'
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
diff --git a/plexpy/webauth.py b/plexpy/webauth.py
index d300516e..0e5d6afd 100644
--- a/plexpy/webauth.py
+++ b/plexpy/webauth.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
@@ -18,15 +20,21 @@
# Form based authentication for CherryPy. Requires the
# Session tool to be loaded.
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import object
+
from datetime import datetime, timedelta
-from urllib import quote, unquote
+from urllib.parse import quote, unquote
import cherrypy
from hashing_passwords import check_hash
import jwt
import plexpy
-import logger
+from plexpy import logger
from plexpy.database import MonitorDatabase
from plexpy.users import Users, refresh_users
from plexpy.plextv import PlexTV
@@ -258,15 +266,15 @@ class AuthController(object):
use_oauth = 'Plex OAuth' if oauth else 'form'
logger.debug("Tautulli WebAuth :: %s user '%s' logged into Tautulli using %s login."
% (user_group.capitalize(), username, use_oauth))
-
+
def on_logout(self, username, user_group):
"""Called on logout"""
logger.debug("Tautulli WebAuth :: %s user '%s' logged out of Tautulli." % (user_group.capitalize(), username))
-
+
def get_loginform(self, redirect_uri=''):
from plexpy.webserve import serve_template
return serve_template(templatename="login.html", title="Login", redirect_uri=unquote(redirect_uri))
-
+
@cherrypy.expose
def index(self, *args, **kwargs):
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "auth/login")
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index d6ea866b..a0f2d1c1 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -1,4 +1,6 @@
-# This file is part of Tautulli.
+# -*- 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
@@ -13,11 +15,19 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import next
+from builtins import str
+from past.builtins import basestring
+from builtins import object
+
import json
import os
import shutil
import threading
-import urllib
+import urllib.request, urllib.parse, urllib.error
import cherrypy
from cherrypy.lib.static import serve_file, serve_download
@@ -30,30 +40,30 @@ from mako import exceptions
import websocket
import plexpy
-import activity_pinger
-import common
-import config
-import database
-import datafactory
-import graphs
-import helpers
-import http_handler
-import libraries
-import log_reader
-import logger
-import newsletter_handler
-import newsletters
-import mobile_app
-import notification_handler
-import notifiers
-import plextv
-import plexivity_import
-import plexwatch_import
-import pmsconnect
-import users
-import versioncheck
-import web_socket
-import webstart
+from plexpy import activity_pinger
+from plexpy import common
+from plexpy import config
+from plexpy import database
+from plexpy import datafactory
+from plexpy import graphs
+from plexpy import helpers
+from plexpy import http_handler
+from plexpy import libraries
+from plexpy import log_reader
+from plexpy import logger
+from plexpy import newsletter_handler
+from plexpy import newsletters
+from plexpy import mobile_app
+from plexpy import notification_handler
+from plexpy import notifiers
+from plexpy import plextv
+from plexpy import plexivity_import
+from plexpy import plexwatch_import
+from plexpy import pmsconnect
+from plexpy import users
+from plexpy import versioncheck
+from plexpy import web_socket
+from plexpy import webstart
from plexpy.api2 import API2
from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates, build_datatables_json, sanitize_out
from plexpy.session import get_session_info, get_session_user_id, allow_session_user, allow_session_library
@@ -288,7 +298,7 @@ class WebInterface(object):
if '{machine_id}' in endpoint:
endpoint = endpoint.format(machine_id=plexpy.CONFIG.PMS_IDENTIFIER)
- return base_url + endpoint + '?' + urllib.urlencode(kwargs)
+ return base_url + endpoint + '?' + urllib.parse.urlencode(kwargs)
@cherrypy.expose
@requireAuth()
@@ -685,7 +695,7 @@ class WebInterface(object):
# Alias 'title' to 'sort_title'
if kwargs.get('order_column') == 'title':
kwargs['order_column'] = 'sort_title'
-
+
# TODO: Find some one way to automatically get the columns
dt_columns = [("added_at", True, False),
("sort_title", True, True),
@@ -2364,13 +2374,13 @@ class WebInterface(object):
try:
temp_loglevel_and_time = l.split(' - ', 1)
loglvl = temp_loglevel_and_time[1].split(' ::', 1)[0].strip()
- msg = helpers.sanitize(unicode(l.split(' : ', 1)[1].replace('\n', ''), 'utf-8'))
+ msg = helpers.sanitize(str(l.split(' : ', 1)[1].replace('\n', ''), 'utf-8'))
fa([temp_loglevel_and_time[0], loglvl, msg])
except IndexError:
# Add traceback message to previous msg.
tl = (len(filt) - 1)
n = len(l) - len(l.lstrip(' '))
- ll = ' ' * (2 * n) + helpers.sanitize(unicode(l[n:], 'utf-8'))
+ ll = ' ' * (2 * n) + helpers.sanitize(str(l[n:], 'utf-8'))
filt[tl][2] += '
' + ll
continue
@@ -2918,14 +2928,14 @@ class WebInterface(object):
# Remove config with 'hsec-' prefix and change home_sections to list
if kwargs.get('home_sections'):
- for k in kwargs.keys():
+ for k in list(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():
+ for k in list(kwargs.keys()):
if k.startswith('hscard-'):
del kwargs[k]
kwargs['home_stats_cards'] = kwargs['home_stats_cards'].split(',')
@@ -2935,7 +2945,7 @@ class WebInterface(object):
# Remove config with 'hlcard-' prefix and change home_library_cards to list
if kwargs.get('home_library_cards'):
- for k in kwargs.keys():
+ for k in list(kwargs.keys()):
if k.startswith('hlcard-'):
del kwargs[k]
kwargs['home_library_cards'] = kwargs['home_library_cards'].split(',')
@@ -3304,7 +3314,7 @@ class WebInterface(object):
'type': param['type'],
'value': param['value']
}
- for category in common.NOTIFICATION_PARAMETERS
+ for category in common.NOTIFICATION_PARAMETERS
for param in category['parameters']]
return parameters
@@ -3864,7 +3874,7 @@ class WebInterface(object):
if git_branch == plexpy.CONFIG.GIT_BRANCH:
logger.error("Already on the %s branch" % git_branch)
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
-
+
# Set the new git remote and branch
plexpy.CONFIG.__setattr__('GIT_REMOTE', git_remote)
plexpy.CONFIG.__setattr__('GIT_BRANCH', git_branch)
diff --git a/plexpy/webstart.py b/plexpy/webstart.py
index 9390e4b9..9b211776 100644
--- a/plexpy/webstart.py
+++ b/plexpy/webstart.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
@@ -13,14 +15,21 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import object
+
import os
import sys
-from urllib import urlencode
+from urllib.parse import urlencode
+
+import cherrypy
import plexpy
-import cherrypy
-import logger
-import webauth
+from plexpy import logger
+from plexpy import webauth
from plexpy.helpers import create_https_certificates
from plexpy.webserve import WebInterface