From ab6196589b0fd86617d822db897fd7e9a26667cf Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 23 Nov 2019 19:11:42 -0800 Subject: [PATCH] Run futurize --stage1 --- Tautulli.py | 2 +- plexpy/__init__.py | 86 ++++++++++++++------------ plexpy/activity_handler.py | 16 +++-- plexpy/activity_pinger.py | 27 ++++---- plexpy/activity_processor.py | 18 +++--- plexpy/api2.py | 43 +++++++------ plexpy/classes.py | 14 +++-- plexpy/common.py | 16 +++-- plexpy/config.py | 110 +++++++++++++++++---------------- plexpy/database.py | 16 +++-- plexpy/datafactory.py | 55 ++++++++++------- plexpy/datatables.py | 11 ++-- plexpy/graphs.py | 21 ++++--- plexpy/helpers.py | 64 ++++++++++++------- plexpy/http_handler.py | 15 +++-- plexpy/libraries.py | 63 ++++++++++--------- plexpy/lock.py | 37 ++++++++--- plexpy/log_reader.py | 12 +++- plexpy/logger.py | 15 +++-- plexpy/mobile_app.py | 13 ++-- plexpy/newsletter_handler.py | 12 ++-- plexpy/newsletters.py | 65 ++++++++++--------- plexpy/notification_handler.py | 88 ++++++++++++++------------ plexpy/notifiers.py | 69 ++++++++++++--------- plexpy/plexivity_import.py | 19 +++--- plexpy/plextv.py | 24 ++++--- plexpy/plexwatch_import.py | 19 +++--- plexpy/pmsconnect.py | 55 ++++++++++------- plexpy/request.py | 13 ++-- plexpy/session.py | 17 +++-- plexpy/users.py | 45 ++++++++------ plexpy/versioncheck.py | 18 ++++-- plexpy/web_socket.py | 20 ++++-- plexpy/webauth.py | 18 ++++-- plexpy/webserve.py | 80 +++++++++++++----------- plexpy/webstart.py | 17 +++-- 36 files changed, 736 insertions(+), 497 deletions(-) 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