mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-15 01:32:57 -07:00
Merge branch 'nightly' into python3
# Conflicts: # plexpy/activity_pinger.py # plexpy/activity_processor.py # plexpy/helpers.py # plexpy/notifiers.py # plexpy/version.py # plexpy/webserve.py
This commit is contained in:
commit
d8f223327e
47 changed files with 566 additions and 3201 deletions
|
@ -472,7 +472,7 @@ def initialize_scheduler():
|
|||
pms_update_check_hours = CONFIG.PMS_UPDATE_CHECK_INTERVAL if 1 <= CONFIG.PMS_UPDATE_CHECK_INTERVAL else 24
|
||||
|
||||
schedule_job(versioncheck.check_update, 'Check GitHub for updates',
|
||||
hours=0, minutes=github_minutes, seconds=0, args=(bool(CONFIG.PLEXPY_AUTO_UPDATE), True))
|
||||
hours=0, minutes=github_minutes, seconds=0, args=(True, True))
|
||||
|
||||
backup_hours = CONFIG.BACKUP_INTERVAL if 1 <= CONFIG.BACKUP_INTERVAL <= 24 else 6
|
||||
|
||||
|
@ -480,15 +480,15 @@ def initialize_scheduler():
|
|||
hours=backup_hours, minutes=0, seconds=0, args=(True, True))
|
||||
schedule_job(config.make_backup, 'Backup Tautulli config',
|
||||
hours=backup_hours, minutes=0, seconds=0, args=(True, True))
|
||||
schedule_job(helpers.update_geoip_db, 'Update GeoLite2 database',
|
||||
hours=12 * bool(CONFIG.GEOIP_DB_INSTALLED), minutes=0, seconds=0)
|
||||
|
||||
if WS_CONNECTED and CONFIG.PMS_IP and CONFIG.PMS_TOKEN:
|
||||
schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs',
|
||||
hours=12 * (not bool(CONFIG.PMS_URL_MANUAL)), minutes=0, seconds=0)
|
||||
|
||||
pms_remote_access_seconds = CONFIG.REMOTE_ACCESS_PING_INTERVAL if 60 <= CONFIG.REMOTE_ACCESS_PING_INTERVAL else 60
|
||||
|
||||
schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access',
|
||||
hours=0, minutes=0, seconds=60 * bool(CONFIG.MONITOR_REMOTE_ACCESS))
|
||||
hours=0, minutes=0, seconds=pms_remote_access_seconds * bool(CONFIG.MONITOR_REMOTE_ACCESS))
|
||||
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
|
||||
hours=pms_update_check_hours * bool(CONFIG.MONITOR_PMS_UPDATES), minutes=0, seconds=0)
|
||||
|
||||
|
@ -612,8 +612,8 @@ def dbcheck():
|
|||
'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, session_key INTEGER, session_id TEXT, '
|
||||
'transcode_key TEXT, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, stopped INTEGER, '
|
||||
'paused_counter INTEGER DEFAULT 0, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, '
|
||||
'ip_address TEXT, machine_id TEXT, player TEXT, product TEXT, platform TEXT, title TEXT, parent_title TEXT, '
|
||||
'grandparent_title TEXT, original_title TEXT, full_title TEXT, '
|
||||
'ip_address TEXT, machine_id TEXT, bandwidth INTEGER, location TEXT, player TEXT, product TEXT, platform TEXT, '
|
||||
'title TEXT, parent_title TEXT, grandparent_title TEXT, original_title TEXT, full_title TEXT, '
|
||||
'media_index INTEGER, parent_media_index INTEGER, '
|
||||
'thumb TEXT, parent_thumb TEXT, grandparent_thumb TEXT, year INTEGER, '
|
||||
'parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
|
||||
|
@ -640,7 +640,13 @@ def dbcheck():
|
|||
'live INTEGER, live_uuid TEXT, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT, '
|
||||
'secure INTEGER, relayed INTEGER, '
|
||||
'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, watched INTEGER DEFAULT 0, '
|
||||
'write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT)'
|
||||
'initial_stream INTEGER DEFAULT 1, write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT)'
|
||||
)
|
||||
|
||||
# sessions_continued table :: This is a temp table that keeps track of continued streaming sessions
|
||||
c_db.execute(
|
||||
'CREATE TABLE IF NOT EXISTS sessions_continued (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'user_id INTEGER, machine_id TEXT, media_type TEXT, stopped INTEGER)'
|
||||
)
|
||||
|
||||
# session_history table :: This is a history table which logs essential stream details
|
||||
|
@ -1294,6 +1300,27 @@ def dbcheck():
|
|||
'ALTER TABLE sessions ADD COLUMN guid TEXT'
|
||||
)
|
||||
|
||||
# Upgrade sessions table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT bandwidth FROM sessions')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table sessions.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE sessions ADD COLUMN bandwidth INTEGER'
|
||||
)
|
||||
c_db.execute(
|
||||
'ALTER TABLE sessions ADD COLUMN location TEXT'
|
||||
)
|
||||
|
||||
# Upgrade sessions table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT initial_stream FROM sessions')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table sessions.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE sessions ADD COLUMN initial_stream INTEGER DEFAULT 1'
|
||||
)
|
||||
|
||||
# Upgrade session_history table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT reference_id FROM session_history')
|
||||
|
|
|
@ -96,14 +96,14 @@ class ActivityHandler(object):
|
|||
|
||||
return None
|
||||
|
||||
def update_db_session(self, session=None):
|
||||
def update_db_session(self, session=None, notify=False):
|
||||
if session is None:
|
||||
session = self.get_live_session()
|
||||
|
||||
if session:
|
||||
# Update our session temp table values
|
||||
ap = activity_processor.ActivityProcessor()
|
||||
ap.write_session(session=session, notify=False)
|
||||
ap.write_session(session=session, notify=notify)
|
||||
|
||||
self.set_session_state()
|
||||
|
||||
|
@ -133,10 +133,11 @@ class ActivityHandler(object):
|
|||
% (str(session['session_key']), str(session['user_id']), session['username'],
|
||||
str(session['rating_key']), session['full_title'], '[Live TV]' if session['live'] else ''))
|
||||
|
||||
plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'})
|
||||
# Send notification after updating db
|
||||
#plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'})
|
||||
|
||||
# Write the new session to our temp session table
|
||||
self.update_db_session(session=session)
|
||||
self.update_db_session(session=session, notify=True)
|
||||
|
||||
# Schedule a callback to force stop a stale stream 5 minutes later
|
||||
schedule_callback('session_key-{}'.format(self.get_session_key()),
|
||||
|
|
|
@ -17,7 +17,6 @@ from __future__ import unicode_literals
|
|||
from future.builtins import str
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
import plexpy
|
||||
if plexpy.PYTHON2:
|
||||
|
@ -327,31 +326,27 @@ def check_server_access():
|
|||
|
||||
# Check for remote access
|
||||
if server_response:
|
||||
|
||||
mapping_state = server_response['mapping_state']
|
||||
mapping_error = server_response['mapping_error']
|
||||
|
||||
# Check if the port is mapped
|
||||
if not mapping_state == 'mapped':
|
||||
if server_response['reason']:
|
||||
ext_ping_count += 1
|
||||
logger.warn("Tautulli Monitor :: Plex remote access port not mapped, ping attempt %s." \
|
||||
% str(ext_ping_count))
|
||||
# Check if the port is open
|
||||
elif mapping_error == 'unreachable':
|
||||
ext_ping_count += 1
|
||||
logger.warn("Tautulli Monitor :: Plex remote access port mapped, but mapping failed, ping attempt %s." \
|
||||
logger.warn("Tautulli Monitor :: Remote access failed: %s, ping attempt %s." \
|
||||
% (server_response['reason'], str(ext_ping_count)))
|
||||
|
||||
# Waiting for port mapping
|
||||
elif server_response['mapping_state'] == 'waiting':
|
||||
logger.warn("Tautulli Monitor :: Remote access waiting for port mapping, ping attempt %s." \
|
||||
% str(ext_ping_count))
|
||||
|
||||
# Reset external ping counter
|
||||
else:
|
||||
if ext_ping_count >= plexpy.CONFIG.REMOTE_ACCESS_PING_THRESHOLD:
|
||||
logger.info("Tautulli Monitor :: Plex remote access is back up.")
|
||||
|
||||
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extup'})
|
||||
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extup', 'remote_access_info': server_response})
|
||||
|
||||
ext_ping_count = 0
|
||||
|
||||
if ext_ping_count == plexpy.CONFIG.REMOTE_ACCESS_PING_THRESHOLD:
|
||||
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extdown'})
|
||||
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extdown', 'remote_access_info': server_response})
|
||||
|
||||
|
||||
def check_server_updates():
|
||||
|
|
|
@ -19,7 +19,6 @@ from future.builtins import object
|
|||
|
||||
from collections import defaultdict
|
||||
import json
|
||||
import time
|
||||
|
||||
import plexpy
|
||||
if plexpy.PYTHON2:
|
||||
|
@ -68,6 +67,8 @@ class ActivityProcessor(object):
|
|||
'year': session.get('year', ''),
|
||||
'friendly_name': session.get('friendly_name', ''),
|
||||
'ip_address': session.get('ip_address', ''),
|
||||
'bandwidth': session.get('bandwidth', 0),
|
||||
'location': session.get('location', ''),
|
||||
'player': session.get('player', ''),
|
||||
'product': session.get('product', ''),
|
||||
'platform': session.get('platform', ''),
|
||||
|
@ -152,15 +153,20 @@ class ActivityProcessor(object):
|
|||
result = self.db.upsert('sessions', values, keys)
|
||||
|
||||
if result == 'insert':
|
||||
# Check if any notification agents have notifications enabled
|
||||
if notify:
|
||||
plexpy.NOTIFY_QUEUE.put({'stream_data': values.copy(), 'notify_action': 'on_play'})
|
||||
|
||||
# If it's our first write then time stamp it.
|
||||
started = helpers.timestamp()
|
||||
timestamp = {'started': started}
|
||||
initial_stream = self.is_initial_stream(user_id=values['user_id'],
|
||||
machine_id=values['machine_id'],
|
||||
media_type=values['media_type'],
|
||||
started=started)
|
||||
timestamp = {'started': started, 'initial_stream': initial_stream}
|
||||
self.db.upsert('sessions', timestamp, keys)
|
||||
|
||||
# Check if any notification agents have notifications enabled
|
||||
if notify:
|
||||
session.update(timestamp)
|
||||
plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'})
|
||||
|
||||
# Add Live TV library if it hasn't been added
|
||||
if values['live']:
|
||||
libraries.add_live_tv_library()
|
||||
|
@ -209,6 +215,12 @@ class ActivityProcessor(object):
|
|||
state='stopped',
|
||||
stopped=stopped)
|
||||
|
||||
if not is_import:
|
||||
self.write_continued_session(user_id=session['user_id'],
|
||||
machine_id=session['machine_id'],
|
||||
media_type=session['media_type'],
|
||||
stopped=stopped)
|
||||
|
||||
if str(session['rating_key']).isdigit() and session['media_type'] in ('movie', 'episode', 'track'):
|
||||
logging_enabled = True
|
||||
else:
|
||||
|
@ -637,3 +649,16 @@ class ActivityProcessor(object):
|
|||
self.db.action('UPDATE sessions SET watched = ?'
|
||||
'WHERE session_key = ?',
|
||||
[1, session_key])
|
||||
|
||||
def write_continued_session(self, user_id=None, machine_id=None, media_type=None, stopped=None):
|
||||
keys = {'user_id': user_id, 'machine_id': machine_id, 'media_type': media_type}
|
||||
values = {'stopped': stopped}
|
||||
self.db.upsert(table_name='sessions_continued', key_dict=keys, value_dict=values)
|
||||
|
||||
def is_initial_stream(self, user_id=None, machine_id=None, media_type=None, started=None):
|
||||
last_session = self.db.select_single('SELECT stopped '
|
||||
'FROM sessions_continued '
|
||||
'WHERE user_id = ? AND machine_id = ? AND media_type = ? '
|
||||
'ORDER BY stopped DESC',
|
||||
[user_id, machine_id, media_type])
|
||||
return int(started - last_session.get('stopped', 0) >= plexpy.CONFIG.NOTIFY_CONTINUED_SESSION_THRESHOLD)
|
||||
|
|
|
@ -631,6 +631,12 @@ General optional parameters:
|
|||
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
|
||||
return out['response']['data']
|
||||
|
||||
elif self._api_cmd == 'get_geoip_lookup':
|
||||
# Remove nested data and put error message inside data for backwards compatibility
|
||||
out['response']['data'] = out['response']['data'].get('data')
|
||||
if not out['response']['data']:
|
||||
out['response']['data'] = {'error': out['response']['message']}
|
||||
|
||||
if self._api_out_type == 'json':
|
||||
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
|
||||
try:
|
||||
|
|
|
@ -224,8 +224,7 @@ SCHEDULER_LIST = [
|
|||
'Refresh libraries list',
|
||||
'Refresh Plex server URLs',
|
||||
'Backup Tautulli database',
|
||||
'Backup Tautulli config',
|
||||
'Update GeoLite2 database'
|
||||
'Backup Tautulli config'
|
||||
]
|
||||
|
||||
DATE_TIME_FORMATS = [
|
||||
|
@ -350,10 +349,13 @@ NOTIFICATION_PARAMETERS = [
|
|||
{
|
||||
'category': 'Stream Details',
|
||||
'parameters': [
|
||||
{'name': 'Streams', 'type': 'int', 'value': 'streams', 'description': 'The number of concurrent streams.'},
|
||||
{'name': 'Direct Plays', 'type': 'int', 'value': 'direct_plays', 'description': 'The number of concurrent direct plays.'},
|
||||
{'name': 'Direct Streams', 'type': 'int', 'value': 'direct_streams', 'description': 'The number of concurrent direct streams.'},
|
||||
{'name': 'Transcodes', 'type': 'int', 'value': 'transcodes', 'description': 'The number of concurrent transcodes.'},
|
||||
{'name': 'Streams', 'type': 'int', 'value': 'streams', 'description': 'The total number of concurrent streams.'},
|
||||
{'name': 'Direct Plays', 'type': 'int', 'value': 'direct_plays', 'description': 'The total number of concurrent direct plays.'},
|
||||
{'name': 'Direct Streams', 'type': 'int', 'value': 'direct_streams', 'description': 'The total number of concurrent direct streams.'},
|
||||
{'name': 'Transcodes', 'type': 'int', 'value': 'transcodes', 'description': 'The total number of concurrent transcodes.'},
|
||||
{'name': 'Total Bandwidth', 'type': 'int', 'value': 'total_bandwidth', 'description': 'The total Plex Streaming Brain reserved bandwidth (in kbps).', 'help_text': 'not the used bandwidth'},
|
||||
{'name': 'LAN Bandwidth', 'type': 'int', 'value': 'lan_bandwidth', 'description': 'The total Plex Streaming Brain reserved LAN bandwidth (in kbps).', 'help_text': 'not the used bandwidth'},
|
||||
{'name': 'WAN Bandwidth', 'type': 'int', 'value': 'wan_bandwidth', 'description': 'The total Plex Streaming Brain reserved WAN bandwidth (in kbps).', 'help_text': 'not the used bandwidth'},
|
||||
{'name': 'User Streams', 'type': 'int', 'value': 'user_streams', 'description': 'The number of concurrent streams by the user streaming.'},
|
||||
{'name': 'User Direct Plays', 'type': 'int', 'value': 'user_direct_plays', 'description': 'The number of concurrent direct plays by the user streaming.'},
|
||||
{'name': 'User Direct Streams', 'type': 'int', 'value': 'user_direct_streams', 'description': 'The number of concurrent direct streams by the user streaming.'},
|
||||
|
@ -361,10 +363,12 @@ NOTIFICATION_PARAMETERS = [
|
|||
{'name': 'User', 'type': 'str', 'value': 'user', 'description': 'The friendly name of the user streaming.'},
|
||||
{'name': 'Username', 'type': 'str', 'value': 'username', 'description': 'The username of the user streaming.'},
|
||||
{'name': 'User Email', 'type': 'str', 'value': 'user_email', 'description': 'The email address of the user streaming.'},
|
||||
{'name': 'User Thumb', 'type': 'str', 'value': 'user_thumb', 'description': 'The profile picture URL of the user streaming.'},
|
||||
{'name': 'Device', 'type': 'str', 'value': 'device', 'description': 'The type of client device being used for playback.'},
|
||||
{'name': 'Platform', 'type': 'str', 'value': 'platform', 'description': 'The type of client platform being used for playback.'},
|
||||
{'name': 'Product', 'type': 'str', 'value': 'product', 'description': 'The type of client product being used for playback.'},
|
||||
{'name': 'Player', 'type': 'str', 'value': 'player', 'description': 'The name of the player being used for playback.'},
|
||||
{'name': 'Initial Stream', 'type': 'int', 'value': 'initial_stream', 'description': 'If the stream is the initial stream of a continuous streaming session.', 'example': '0 or 1'},
|
||||
{'name': 'IP Address', 'type': 'str', 'value': 'ip_address', 'description': 'The IP address of the device being used for playback.'},
|
||||
{'name': 'Stream Duration', 'type': 'int', 'value': 'stream_duration', 'description': 'The duration (in minutes) for the stream.'},
|
||||
{'name': 'Stream Time', 'type': 'str', 'value': 'stream_time', 'description': 'The duration (in time format) of the stream.'},
|
||||
|
@ -389,7 +393,7 @@ NOTIFICATION_PARAMETERS = [
|
|||
{'name': 'Relayed', 'type': 'int', 'value': 'relayed', 'description': 'If the stream is using Plex Relay.', 'example': '0 or 1'},
|
||||
{'name': 'Stream Local', 'type': 'int', 'value': 'stream_local', 'description': 'If the stream is local.', 'example': '0 or 1'},
|
||||
{'name': 'Stream Location', 'type': 'str', 'value': 'stream_location', 'description': 'The network location of the stream.', 'example': 'lan or wan'},
|
||||
{'name': 'Stream Bandwidth', 'type': 'int', 'value': 'stream_bandwidth', 'description': 'The required bandwidth (in kbps) of the stream.', 'help_text': 'not the used bandwidth'},
|
||||
{'name': 'Stream Bandwidth', 'type': 'int', 'value': 'stream_bandwidth', 'description': 'The Plex Streaming Brain reserved bandwidth (in kbps) of the stream.', 'help_text': 'not the used bandwidth'},
|
||||
{'name': 'Stream Container', 'type': 'str', 'value': 'stream_container', 'description': 'The media container of the stream.'},
|
||||
{'name': 'Stream Bitrate', 'type': 'int', 'value': 'stream_bitrate', 'description': 'The bitrate (in kbps) of the stream.'},
|
||||
{'name': 'Stream Aspect Ratio', 'type': 'float', 'value': 'stream_aspect_ratio', 'description': 'The aspect ratio of the stream.'},
|
||||
|
@ -556,6 +560,18 @@ NOTIFICATION_PARAMETERS = [
|
|||
{'name': 'Indexes', 'type': 'int', 'value': 'indexes', 'description': 'If the media has video preview thumbnails.', 'example': '0 or 1'},
|
||||
]
|
||||
},
|
||||
{
|
||||
'category': 'Plex Remote Access',
|
||||
'parameters': [
|
||||
{'name': 'Remote Access Mapping State', 'type': 'str', 'value': 'remote_access_mapping_state', 'description': 'The mapping state of the Plex remote access port.'},
|
||||
{'name': 'Remote Access Mapping Error', 'type': 'str', 'value': 'remote_access_mapping_error', 'description': 'The mapping error of the Plex remote access port.'},
|
||||
{'name': 'Remote Access Public IP Address', 'type': 'str', 'value': 'remote_access_public_address', 'description': 'The Plex remote access public IP address.'},
|
||||
{'name': 'Remote Access Public Port', 'type': 'str', 'value': 'remote_access_public_port', 'description': 'The Plex remote access public port.'},
|
||||
{'name': 'Remote Access Private IP Address', 'type': 'str', 'value': 'remote_access_private_address', 'description': 'The Plex remote access private IP address.'},
|
||||
{'name': 'Remote Access Private Port', 'type': 'str', 'value': 'remote_access_private_port', 'description': 'The Plex remote access private port.'},
|
||||
{'name': 'Remote Access Failure Reason', 'type': 'str', 'value': 'remote_access_reason', 'description': 'The failure reason for Plex remote access going down.'},
|
||||
]
|
||||
},
|
||||
{
|
||||
'category': 'Plex Update Available',
|
||||
'parameters': [
|
||||
|
|
|
@ -182,9 +182,6 @@ _CONFIG_DEFINITIONS = {
|
|||
'FACEBOOK_ON_NEWDEVICE': (int, 'Facebook', 0),
|
||||
'FIRST_RUN_COMPLETE': (int, 'General', 0),
|
||||
'FREEZE_DB': (int, 'General', 0),
|
||||
'GEOIP_DB': (str, 'General', ''),
|
||||
'GEOIP_DB_INSTALLED': (int, 'General', 0),
|
||||
'GEOIP_DB_UPDATE_DAYS': (int, 'General', 30),
|
||||
'GET_FILE_SIZES': (int, 'General', 0),
|
||||
'GET_FILE_SIZES_HOLD': (dict, 'General', {'section_ids': [], 'rating_keys': []}),
|
||||
'GIT_BRANCH': (str, 'General', 'master'),
|
||||
|
@ -299,7 +296,6 @@ _CONFIG_DEFINITIONS = {
|
|||
'LOG_BLACKLIST': (int, 'General', 1),
|
||||
'LOG_DIR': (str, 'General', ''),
|
||||
'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120),
|
||||
'MAXMIND_LICENSE_KEY': (str, 'General', ''),
|
||||
'METADATA_CACHE_SECONDS': (int, 'Advanced', 1800),
|
||||
'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
'MOVIE_NOTIFY_ENABLE': (int, 'Monitoring', 0),
|
||||
|
@ -345,6 +341,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'NMA_ON_NEWDEVICE': (int, 'NMA', 0),
|
||||
'NOTIFICATION_THREADS': (int, 'Advanced', 2),
|
||||
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
|
||||
'NOTIFY_CONTINUED_SESSION_THRESHOLD': (int, 'Monitoring', 15),
|
||||
'NOTIFY_GROUP_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 1),
|
||||
'NOTIFY_GROUP_RECENTLY_ADDED_PARENT': (int, 'Monitoring', 1),
|
||||
'NOTIFY_GROUP_RECENTLY_ADDED': (int, 'Monitoring', 1),
|
||||
|
@ -497,6 +494,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1),
|
||||
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
|
||||
'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1),
|
||||
'REMOTE_ACCESS_PING_INTERVAL': (int, 'Advanced', 60),
|
||||
'REMOTE_ACCESS_PING_THRESHOLD': (int, 'Advanced', 3),
|
||||
'SESSION_DB_WRITE_ATTEMPTS': (int, 'Advanced', 5),
|
||||
'SHOW_ADVANCED_SETTINGS': (int, 'General', 0),
|
||||
|
@ -937,8 +935,6 @@ class Config(object):
|
|||
self.CONFIG_VERSION = 13
|
||||
|
||||
if self.CONFIG_VERSION == 13:
|
||||
if not self.GEOIP_DB:
|
||||
self.GEOIP_DB = os.path.join(plexpy.DATA_DIR, 'GeoLite2-City.mmdb')
|
||||
|
||||
self.CONFIG_VERSION = 14
|
||||
|
||||
|
|
|
@ -250,7 +250,7 @@ class MonitorDatabase(object):
|
|||
sql_results = self.action(query, args).fetchone()
|
||||
|
||||
if sql_results is None or sql_results == "":
|
||||
return ""
|
||||
return {}
|
||||
|
||||
return sql_results
|
||||
|
||||
|
|
|
@ -246,6 +246,7 @@ class DataFactory(object):
|
|||
|
||||
row = {'reference_id': item['reference_id'],
|
||||
'row_id': item['row_id'],
|
||||
'id': item['row_id'],
|
||||
'date': item['date'],
|
||||
'started': item['started'],
|
||||
'stopped': item['stopped'],
|
||||
|
|
|
@ -23,15 +23,12 @@ from future.builtins import str
|
|||
|
||||
import arrow
|
||||
import base64
|
||||
import certifi
|
||||
import cloudinary
|
||||
from cloudinary.api import delete_resources_by_tag
|
||||
from cloudinary.uploader import upload
|
||||
from cloudinary.utils import cloudinary_url
|
||||
import datetime
|
||||
from functools import wraps
|
||||
import geoip2.database
|
||||
import geoip2.errors
|
||||
import hashlib
|
||||
import imghdr
|
||||
from future.moves.itertools import zip_longest
|
||||
|
@ -41,19 +38,14 @@ import ipwhois.utils
|
|||
from IPy import IP
|
||||
import json
|
||||
import math
|
||||
import maxminddb
|
||||
from operator import itemgetter
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
import unicodedata
|
||||
from future.moves.urllib.parse import urlencode
|
||||
import urllib3
|
||||
from xml.dom import minidom
|
||||
import xmltodict
|
||||
|
||||
|
@ -612,164 +604,6 @@ def is_valid_ip(address):
|
|||
return False
|
||||
|
||||
|
||||
def update_geoip_db():
|
||||
if plexpy.CONFIG.GEOIP_DB_INSTALLED:
|
||||
logger.info("Tautulli Helpers :: Checking for GeoLite2 database updates.")
|
||||
now = timestamp()
|
||||
if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS * 24 * 60 * 60:
|
||||
return install_geoip_db(update=True)
|
||||
logger.info("Tautulli Helpers :: GeoLite2 database already updated within the last %s days."
|
||||
% plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS)
|
||||
|
||||
|
||||
def install_geoip_db(update=False):
|
||||
if not plexpy.CONFIG.MAXMIND_LICENSE_KEY:
|
||||
logger.error("Tautulli Helpers :: Failed to download GeoLite2 database file from MaxMind: Missing MaxMindLicense Key")
|
||||
return False
|
||||
|
||||
maxmind_db = 'GeoLite2-City'
|
||||
maxmind_url = 'https://download.maxmind.com/app/geoip_download?edition_id={db}&suffix={{suffix}}&license_key={key}'.format(
|
||||
db=maxmind_db, key=plexpy.CONFIG.MAXMIND_LICENSE_KEY)
|
||||
geolite2_db_url = maxmind_url.format(suffix='tar.gz')
|
||||
geolite2_md5_url = maxmind_url.format(suffix='tar.gz.md5')
|
||||
geolite2_gz = maxmind_db + '.tar.gz'
|
||||
geolite2_md5 = geolite2_gz + '.md5'
|
||||
geolite2_db = maxmind_db + '.mmdb'
|
||||
geolite2_db_path = plexpy.CONFIG.GEOIP_DB or os.path.join(plexpy.DATA_DIR, geolite2_db)
|
||||
|
||||
# Check path ends with .mmdb
|
||||
if os.path.splitext(geolite2_db_path)[1] != os.path.splitext(geolite2_db)[1]:
|
||||
geolite2_db_path = os.path.join(geolite2_db_path, geolite2_db)
|
||||
|
||||
temp_gz = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_gz)
|
||||
temp_md5 = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_md5)
|
||||
|
||||
# Retrieve the GeoLite2 gzip file
|
||||
logger.debug("Tautulli Helpers :: Downloading GeoLite2 gzip file from MaxMind...")
|
||||
try:
|
||||
maxmind = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())
|
||||
with maxmind.request('GET', geolite2_db_url, preload_content=False) as r_db, open(temp_gz, 'wb') as f_db:
|
||||
shutil.copyfileobj(r_db, f_db)
|
||||
with maxmind.request('GET', geolite2_md5_url, preload_content=False) as r_md5, open(temp_md5, 'wb') as f_md5:
|
||||
shutil.copyfileobj(r_md5, f_md5)
|
||||
except Exception as e:
|
||||
logger.error("Tautulli Helpers :: Failed to download GeoLite2 gzip file from MaxMind: %s" % e)
|
||||
return False
|
||||
|
||||
# Check MD5 hash for GeoLite2 tar.gz file
|
||||
logger.debug("Tautulli Helpers :: Checking MD5 checksum for GeoLite2 gzip file...")
|
||||
try:
|
||||
hash_md5 = hashlib.md5()
|
||||
with open(temp_gz, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
hash_md5.update(chunk)
|
||||
md5_hash = hash_md5.hexdigest()
|
||||
|
||||
with open(temp_md5, 'r') as f:
|
||||
md5_checksum = f.read()
|
||||
|
||||
if md5_hash != md5_checksum:
|
||||
logger.error("Tautulli Helpers :: MD5 checksum doesn't match for GeoLite2 database. "
|
||||
"Checksum: %s, file hash: %s" % (md5_checksum, md5_hash))
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error("Tautulli Helpers :: Failed to generate MD5 checksum for GeoLite2 gzip file: %s" % e)
|
||||
return False
|
||||
|
||||
# Extract the GeoLite2 database file
|
||||
logger.debug("Tautulli Helpers :: Extracting GeoLite2 database...")
|
||||
try:
|
||||
mmdb = None
|
||||
with tarfile.open(temp_gz, 'r:gz') as tar:
|
||||
for member in tar.getmembers():
|
||||
if geolite2_db in member.name:
|
||||
member.name = os.path.basename(member.name)
|
||||
tar.extractall(path=os.path.dirname(geolite2_db_path), members=[member])
|
||||
mmdb = True
|
||||
break
|
||||
if not mmdb:
|
||||
raise Exception("{} not found in gzip file.".format(geolite2_db))
|
||||
except Exception as e:
|
||||
logger.error("Tautulli Helpers :: Failed to extract the GeoLite2 database: %s" % e)
|
||||
return False
|
||||
|
||||
# Delete temportary GeoLite2 gzip file
|
||||
logger.debug("Tautulli Helpers :: Deleting temporary GeoLite2 gzip file...")
|
||||
try:
|
||||
os.remove(temp_gz)
|
||||
os.remove(temp_md5)
|
||||
except Exception as e:
|
||||
logger.warn("Tautulli Helpers :: Failed to remove temporary GeoLite2 gzip file: %s" % e)
|
||||
|
||||
plexpy.CONFIG.__setattr__('GEOIP_DB', geolite2_db_path)
|
||||
plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', timestamp())
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
logger.debug("Tautulli Helpers :: GeoLite2 database installed successfully.")
|
||||
|
||||
if not update:
|
||||
plexpy.schedule_job(update_geoip_db, 'Update GeoLite2 database', hours=12, minutes=0, seconds=0)
|
||||
|
||||
return plexpy.CONFIG.GEOIP_DB_INSTALLED
|
||||
|
||||
|
||||
def uninstall_geoip_db():
|
||||
logger.debug("Tautulli Helpers :: Uninstalling the GeoLite2 database...")
|
||||
try:
|
||||
os.remove(plexpy.CONFIG.GEOIP_DB)
|
||||
except Exception as e:
|
||||
logger.error("Tautulli Helpers :: Failed to uninstall the GeoLite2 database: %s" % e)
|
||||
return False
|
||||
|
||||
plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', 0)
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
logger.debug("Tautulli Helpers :: GeoLite2 database uninstalled successfully.")
|
||||
|
||||
plexpy.schedule_job(update_geoip_db, 'Update GeoLite2 database', hours=0, minutes=0, seconds=0)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def geoip_lookup(ip_address):
|
||||
if not plexpy.CONFIG.GEOIP_DB_INSTALLED:
|
||||
return 'GeoLite2 database not installed. Please install from the ' \
|
||||
'<a href="settings?install_geoip=true">Settings</a> page.'
|
||||
|
||||
if not ip_address:
|
||||
return 'No IP address provided.'
|
||||
|
||||
try:
|
||||
reader = geoip2.database.Reader(plexpy.CONFIG.GEOIP_DB)
|
||||
geo = reader.city(ip_address)
|
||||
reader.close()
|
||||
except ValueError as e:
|
||||
return 'Invalid IP address provided: %s.' % ip_address
|
||||
except IOError as e:
|
||||
return 'Missing GeoLite2 database. Please reinstall from the ' \
|
||||
'<a href="settings?install_geoip=true">Settings</a> page.'
|
||||
except maxminddb.InvalidDatabaseError as e:
|
||||
return 'Invalid GeoLite2 database. Please reinstall from the ' \
|
||||
'<a href="settings?install_geoip=true">Settings</a> page.'
|
||||
except geoip2.errors.AddressNotFoundError as e:
|
||||
return '%s' % e
|
||||
except Exception as e:
|
||||
return 'Error: %s' % e
|
||||
|
||||
geo_info = {'continent': geo.continent.name,
|
||||
'country': geo.country.name,
|
||||
'region': geo.subdivisions.most_specific.name,
|
||||
'city': geo.city.name,
|
||||
'postal_code': geo.postal.code,
|
||||
'timezone': geo.location.time_zone,
|
||||
'latitude': geo.location.latitude,
|
||||
'longitude': geo.location.longitude,
|
||||
'accuracy': geo.location.accuracy_radius
|
||||
}
|
||||
|
||||
return geo_info
|
||||
|
||||
|
||||
def whois_lookup(ip_address):
|
||||
|
||||
nets = []
|
||||
|
|
|
@ -755,7 +755,7 @@ class Libraries(object):
|
|||
except Exception as e:
|
||||
logger.warn("Tautulli Libraries :: Unable to execute database query for set_config: %s." % e)
|
||||
|
||||
def get_details(self, section_id=None):
|
||||
def get_details(self, section_id=None, server_id=None):
|
||||
default_return = {'row_id': 0,
|
||||
'server_id': '',
|
||||
'section_id': 0,
|
||||
|
@ -776,7 +776,10 @@ class Libraries(object):
|
|||
if not section_id:
|
||||
return default_return
|
||||
|
||||
def get_library_details(section_id=section_id):
|
||||
if server_id is None:
|
||||
server_id = plexpy.CONFIG.PMS_IDENTIFIER
|
||||
|
||||
def get_library_details(section_id=section_id, server_id=server_id):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
try:
|
||||
|
@ -787,8 +790,8 @@ class Libraries(object):
|
|||
'custom_art_url AS custom_art, is_active, ' \
|
||||
'do_notify, do_notify_created, keep_history, deleted_section ' \
|
||||
'FROM library_sections ' \
|
||||
'WHERE section_id = ? '
|
||||
result = monitor_db.select(query, args=[section_id])
|
||||
'WHERE section_id = ? AND server_id = ? '
|
||||
result = monitor_db.select(query, args=[section_id, server_id])
|
||||
else:
|
||||
result = []
|
||||
except Exception as e:
|
||||
|
@ -828,7 +831,7 @@ class Libraries(object):
|
|||
}
|
||||
return library_details
|
||||
|
||||
library_details = get_library_details(section_id=section_id)
|
||||
library_details = get_library_details(section_id=section_id, server_id=server_id)
|
||||
|
||||
if library_details:
|
||||
return library_details
|
||||
|
@ -839,7 +842,7 @@ class Libraries(object):
|
|||
# Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet
|
||||
refresh_libraries()
|
||||
|
||||
library_details = get_library_details(section_id=section_id)
|
||||
library_details = get_library_details(section_id=section_id, server_id=server_id)
|
||||
|
||||
if library_details:
|
||||
return library_details
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
import email.utils
|
||||
|
|
|
@ -565,6 +565,10 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||
stream_count = len(sessions)
|
||||
user_stream_count = len(user_sessions)
|
||||
|
||||
lan_bandwidth = sum(helpers.cast_to_int(s['bandwidth']) for s in sessions if s['location'] == 'lan')
|
||||
wan_bandwidth = sum(helpers.cast_to_int(s['bandwidth']) for s in sessions if s['location'] != 'lan')
|
||||
total_bandwidth = lan_bandwidth + wan_bandwidth
|
||||
|
||||
# Generate a combined transcode decision value
|
||||
if session.get('stream_video_decision', '') == 'transcode' or session.get('stream_audio_decision', '') == 'transcode':
|
||||
transcode_decision = 'Transcode'
|
||||
|
@ -650,6 +654,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||
themoviedb_info = lookup_themoviedb_by_id(rating_key=lookup_key,
|
||||
thetvdb_id=notify_params.get('thetvdb_id'),
|
||||
imdb_id=notify_params.get('imdb_id'))
|
||||
themoviedb_info.pop('rating_key', None)
|
||||
notify_params.update(themoviedb_info)
|
||||
|
||||
# Get TVmaze info (for tv shows only)
|
||||
|
@ -665,6 +670,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||
tvmaze_info = lookup_tvmaze_by_id(rating_key=lookup_key,
|
||||
thetvdb_id=notify_params.get('thetvdb_id'),
|
||||
imdb_id=notify_params.get('imdb_id'))
|
||||
tvmaze_info.pop('rating_key', None)
|
||||
notify_params.update(tvmaze_info)
|
||||
|
||||
if tvmaze_info.get('thetvdb_id'):
|
||||
|
@ -685,7 +691,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||
tracks = notify_params['children_count']
|
||||
else:
|
||||
musicbrainz_type = 'recording'
|
||||
artist = notify_params['original_title']
|
||||
artist = notify_params['original_title'] or notify_params['grandparent_title']
|
||||
release = notify_params['parent_title']
|
||||
recording = notify_params['title']
|
||||
tracks = notify_params['children_count']
|
||||
|
@ -694,6 +700,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||
musicbrainz_info = lookup_musicbrainz_info(musicbrainz_type=musicbrainz_type, rating_key=rating_key,
|
||||
artist=artist, release=release, recording=recording, tracks=tracks,
|
||||
tnum=tnum)
|
||||
musicbrainz_info.pop('rating_key', None)
|
||||
notify_params.update(musicbrainz_info)
|
||||
|
||||
if notify_params['media_type'] in ('movie', 'show', 'artist'):
|
||||
|
@ -831,6 +838,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||
'direct_plays': transcode_decision_count['direct play'],
|
||||
'direct_streams': transcode_decision_count['copy'],
|
||||
'transcodes': transcode_decision_count['transcode'],
|
||||
'total_bandwidth': total_bandwidth,
|
||||
'lan_bandwidth': lan_bandwidth,
|
||||
'wan_bandwidth': wan_bandwidth,
|
||||
'user_streams': user_stream_count,
|
||||
'user_direct_plays': user_transcode_decision_count['direct play'],
|
||||
'user_direct_streams': user_transcode_decision_count['copy'],
|
||||
|
@ -838,6 +848,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||
'user': notify_params['friendly_name'],
|
||||
'username': notify_params['user'],
|
||||
'user_email': notify_params['email'],
|
||||
'user_thumb': notify_params['user_thumb'],
|
||||
'device': notify_params['device'],
|
||||
'platform': notify_params['platform'],
|
||||
'product': notify_params['product'],
|
||||
|
@ -850,6 +861,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||
'progress_duration': view_offset,
|
||||
'progress_time': arrow.get(view_offset * 60).format(duration_format),
|
||||
'progress_percent': helpers.get_percent(view_offset, duration),
|
||||
'initial_stream': notify_params['initial_stream'],
|
||||
'transcode_decision': transcode_decision,
|
||||
'video_decision': notify_params['video_decision'],
|
||||
'audio_decision': notify_params['audio_decision'],
|
||||
|
@ -1047,6 +1059,7 @@ def build_server_notify_params(notify_action=None, **kwargs):
|
|||
|
||||
pms_download_info = defaultdict(str, kwargs.pop('pms_download_info', {}))
|
||||
plexpy_download_info = defaultdict(str, kwargs.pop('plexpy_download_info', {}))
|
||||
remote_access_info = defaultdict(str, kwargs.pop('remote_access_info', {}))
|
||||
|
||||
now = arrow.now()
|
||||
now_iso = now.isocalendar()
|
||||
|
@ -1078,6 +1091,14 @@ def build_server_notify_params(notify_action=None, **kwargs):
|
|||
'timestamp': now.format(time_format),
|
||||
'unixtime': helpers.timestamp(),
|
||||
'utctime': helpers.utc_now_iso(),
|
||||
# Plex remote access parameters
|
||||
'remote_access_mapping_state': remote_access_info['mapping_state'],
|
||||
'remote_access_mapping_error': remote_access_info['mapping_error'],
|
||||
'remote_access_public_address': remote_access_info['public_address'],
|
||||
'remote_access_public_port': remote_access_info['public_port'],
|
||||
'remote_access_private_address': remote_access_info['private_address'],
|
||||
'remote_access_private_port': remote_access_info['private_port'],
|
||||
'remote_access_reason': remote_access_info['reason'],
|
||||
# Plex Media Server update parameters
|
||||
'update_version': pms_download_info['version'],
|
||||
'update_url': pms_download_info['download_url'],
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from future.builtins import next
|
||||
from future.builtins import str
|
||||
from future.builtins import object
|
||||
|
||||
|
@ -81,7 +80,6 @@ else:
|
|||
|
||||
BROWSER_NOTIFIERS = {}
|
||||
|
||||
|
||||
AGENT_IDS = {'growl': 0,
|
||||
'prowl': 1,
|
||||
'xbmc': 2,
|
||||
|
@ -104,7 +102,8 @@ AGENT_IDS = {'growl': 0,
|
|||
'groupme': 22,
|
||||
'mqtt': 23,
|
||||
'zapier': 24,
|
||||
'webhook': 25
|
||||
'webhook': 25,
|
||||
'plexmobileapp': 26
|
||||
}
|
||||
|
||||
DEFAULT_CUSTOM_CONDITIONS = [{'parameter': '', 'operator': '', 'value': ''}]
|
||||
|
@ -113,91 +112,141 @@ DEFAULT_CUSTOM_CONDITIONS = [{'parameter': '', 'operator': '', 'value': ''}]
|
|||
def available_notification_agents():
|
||||
agents = [{'label': 'Tautulli Remote Android App',
|
||||
'name': 'androidapp',
|
||||
'id': AGENT_IDS['androidapp']
|
||||
'id': AGENT_IDS['androidapp'],
|
||||
'class': ANDROIDAPP,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Boxcar',
|
||||
'name': 'boxcar',
|
||||
'id': AGENT_IDS['boxcar']
|
||||
'id': AGENT_IDS['boxcar'],
|
||||
'class': BOXCAR,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Browser',
|
||||
'name': 'browser',
|
||||
'id': AGENT_IDS['browser']
|
||||
'id': AGENT_IDS['browser'],
|
||||
'class': BROWSER,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Discord',
|
||||
'name': 'discord',
|
||||
'id': AGENT_IDS['discord'],
|
||||
'class': DISCORD,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Email',
|
||||
'name': 'email',
|
||||
'id': AGENT_IDS['email']
|
||||
'id': AGENT_IDS['email'],
|
||||
'class': EMAIL,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Facebook',
|
||||
'name': 'facebook',
|
||||
'id': AGENT_IDS['facebook']
|
||||
'id': AGENT_IDS['facebook'],
|
||||
'class': FACEBOOK,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'GroupMe',
|
||||
'name': 'groupme',
|
||||
'id': AGENT_IDS['groupme']
|
||||
'id': AGENT_IDS['groupme'],
|
||||
'class': GROUPME,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Growl',
|
||||
'name': 'growl',
|
||||
'id': AGENT_IDS['growl']
|
||||
'id': AGENT_IDS['growl'],
|
||||
'class': GROWL,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'IFTTT',
|
||||
'name': 'ifttt',
|
||||
'id': AGENT_IDS['ifttt']
|
||||
'id': AGENT_IDS['ifttt'],
|
||||
'class': IFTTT,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Join',
|
||||
'name': 'join',
|
||||
'id': AGENT_IDS['join']
|
||||
'id': AGENT_IDS['join'],
|
||||
'class': JOIN,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Kodi',
|
||||
'name': 'xbmc',
|
||||
'id': AGENT_IDS['xbmc']
|
||||
'id': AGENT_IDS['xbmc'],
|
||||
'class': XBMC,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'MQTT',
|
||||
'name': 'mqtt',
|
||||
'id': AGENT_IDS['mqtt']
|
||||
'id': AGENT_IDS['mqtt'],
|
||||
'class': MQTT,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Plex Home Theater',
|
||||
'name': 'plex',
|
||||
'id': AGENT_IDS['plex']
|
||||
'id': AGENT_IDS['plex'],
|
||||
'class': PLEX,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Plex Android / iOS App',
|
||||
'name': 'plexmobileapp',
|
||||
'id': AGENT_IDS['plexmobileapp'],
|
||||
'class': PLEXMOBILEAPP,
|
||||
'action_types': ('on_play', 'on_created', 'on_newdevice')
|
||||
},
|
||||
{'label': 'Prowl',
|
||||
'name': 'prowl',
|
||||
'id': AGENT_IDS['prowl']
|
||||
'id': AGENT_IDS['prowl'],
|
||||
'class': PROWL,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Pushbullet',
|
||||
'name': 'pushbullet',
|
||||
'id': AGENT_IDS['pushbullet']
|
||||
'id': AGENT_IDS['pushbullet'],
|
||||
'class': PUSHBULLET,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Pushover',
|
||||
'name': 'pushover',
|
||||
'id': AGENT_IDS['pushover']
|
||||
'id': AGENT_IDS['pushover'],
|
||||
'class': PUSHOVER,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Script',
|
||||
'name': 'scripts',
|
||||
'id': AGENT_IDS['scripts']
|
||||
'id': AGENT_IDS['scripts'],
|
||||
'class': SCRIPTS,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Slack',
|
||||
'name': 'slack',
|
||||
'id': AGENT_IDS['slack']
|
||||
'id': AGENT_IDS['slack'],
|
||||
'class': SLACK,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Telegram',
|
||||
'name': 'telegram',
|
||||
'id': AGENT_IDS['telegram']
|
||||
'id': AGENT_IDS['telegram'],
|
||||
'class': TELEGRAM,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Twitter',
|
||||
'name': 'twitter',
|
||||
'id': AGENT_IDS['twitter']
|
||||
'id': AGENT_IDS['twitter'],
|
||||
'class': TWITTER,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Webhook',
|
||||
'name': 'webhook',
|
||||
'id': AGENT_IDS['webhook']
|
||||
'id': AGENT_IDS['webhook'],
|
||||
'class': WEBHOOK,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Zapier',
|
||||
'name': 'zapier',
|
||||
'id': AGENT_IDS['zapier']
|
||||
'id': AGENT_IDS['zapier'],
|
||||
'class': ZAPIER,
|
||||
'action_types': ('all',)
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -205,13 +254,15 @@ def available_notification_agents():
|
|||
if OSX().validate():
|
||||
agents.append({'label': 'macOS Notification Center',
|
||||
'name': 'osx',
|
||||
'id': AGENT_IDS['osx']
|
||||
'id': AGENT_IDS['osx'],
|
||||
'class': OSX,
|
||||
'action_types': ('all',)
|
||||
})
|
||||
|
||||
return agents
|
||||
|
||||
|
||||
def available_notification_actions():
|
||||
def available_notification_actions(agent_id=None):
|
||||
actions = [{'label': 'Playback Start',
|
||||
'name': 'on_play',
|
||||
'description': 'Trigger a notification when a stream is started.',
|
||||
|
@ -312,7 +363,7 @@ def available_notification_actions():
|
|||
'name': 'on_extdown',
|
||||
'description': 'Trigger a notification when the Plex Media Server cannot be reached externally.',
|
||||
'subject': 'Tautulli ({server_name})',
|
||||
'body': 'The Plex Media Server remote access is down.',
|
||||
'body': 'The Plex Media Server remote access is down. ({remote_access_reason})',
|
||||
'icon': 'fa-server',
|
||||
'media_types': ('server',)
|
||||
},
|
||||
|
@ -350,72 +401,31 @@ def available_notification_actions():
|
|||
}
|
||||
]
|
||||
|
||||
if str(agent_id).isdigit():
|
||||
action_types = get_notify_agents(return_dict=True).get(int(agent_id), {}).get('action_types', [])
|
||||
if 'all' not in action_types:
|
||||
actions = [a for a in actions if a['name'] in action_types]
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
def get_agent_class(agent_id=None, config=None):
|
||||
if str(agent_id).isdigit():
|
||||
agent_id = int(agent_id)
|
||||
|
||||
if agent_id == 0:
|
||||
return GROWL(config=config)
|
||||
elif agent_id == 1:
|
||||
return PROWL(config=config)
|
||||
elif agent_id == 2:
|
||||
return XBMC(config=config)
|
||||
elif agent_id == 3:
|
||||
return PLEX(config=config)
|
||||
elif agent_id == 6:
|
||||
return PUSHBULLET(config=config)
|
||||
elif agent_id == 7:
|
||||
return PUSHOVER(config=config)
|
||||
elif agent_id == 8:
|
||||
return OSX(config=config)
|
||||
elif agent_id == 9:
|
||||
return BOXCAR(config=config)
|
||||
elif agent_id == 10:
|
||||
return EMAIL(config=config)
|
||||
elif agent_id == 11:
|
||||
return TWITTER(config=config)
|
||||
elif agent_id == 12:
|
||||
return IFTTT(config=config)
|
||||
elif agent_id == 13:
|
||||
return TELEGRAM(config=config)
|
||||
elif agent_id == 14:
|
||||
return SLACK(config=config)
|
||||
elif agent_id == 15:
|
||||
return SCRIPTS(config=config)
|
||||
elif agent_id == 16:
|
||||
return FACEBOOK(config=config)
|
||||
elif agent_id == 17:
|
||||
return BROWSER(config=config)
|
||||
elif agent_id == 18:
|
||||
return JOIN(config=config)
|
||||
elif agent_id == 20:
|
||||
return DISCORD(config=config)
|
||||
elif agent_id == 21:
|
||||
return ANDROIDAPP(config=config)
|
||||
elif agent_id == 22:
|
||||
return GROUPME(config=config)
|
||||
elif agent_id == 23:
|
||||
return MQTT(config=config)
|
||||
elif agent_id == 24:
|
||||
return ZAPIER(config=config)
|
||||
elif agent_id == 25:
|
||||
return WEBHOOK(config=config)
|
||||
else:
|
||||
return Notifier(config=config)
|
||||
agent = get_notify_agents(return_dict=True).get(int(agent_id), {}).get('class', Notifier)
|
||||
return agent(config=config)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_notify_agents():
|
||||
def get_notify_agents(return_dict=False):
|
||||
if return_dict:
|
||||
return {a['id']: a for a in available_notification_agents()}
|
||||
return tuple(a['name'] for a in sorted(available_notification_agents(), key=lambda k: k['label']))
|
||||
|
||||
|
||||
def get_notify_actions(return_dict=False):
|
||||
if return_dict:
|
||||
return {a.pop('name'): a for a in available_notification_actions()}
|
||||
return {a['name']: a for a in available_notification_actions()}
|
||||
return tuple(a['name'] for a in available_notification_actions())
|
||||
|
||||
|
||||
|
@ -523,7 +533,7 @@ def add_notifier_config(agent_id=None, **kwargs):
|
|||
% agent_id)
|
||||
return False
|
||||
|
||||
agent = next((a for a in available_notification_agents() if a['id'] == agent_id), None)
|
||||
agent = get_notify_agents(return_dict=True).get(agent_id, None)
|
||||
|
||||
if not agent:
|
||||
logger.error("Tautulli Notifiers :: Unable to retrieve new notification agent: invalid agent_id %s."
|
||||
|
@ -572,7 +582,7 @@ def set_notifier_config(notifier_id=None, agent_id=None, **kwargs):
|
|||
% agent_id)
|
||||
return False
|
||||
|
||||
agent = next((a for a in available_notification_agents() if a['id'] == agent_id), None)
|
||||
agent = get_notify_agents(return_dict=True).get(agent_id, None)
|
||||
|
||||
if not agent:
|
||||
logger.error("Tautulli Notifiers :: Unable to retrieve existing notification agent: invalid agent_id %s."
|
||||
|
@ -2368,6 +2378,190 @@ class PLEX(Notifier):
|
|||
return config_option
|
||||
|
||||
|
||||
class PLEXMOBILEAPP(Notifier):
|
||||
"""
|
||||
Plex Mobile App Notifications
|
||||
"""
|
||||
NAME = 'Plex Android / iOS App'
|
||||
NOTIFICATION_URL = 'https://notifications.plex.tv/api/v1/notifications'
|
||||
_DEFAULT_CONFIG = {'user_ids': [],
|
||||
'tap_action': 'preplay',
|
||||
}
|
||||
|
||||
def __init__(self, config=None):
|
||||
super(PLEXMOBILEAPP, self).__init__(config=config)
|
||||
|
||||
self.configurations = {
|
||||
'created': {'group': 'media', 'identifier': 'tv.plex.notification.library.new'},
|
||||
'play': {'group': 'media', 'identifier': 'tv.plex.notification.playback.started'},
|
||||
'newdevice': {'group': 'admin', 'identifier': 'tv.plex.notification.device.new'}
|
||||
}
|
||||
|
||||
def agent_notify(self, subject='', body='', action='', **kwargs):
|
||||
if action not in self.configurations and not action.startswith('test'):
|
||||
logger.error(u"Tautulli Notifiers :: Notification action %s not allowed for %s." % (action, self.NAME))
|
||||
return
|
||||
|
||||
if action == 'test':
|
||||
tests = []
|
||||
for configuration in self.configurations:
|
||||
tests.append(self.agent_notify(subject=subject, body=body, action='test_'+configuration))
|
||||
return all(tests)
|
||||
|
||||
configuration_action = action.split('test_')[-1]
|
||||
|
||||
# No subject to always show up regardless of client selected filters
|
||||
# icon can be info, warning, or error
|
||||
# play = true to start playing when tapping the notification
|
||||
# Send the minimal amount of data necessary through Plex servers
|
||||
data = {
|
||||
'group': self.configurations[configuration_action]['group'],
|
||||
'identifier': self.configurations[configuration_action]['identifier'],
|
||||
'to': self.config['user_ids'],
|
||||
'data': {
|
||||
'provider': {
|
||||
'identifier': plexpy.CONFIG.PMS_IDENTIFIER,
|
||||
'title': plexpy.CONFIG.PMS_NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pretty_metadata = PrettyMetadata(kwargs.get('parameters'))
|
||||
|
||||
if action.startswith('test'):
|
||||
data['data']['player'] = {
|
||||
'title': 'Device',
|
||||
'platform': 'Platform',
|
||||
'machineIdentifier': 'Tautulli'
|
||||
}
|
||||
data['data']['user'] = {
|
||||
'title': 'User',
|
||||
'id': 0
|
||||
}
|
||||
data['metadata'] = {
|
||||
'type': 'movie',
|
||||
'title': subject,
|
||||
'year': body
|
||||
}
|
||||
|
||||
elif action in ('play', 'newdevice'):
|
||||
data['data']['player'] = {
|
||||
'title': pretty_metadata.parameters['player'],
|
||||
'platform': pretty_metadata.parameters['platform'],
|
||||
'machineIdentifier': pretty_metadata.parameters['machine_id']
|
||||
}
|
||||
data['data']['user'] = {
|
||||
'title': pretty_metadata.parameters['user'],
|
||||
'id': pretty_metadata.parameters['user_id'],
|
||||
'thumb': pretty_metadata.parameters['user_thumb'],
|
||||
}
|
||||
|
||||
elif action == 'created':
|
||||
# No addition data required for recently added
|
||||
pass
|
||||
|
||||
else:
|
||||
logger.error(u"Tautulli Notifiers :: Notification action %s not supported for %s." % (action, self.NAME))
|
||||
return
|
||||
|
||||
if data['group'] == 'media' and not action.startswith('test'):
|
||||
media_type = pretty_metadata.media_type
|
||||
uri_rating_key = None
|
||||
|
||||
if media_type == 'movie':
|
||||
metadata = {
|
||||
'type': media_type,
|
||||
'title': pretty_metadata.parameters['title'],
|
||||
'year': pretty_metadata.parameters['year'],
|
||||
'thumb': pretty_metadata.parameters['thumb']
|
||||
}
|
||||
elif media_type == 'show':
|
||||
metadata = {
|
||||
'type': media_type,
|
||||
'title': pretty_metadata.parameters['show_name'],
|
||||
'thumb': pretty_metadata.parameters['thumb']
|
||||
}
|
||||
elif media_type == 'season':
|
||||
metadata = {
|
||||
'type': 'show',
|
||||
'title': pretty_metadata.parameters['show_name'],
|
||||
'thumb': pretty_metadata.parameters['thumb'],
|
||||
}
|
||||
data['data']['count'] = pretty_metadata.parameters['episode_count']
|
||||
elif media_type == 'episode':
|
||||
metadata = {
|
||||
'type': media_type,
|
||||
'title': pretty_metadata.parameters['episode_name'],
|
||||
'grandparentTitle': pretty_metadata.parameters['show_name'],
|
||||
'index': pretty_metadata.parameters['episode_num'],
|
||||
'parentIndex': pretty_metadata.parameters['season_num'],
|
||||
'grandparentThumb': pretty_metadata.parameters['grandparent_thumb']
|
||||
}
|
||||
elif media_type == 'artist':
|
||||
metadata = {
|
||||
'type': media_type,
|
||||
'title': pretty_metadata.parameters['artist_name'],
|
||||
'thumb': pretty_metadata.parameters['thumb']
|
||||
}
|
||||
elif media_type == 'album':
|
||||
metadata = {
|
||||
'type': media_type,
|
||||
'title': pretty_metadata.parameters['album_name'],
|
||||
'year': pretty_metadata.parameters['year'],
|
||||
'parentTitle': pretty_metadata.parameters['artist_name'],
|
||||
'thumb': pretty_metadata.parameters['thumb'],
|
||||
}
|
||||
elif media_type == 'track':
|
||||
metadata = {
|
||||
'type': 'album',
|
||||
'title': pretty_metadata.parameters['album_name'],
|
||||
'year': pretty_metadata.parameters['year'],
|
||||
'parentTitle': pretty_metadata.parameters['artist_name'],
|
||||
'thumb': pretty_metadata.parameters['parent_thumb']
|
||||
}
|
||||
uri_rating_key = pretty_metadata.parameters['parent_rating_key']
|
||||
else:
|
||||
logger.error(u"Tautulli Notifiers :: Media type %s not supported for %s." % (media_type, self.NAME))
|
||||
return
|
||||
|
||||
data['metadata'] = metadata
|
||||
data['uri'] = 'server://{}/com.plexapp.plugins.library/library/metadata/{}'.format(
|
||||
plexpy.CONFIG.PMS_IDENTIFIER, uri_rating_key or pretty_metadata.parameters['rating_key']
|
||||
)
|
||||
data['play'] = self.config['tap_action'] == 'play'
|
||||
|
||||
headers = {'X-Plex-Token': plexpy.CONFIG.PMS_TOKEN}
|
||||
|
||||
return self.make_request(self.NOTIFICATION_URL, headers=headers, json=data)
|
||||
|
||||
def get_users(self):
|
||||
user_ids = {u['user_id']: u['friendly_name'] for u in users.Users().get_users() if u['user_id']}
|
||||
user_ids[''] = ''
|
||||
return user_ids
|
||||
|
||||
def _return_config_options(self):
|
||||
config_option = [{'label': 'Plex User(s)',
|
||||
'value': self.config['user_ids'],
|
||||
'name': 'plexmobileapp_user_ids',
|
||||
'description': 'Select which Plex User(s) to receive notifications.<br>'
|
||||
'Note: The user(s) must have notifications enabled '
|
||||
'for the matching Tautulli triggers in their Plex mobile app.',
|
||||
'input_type': 'select',
|
||||
'select_options': self.get_users()
|
||||
},
|
||||
{'label': 'Notification Tap Action',
|
||||
'value': self.config['tap_action'],
|
||||
'name': 'plexmobileapp_tap_action',
|
||||
'description': 'Set the action when tapping on the notification.',
|
||||
'input_type': 'select',
|
||||
'select_options': {'preplay': 'Go to media pre-play screen',
|
||||
'play': 'Start playing the media'}
|
||||
},
|
||||
]
|
||||
|
||||
return config_option
|
||||
|
||||
|
||||
class PROWL(Notifier):
|
||||
"""
|
||||
Prowl notifications.
|
||||
|
|
|
@ -390,6 +390,14 @@ class PlexTV(object):
|
|||
|
||||
return request
|
||||
|
||||
def get_plextv_geoip(self, ip_address='', output_format=''):
|
||||
uri = '/api/v2/geoip?ip_address=%s' % ip_address
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
request_type='GET',
|
||||
output_format=output_format)
|
||||
|
||||
return request
|
||||
|
||||
def get_full_users_list(self):
|
||||
own_account = self.get_plextv_user_details(output_format='xml')
|
||||
friends_list = self.get_plextv_friends(output_format='xml')
|
||||
|
@ -936,3 +944,35 @@ class PlexTV(object):
|
|||
"user_token": helpers.get_xml_attr(a, 'authToken')
|
||||
}
|
||||
return account_details
|
||||
|
||||
def get_geoip_lookup(self, ip_address=''):
|
||||
if not ip_address or not helpers.is_public_ip(ip_address):
|
||||
return
|
||||
|
||||
geoip_data = self.get_plextv_geoip(ip_address=ip_address, output_format='xml')
|
||||
|
||||
try:
|
||||
xml_head = geoip_data.getElementsByTagName('location')
|
||||
except Exception as e:
|
||||
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_geoip_lookup: %s." % e)
|
||||
return None
|
||||
|
||||
for a in xml_head:
|
||||
coordinates = helpers.get_xml_attr(a, 'coordinates').split(',')
|
||||
latitude = longitude = None
|
||||
if len(coordinates) == 2:
|
||||
latitude, longitude = [helpers.cast_to_float(c) for c in coordinates]
|
||||
|
||||
geo_info = {"code": helpers.get_xml_attr(a, 'code') or None,
|
||||
"country": helpers.get_xml_attr(a, 'country') or None,
|
||||
"region": helpers.get_xml_attr(a, 'subdivisions') or None,
|
||||
"city": helpers.get_xml_attr(a, 'city') or None,
|
||||
"postal_code": helpers.get_xml_attr(a, 'postal_code') or None,
|
||||
"timezone": helpers.get_xml_attr(a, 'time_zone') or None,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"continent": None, # keep for backwards compatibility with GeoLite2
|
||||
"accuracy": None # keep for backwards compatibility with GeoLite2
|
||||
}
|
||||
|
||||
return geo_info
|
||||
|
|
|
@ -2980,10 +2980,26 @@ class PmsConnect(object):
|
|||
for a in xml_head:
|
||||
server_response = {'mapping_state': helpers.get_xml_attr(a, 'mappingState'),
|
||||
'mapping_error': helpers.get_xml_attr(a, 'mappingError'),
|
||||
'sign_in_state': helpers.get_xml_attr(a, 'signInState'),
|
||||
'public_address': helpers.get_xml_attr(a, 'publicAddress'),
|
||||
'public_port': helpers.get_xml_attr(a, 'publicPort')
|
||||
'public_port': helpers.get_xml_attr(a, 'publicPort'),
|
||||
'private_address': helpers.get_xml_attr(a, 'privateAddress'),
|
||||
'private_port': helpers.get_xml_attr(a, 'privatePort')
|
||||
}
|
||||
|
||||
if server_response['mapping_state'] == 'unknown':
|
||||
server_response['reason'] = 'Plex remote access port mapping unknown'
|
||||
elif server_response['mapping_state'] not in ('mapped', 'waiting'):
|
||||
server_response['reason'] = 'Plex remote access port not mapped'
|
||||
elif server_response['mapping_error'] == 'unreachable':
|
||||
server_response['reason'] = 'Plex remote access port mapped, ' \
|
||||
'but the port is unreachable from Plex.tv'
|
||||
elif server_response['mapping_error'] == 'publisherror':
|
||||
server_response['reason'] = 'Plex remote access port mapped, ' \
|
||||
'but failed to publish the port to Plex.tv'
|
||||
else:
|
||||
server_response['reason'] = ''
|
||||
|
||||
return server_response
|
||||
|
||||
def get_update_staus(self):
|
||||
|
|
|
@ -21,7 +21,6 @@ from future.builtins import str
|
|||
from future.builtins import object
|
||||
|
||||
import httpagentparser
|
||||
import time
|
||||
|
||||
import plexpy
|
||||
if plexpy.PYTHON2:
|
||||
|
|
|
@ -18,4 +18,4 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
PLEXPY_BRANCH = "python3"
|
||||
PLEXPY_RELEASE_VERSION = "v2.2.2-beta"
|
||||
PLEXPY_RELEASE_VERSION = "v2.2.3-beta"
|
||||
|
|
|
@ -147,8 +147,8 @@ def getVersion():
|
|||
return current_version, 'origin', current_branch
|
||||
|
||||
|
||||
def check_update(auto_update=False, notify=False):
|
||||
check_github(auto_update=auto_update, notify=notify)
|
||||
def check_update(scheduler=False, notify=False):
|
||||
check_github(scheduler=scheduler, notify=notify)
|
||||
|
||||
if not plexpy.CURRENT_VERSION:
|
||||
plexpy.UPDATE_AVAILABLE = None
|
||||
|
@ -171,7 +171,7 @@ def check_update(auto_update=False, notify=False):
|
|||
plexpy.WIN_SYS_TRAY_ICON.update(icon=icon, hover_text=hover_text)
|
||||
|
||||
|
||||
def check_github(auto_update=False, notify=False):
|
||||
def check_github(scheduler=False, notify=False):
|
||||
plexpy.COMMITS_BEHIND = 0
|
||||
|
||||
if plexpy.CONFIG.GIT_TOKEN:
|
||||
|
@ -248,7 +248,7 @@ def check_github(auto_update=False, notify=False):
|
|||
'plexpy_update_commit': plexpy.LATEST_VERSION,
|
||||
'plexpy_update_behind': plexpy.COMMITS_BEHIND})
|
||||
|
||||
if auto_update and not plexpy.DOCKER:
|
||||
if scheduler and plexpy.CONFIG.PLEXPY_AUTO_UPDATE and not plexpy.DOCKER:
|
||||
logger.info('Running automatic update.')
|
||||
plexpy.shutdown(restart=True, update=True)
|
||||
|
||||
|
|
|
@ -1936,6 +1936,10 @@ class WebInterface(object):
|
|||
}
|
||||
```
|
||||
"""
|
||||
# For backwards compatibility
|
||||
if 'id' in kwargs:
|
||||
row_id = kwargs['id']
|
||||
|
||||
data_factory = datafactory.DataFactory()
|
||||
stream_data = data_factory.get_stream_details(row_id, session_key)
|
||||
|
||||
|
@ -2993,6 +2997,7 @@ class WebInterface(object):
|
|||
"notify_recently_added_delay": plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY,
|
||||
"notify_concurrent_by_ip": checked(plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP),
|
||||
"notify_concurrent_threshold": plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD,
|
||||
"notify_continued_session_threshold": plexpy.CONFIG.NOTIFY_CONTINUED_SESSION_THRESHOLD,
|
||||
"home_sections": json.dumps(plexpy.CONFIG.HOME_SECTIONS),
|
||||
"home_stats_cards": json.dumps(plexpy.CONFIG.HOME_STATS_CARDS),
|
||||
"home_library_cards": json.dumps(plexpy.CONFIG.HOME_LIBRARY_CARDS),
|
||||
|
@ -3024,11 +3029,7 @@ class WebInterface(object):
|
|||
"newsletter_password": plexpy.CONFIG.NEWSLETTER_PASSWORD,
|
||||
"newsletter_inline_styles": checked(plexpy.CONFIG.NEWSLETTER_INLINE_STYLES),
|
||||
"newsletter_custom_dir": plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR,
|
||||
"win_sys_tray": checked(plexpy.CONFIG.WIN_SYS_TRAY),
|
||||
"maxmind_license_key": plexpy.CONFIG.MAXMIND_LICENSE_KEY,
|
||||
"geoip_db": plexpy.CONFIG.GEOIP_DB,
|
||||
"geoip_db_installed": plexpy.CONFIG.GEOIP_DB_INSTALLED,
|
||||
"geoip_db_update_days": plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS
|
||||
"win_sys_tray": checked(plexpy.CONFIG.WIN_SYS_TRAY)
|
||||
}
|
||||
|
||||
return serve_template(templatename="settings.html", title="Settings", config=config, kwargs=kwargs)
|
||||
|
@ -3260,36 +3261,6 @@ class WebInterface(object):
|
|||
else:
|
||||
return {'result': 'error', 'message': 'Database backup failed.'}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def install_geoip_db(self, update=False, **kwargs):
|
||||
""" Downloads and installs the GeoLite2 database """
|
||||
|
||||
update = helpers.bool_true(update)
|
||||
|
||||
result = helpers.install_geoip_db(update=update)
|
||||
|
||||
if result:
|
||||
return {'result': 'success', 'message': 'GeoLite2 database installed successful.', 'updated': result}
|
||||
else:
|
||||
return {'result': 'error', 'message': 'GeoLite2 database install failed.', 'updated': 0}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def uninstall_geoip_db(self, **kwargs):
|
||||
""" Uninstalls the GeoLite2 database """
|
||||
|
||||
result = helpers.uninstall_geoip_db()
|
||||
|
||||
if result:
|
||||
return {'result': 'success', 'message': 'GeoLite2 database uninstalled successfully.'}
|
||||
else:
|
||||
return {'result': 'error', 'message': 'GeoLite2 database uninstall failed.'}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
|
@ -5807,7 +5778,7 @@ class WebInterface(object):
|
|||
@requireAuth()
|
||||
@addtoapi()
|
||||
def get_geoip_lookup(self, ip_address='', **kwargs):
|
||||
""" Get the geolocation info for an IP address. The GeoLite2 database must be installed.
|
||||
""" Get the geolocation info for an IP address.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
|
@ -5818,7 +5789,7 @@ class WebInterface(object):
|
|||
|
||||
Returns:
|
||||
json:
|
||||
{"continent": "North America",
|
||||
{"code": 'US",
|
||||
"country": "United States",
|
||||
"region": "California",
|
||||
"city": "Mountain View",
|
||||
|
@ -5828,15 +5799,24 @@ class WebInterface(object):
|
|||
"longitude": -122.0838,
|
||||
"accuracy": 1000
|
||||
}
|
||||
json:
|
||||
{"error": "The address 127.0.0.1 is not in the database."
|
||||
}
|
||||
```
|
||||
"""
|
||||
geo_info = helpers.geoip_lookup(ip_address)
|
||||
if isinstance(geo_info, str):
|
||||
return {'error': geo_info}
|
||||
return geo_info
|
||||
message = ''
|
||||
if not ip_address:
|
||||
message = 'No IP address provided.'
|
||||
elif not helpers.is_valid_ip(ip_address):
|
||||
message = 'Invalid IP address provided: %s' % ip_address
|
||||
elif not helpers.is_public_ip(ip_address):
|
||||
message = 'Non-public IP address provided: %s' % ip_address
|
||||
|
||||
if message:
|
||||
return {'result': 'error', 'message': message}
|
||||
|
||||
plex_tv = plextv.PlexTV()
|
||||
geo_info = plex_tv.get_geoip_lookup(ip_address)
|
||||
if geo_info:
|
||||
return {'result': 'success', 'data': geo_info}
|
||||
return {'result': 'error', 'message': 'Failed to lookup GeoIP info for address: %s' % ip_address}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue