diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d928908..474c778d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## v1.3.5 (2016-02-02)
+
+* Fix: Removing unique constraints from database.
+* Fix: Unable to expand media info table when missing "Added At" date.
+* Fix: Server verification for unpublished servers.
+* Fix: Updating PMS identifier for server change.
+* Add: {stream_time}, {remaining_time}, and {progress_time} to notification options.
+* Add: Powershell script support. (Thanks @Hellowlol)
+* Add: Method to delete duplicate libraries.
+* Change: Daemonize before running start up tasks.
+
+
## v1.3.4 (2016-01-29)
* Fix: Activity checker not starting with library update (history not logging).
@@ -8,10 +20,10 @@
* Fix: Libraries and Users lists not refreshing.
* Fix: Server verification in settings.
* Fix: Empty libraries not added to database.
-* Add: Unique identifier to notification options.
-* Remove: Media type toggles for recently added notifications.
+* Add: Unique identifiers to notification options.
+* Remove: Requirement of media type toggles for recently added notifications.
* Remove: Built in Twitter key and secret.
-* Remove: Unnecessary quoting of script arguments.
+* Change: Unnecessary quoting of script arguments.
* Change: Facebook notification instructions.
diff --git a/PlexPy.py b/PlexPy.py
index cafb7804..15e3212e 100755
--- a/PlexPy.py
+++ b/PlexPy.py
@@ -153,12 +153,12 @@ def main():
# Put the database in the DATA_DIR
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db')
- # Read config and start logging
- plexpy.initialize(config_file)
-
if plexpy.DAEMON:
plexpy.daemonize()
+ # Read config and start logging
+ plexpy.initialize(config_file)
+
# Force the http port if neccessary
if args.port:
http_port = args.port
diff --git a/data/interfaces/default/js/tables/media_info_table.js b/data/interfaces/default/js/tables/media_info_table.js
index d9d745cc..87615398 100644
--- a/data/interfaces/default/js/tables/media_info_table.js
+++ b/data/interfaces/default/js/tables/media_info_table.js
@@ -34,9 +34,12 @@ media_info_table_options = {
"targets": [0],
"data": "added_at",
"createdCell": function (td, cellData, rowData, row, col) {
- if (cellData !== null && cellData !== '') {
+ if (rowData) {
var expand_details = '';
- var date = moment(cellData, "X").format(date_format);
+ var date = '';
+ if (cellData !== null && cellData !== '') {
+ date = moment(cellData, "X").format(date_format);
+ }
if (rowData['media_type'] === 'show') {
expand_details = '';
$(td).html('
');
diff --git a/data/interfaces/default/notification_config.html b/data/interfaces/default/notification_config.html
index 6ae4a0c6..d071ce77 100644
--- a/data/interfaces/default/notification_config.html
+++ b/data/interfaces/default/notification_config.html
@@ -181,6 +181,10 @@ from plexpy import helpers
});
$('#facebookStep1').click(function () {
+ // Remove trailing '/' from Facebook redirect URI
+ if ($('#facebook_redirect_uri') && $('#facebook_redirect_uri').val().endsWith('/')) {
+ $('#facebook_redirect_uri').val($('#facebook_redirect_uri').val().slice(0, -1));
+ }
doAjaxCall('set_notification_config', $(this), 'tabs', true);
$.get('facebookStep1', function (data) { window.open(data); })
.done(function () { showMsg(' Confirm Authorization. Check pop-up blocker if no response.', false, true, 3000); });
diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html
index f05d4ba7..bbcec5f6 100644
--- a/data/interfaces/default/settings.html
+++ b/data/interfaces/default/settings.html
@@ -1093,11 +1093,11 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
{datestamp} |
- The date the notification was triggered. |
+ The date (in date format) the notification was triggered. |
{timestamp} |
- The time the notification was triggered. |
+ The time (in time format) the notification was triggered. |
@@ -1134,14 +1134,26 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
{stream_duration} |
The stream duration (in minutes) for the item. |
+
+ {stream_time} |
+ The stream duration (in time format) for the item. |
+
{remaining_duration} |
The remaining duration (in minutes) for the item. |
- {progress} |
+ {remaining_time} |
+ The remaining duration (in time format) for the item. |
+
+
+ {progress_duration} |
The last reported offset (in minutes) for the item. |
+
+ {progress_time} |
+ The last reported offset (in time format) for the item. |
+
{progress_percent} |
The last reported progress percent for the item. |
@@ -1222,6 +1234,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
{transcode_audio_channels} |
The audio channels of the transcoded media. |
+
+ {session_key} |
+ The unique identifier for the session. |
+
{user_id} |
The unique identifier for the user. |
@@ -1569,11 +1585,11 @@ $(document).ready(function() {
});
function verifyServer(_callback) {
- var pms_ip = $("#pms_ip").val()
- var pms_port = $("#pms_port").val()
- var pms_identifier = $("#pms_identifier").val()
- var pms_ssl = $("#pms_ssl").val()
- var pms_is_remote = $("#pms_is_remote").val()
+ var pms_ip = $("#pms_ip").val();
+ var pms_port = $("#pms_port").val();
+ var pms_identifier = $("#pms_identifier").val();
+ var pms_ssl = $("#pms_ssl").is(':checked') ? 1 : 0;
+ var pms_is_remote = $("#pms_is_remote").is(':checked') ? 1 : 0;
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
$("#pms-verify").html('');
$('#pms-verify').fadeIn('fast');
@@ -1582,15 +1598,16 @@ $(document).ready(function() {
data : { hostname: pms_ip, port: pms_port, identifier: pms_identifier, ssl: pms_ssl, remote: pms_is_remote },
cache: true,
async: true,
- timeout: 5000,
+ timeout: 10000,
error: function(jqXHR, textStatus, errorThrown) {
$("#pms-verify").html('');
$('#pms-verify').fadeIn('fast');
$("#pms-ip-group").addClass("has-error");
},
- success: function (xml) {
- if ($(xml).find('MediaContainer').attr('machineIdentifier')) {
- $("#pms_identifier").val($(xml).find('MediaContainer').attr('machineIdentifier'));
+ success: function (json) {
+ var machine_identifier = json;
+ if (machine_identifier) {
+ $("#pms_identifier").val(machine_identifier);
$("#pms-verify").html('');
$('#pms-verify').fadeIn('fast');
$("#pms-ip-group").removeClass("has-error");
diff --git a/data/interfaces/default/welcome.html b/data/interfaces/default/welcome.html
index a717ad0e..5174ade1 100644
--- a/data/interfaces/default/welcome.html
+++ b/data/interfaces/default/welcome.html
@@ -393,9 +393,10 @@ from plexpy import common
$("#pms-verify-status").html(' This is not a Plex Server!');
$('#pms-verify-status').fadeIn('fast');
},
- success: function (xml) {
- if ($(xml).find('MediaContainer').attr('machineIdentifier')) {
- $("#pms_identifier").val($(xml).find('MediaContainer').attr('machineIdentifier'));
+ success: function (json) {
+ var machine_identifier = json;
+ if (machine_identifier) {
+ $("#pms_identifier").val(machine_identifier);
$("#pms-verify-status").html(' Server found!');
$('#pms-verify-status').fadeIn('fast');
pms_verified = true;
diff --git a/plexpy/__init__.py b/plexpy/__init__.py
index 6827df72..806833b6 100644
--- a/plexpy/__init__.py
+++ b/plexpy/__init__.py
@@ -713,8 +713,8 @@ def dbcheck():
# Upgrade library_sections table from earlier versions (remove UNIQUE constraint on section_id)
try:
- result = c_db.execute('PRAGMA index_xinfo("sqlite_autoindex_library_sections_1")')
- if result and 'server_id' not in [row[2] for row in result]:
+ result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="library_sections"').fetchone()
+ if 'section_id INTEGER UNIQUE' in result[0]:
logger.debug(u"Altering database. Removing unique constraint on section_id from library_sections table.")
c_db.execute(
'CREATE TABLE library_sections_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, '
@@ -760,8 +760,8 @@ def dbcheck():
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
try:
- result = c_db.execute('PRAGMA index_xinfo("sqlite_autoindex_users_2")')
- if result and 'username' in [row[2] for row in result]:
+ result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="users"').fetchone()
+ if 'username TEXT NOT NULL UNIQUE' in result[0]:
logger.debug(u"Altering database. Removing unique constraint on username from users table.")
c_db.execute(
'CREATE TABLE users_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, '
diff --git a/plexpy/config.py b/plexpy/config.py
index 9cc23068..45eb1c79 100644
--- a/plexpy/config.py
+++ b/plexpy/config.py
@@ -512,6 +512,7 @@ class Config(object):
self.MOVIE_LOGGING_ENABLE = 0
self.TV_LOGGING_ENABLE = 0
self.CONFIG_VERSION = '1'
+
if self.CONFIG_VERSION == '1':
# Change home_stats_cards to list
if self.HOME_STATS_CARDS:
@@ -525,4 +526,20 @@ class Config(object):
if 'library_statistics' in home_library_cards:
home_library_cards.remove('library_statistics')
self.HOME_LIBRARY_CARDS = home_library_cards
- self.CONFIG_VERSION = '2'
\ No newline at end of file
+ self.CONFIG_VERSION = '2'
+
+ if self.CONFIG_VERSION == '2':
+ self.NOTIFY_ON_START_SUBJECT_TEXT = self.NOTIFY_ON_START_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_START_BODY_TEXT = self.NOTIFY_ON_START_BODY_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_STOP_SUBJECT_TEXT = self.NOTIFY_ON_STOP_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_STOP_BODY_TEXT = self.NOTIFY_ON_STOP_BODY_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_PAUSE_SUBJECT_TEXT = self.NOTIFY_ON_PAUSE_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_PAUSE_BODY_TEXT = self.NOTIFY_ON_PAUSE_BODY_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_RESUME_SUBJECT_TEXT = self.NOTIFY_ON_RESUME_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_RESUME_BODY_TEXT = self.NOTIFY_ON_RESUME_BODY_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_BUFFER_SUBJECT_TEXT = self.NOTIFY_ON_BUFFER_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_BUFFER_BODY_TEXT = self.NOTIFY_ON_BUFFER_BODY_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_WATCHED_SUBJECT_TEXT = self.NOTIFY_ON_WATCHED_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_ON_WATCHED_BODY_TEXT = self.NOTIFY_ON_WATCHED_BODY_TEXT.replace('{progress}','{progress_duration}')
+ self.NOTIFY_SCRIPTS_ARGS_TEXT = self.NOTIFY_SCRIPTS_ARGS_TEXT.replace('{progress}','{progress_duration}')
+ self.CONFIG_VERSION = '3'
diff --git a/plexpy/helpers.py b/plexpy/helpers.py
index cb11e87d..a5d9e2d4 100644
--- a/plexpy/helpers.py
+++ b/plexpy/helpers.py
@@ -135,6 +135,15 @@ def convert_seconds(s):
return minutes
+def convert_seconds_to_minutes(s):
+
+ if str(s).isdigit():
+ minutes = round(float(s) / 60, 0)
+
+ return math.trunc(minutes)
+
+ return 0
+
def today():
today = datetime.date.today()
diff --git a/plexpy/http_handler.py b/plexpy/http_handler.py
index c85d2958..013512ba 100644
--- a/plexpy/http_handler.py
+++ b/plexpy/http_handler.py
@@ -44,7 +44,8 @@ class HTTPHandler(object):
headers=None,
output_format='raw',
return_type=False,
- no_token=False):
+ no_token=False,
+ timeout=20):
valid_request_types = ['GET', 'POST', 'PUT', 'DELETE']
@@ -56,12 +57,12 @@ class HTTPHandler(object):
if proto.upper() == 'HTTPS':
if not self.ssl_verify and hasattr(ssl, '_create_unverified_context'):
context = ssl._create_unverified_context()
- handler = HTTPSConnection(host=self.host, port=self.port, timeout=20, context=context)
+ handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout, context=context)
logger.warn(u"PlexPy HTTP Handler :: Unverified HTTPS request made. This connection is not secure.")
else:
- handler = HTTPSConnection(host=self.host, port=self.port, timeout=20)
+ handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout)
else:
- handler = HTTPConnection(host=self.host, port=self.port, timeout=20)
+ handler = HTTPConnection(host=self.host, port=self.port, timeout=timeout)
token_string = ''
if not no_token:
diff --git a/plexpy/libraries.py b/plexpy/libraries.py
index b764077b..cbe2e33e 100644
--- a/plexpy/libraries.py
+++ b/plexpy/libraries.py
@@ -879,3 +879,21 @@ class Libraries(object):
return 'Unable to delete media info table cache, section_id not valid.'
except Exception as e:
logger.warn(u"PlexPy Libraries :: Unable to delete media info table cache: %s." % e)
+
+ def delete_duplicate_libraries(self):
+ from plexpy import plextv
+
+ monitor_db = database.MonitorDatabase()
+
+ # Refresh the PMS_URL to make sure the server_id is updated
+ plextv.get_real_pms_url()
+
+ server_id = plexpy.CONFIG.PMS_IDENTIFIER
+
+ try:
+ logger.debug(u"PlexPy Libraries :: Deleting libraries where server_id does not match %s." % server_id)
+ monitor_db.action('DELETE FROM library_sections WHERE server_id != ?', [server_id])
+
+ return 'Deleted duplicate libraries from the database.'
+ except Exception as e:
+ logger.warn(u"PlexPy Libraries :: Unable to delete duplicate libraries: %s." % e)
\ No newline at end of file
diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py
index 2e400ca8..6962c298 100644
--- a/plexpy/notification_handler.py
+++ b/plexpy/notification_handler.py
@@ -340,6 +340,11 @@ def set_notify_state(session, state, agent_info):
def build_notify_text(session=None, timeline=None, state=None):
+ # Get time formats
+ date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
+ time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
+ duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','')
+
# Get the server name
server_name = plexpy.CONFIG.PMS_NAME
@@ -428,80 +433,30 @@ def build_notify_text(session=None, timeline=None, state=None):
else:
full_title = metadata['title']
- duration = helpers.convert_milliseconds_to_minutes(metadata['duration'])
-
- # Default values
- user = ''
- platform = ''
- player = ''
- ip_address = 'N/A'
- stream_duration = 0
- view_offset = 0
- container = ''
- video_codec = ''
- video_bitrate = ''
- video_width = ''
- video_height = ''
- video_resolution = ''
- video_framerate = ''
- aspect_ratio = ''
- audio_codec = ''
- audio_channels = ''
- transcode_decision = ''
- video_decision = ''
- audio_decision = ''
- transcode_container = ''
- transcode_video_codec = ''
- transcode_video_width = ''
- transcode_video_height = ''
- transcode_audio_codec = ''
- transcode_audio_channels = ''
- user_id = ''
-
# Session values
- if session:
- # Generate a combined transcode decision value
- video_decision = session['video_decision'].title()
- audio_decision = session['audio_decision'].title()
+ if session is None:
+ session = {}
- if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode':
- transcode_decision = 'Transcode'
- elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy':
- transcode_decision = 'Direct Stream'
- else:
- transcode_decision = 'Direct Play'
-
- if state != 'play':
- if session['paused_counter']:
- stream_duration = int((time.time() - helpers.cast_to_float(session['started']) -
- helpers.cast_to_float(session['paused_counter'])) / 60)
- else:
- stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60)
-
- view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset'])
- user = session['friendly_name']
- platform = session['platform']
- player = session['player']
- ip_address = session['ip_address'] if session['ip_address'] else 'N/A'
- container = session['container']
- video_codec = session['video_codec']
- video_bitrate = session['bitrate']
- video_width = session['width']
- video_height = session['height']
- video_resolution = session['video_resolution']
- video_framerate = session['video_framerate']
- aspect_ratio = session['aspect_ratio']
- audio_codec = session['audio_codec']
- audio_channels = session['audio_channels']
- transcode_container = session['transcode_container']
- transcode_video_codec = session['transcode_video_codec']
- transcode_video_width = session['transcode_width']
- transcode_video_height = session['transcode_height']
- transcode_audio_codec = session['transcode_audio_codec']
- transcode_audio_channels = session['transcode_audio_channels']
- user_id = session['user_id']
+ # Generate a combined transcode decision value
+ if session.get('video_decision','') == 'transcode' or session.get('audio_decision','') == 'transcode':
+ transcode_decision = 'Transcode'
+ elif session.get('video_decision','') == 'copy' or session.get('audio_decision','') == 'copy':
+ transcode_decision = 'Direct Stream'
+ else:
+ transcode_decision = 'Direct Play'
+
+ if state != 'play':
+ stream_duration = helpers.convert_seconds_to_minutes(
+ time.time() -
+ helpers.cast_to_float(session.get('started', 0)) -
+ helpers.cast_to_float(session.get('paused_counter', 0)))
+ else:
+ stream_duration = 0
+ view_offset = helpers.convert_milliseconds_to_minutes(session.get('view_offset', 0))
+ duration = helpers.convert_milliseconds_to_minutes(metadata['duration'])
progress_percent = helpers.get_percent(view_offset, duration)
+ remaining_duration = duration - view_offset
# Fix metadata params for notify recently added grandparent
if state == 'created' and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
@@ -517,41 +472,48 @@ def build_notify_text(session=None, timeline=None, state=None):
album_name = metadata['parent_title']
track_name = metadata['title']
- available_params = {'server_name': server_name,
+ available_params = {# Global paramaters
+ 'server_name': server_name,
'server_uptime': server_uptime,
- 'action': state,
- 'datestamp': arrow.now().format(plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')),
- 'timestamp': arrow.now().format(plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')),
+ 'action': state.title(),
+ 'datestamp': arrow.now().format(date_format),
+ 'timestamp': arrow.now().format(time_format),
+ # Stream parameters
'streams': stream_count,
- 'user': user,
- 'platform': platform,
- 'player': player,
- 'ip_address': ip_address,
- 'media_type': metadata['media_type'],
+ 'user': session.get('friendly_name',''),
+ 'platform': session.get('platform',''),
+ 'player': session.get('player',''),
+ 'ip_address': session.get('ip_address','N/A'),
'stream_duration': stream_duration,
- 'remaining_duration': duration - view_offset,
- 'progress': view_offset,
+ 'stream_time': arrow.get(stream_duration * 60).format(duration_format),
+ 'remaining_duration': remaining_duration,
+ 'remaining_time': arrow.get(remaining_duration * 60).format(duration_format),
+ 'progress_duration': view_offset,
+ 'progress_time': arrow.get(view_offset * 60).format(duration_format),
'progress_percent': progress_percent,
- 'container': container,
- 'video_codec': video_codec,
- 'video_bitrate': video_bitrate,
- 'video_width': video_width,
- 'video_height': video_height,
- 'video_resolution': video_resolution,
- 'video_framerate': video_framerate,
- 'aspect_ratio': aspect_ratio,
- 'audio_codec': audio_codec,
- 'audio_channels': audio_channels,
+ 'container': session.get('container',''),
+ 'video_codec': session.get('video_codec',''),
+ 'video_bitrate': session.get('bitrate',''),
+ 'video_width': session.get('width',''),
+ 'video_height': session.get('height',''),
+ 'video_resolution': session.get('video_resolution',''),
+ 'video_framerate': session.get('video_framerate',''),
+ 'aspect_ratio': session.get('aspect_ratio',''),
+ 'audio_codec': session.get('audio_codec',''),
+ 'audio_channels': session.get('audio_channels',''),
'transcode_decision': transcode_decision,
- 'video_decision': video_decision,
- 'audio_decision': audio_decision,
- 'transcode_container': transcode_container,
- 'transcode_video_codec': transcode_video_codec,
- 'transcode_video_width': transcode_video_width,
- 'transcode_video_height': transcode_video_height,
- 'transcode_audio_codec': transcode_audio_codec,
- 'transcode_audio_channels': transcode_audio_channels,
- 'user_id': user_id,
+ 'video_decision': session.get('video_decision','').title(),
+ 'audio_decision': session.get('audio_decision','').title(),
+ 'transcode_container': session.get('transcode_container',''),
+ 'transcode_video_codec': session.get('transcode_video_codec',''),
+ 'transcode_video_width': session.get('transcode_width',''),
+ 'transcode_video_height': session.get('transcode_height',''),
+ 'transcode_audio_codec': session.get('transcode_audio_codec',''),
+ 'transcode_audio_channels': session.get('transcode_audio_channels',''),
+ 'session_key': session.get('session_key',''),
+ 'user_id': session.get('user_id',''),
+ # Metadata parameters
+ 'media_type': metadata['media_type'],
'title': full_title,
'library_name': metadata['library_name'],
'show_name': show_name,
@@ -575,7 +537,7 @@ def build_notify_text(session=None, timeline=None, state=None):
'summary': metadata['summary'],
'tagline': metadata['tagline'],
'rating': metadata['rating'],
- 'duration': duration,
+ 'duration': metadata['duration'],
'section_id': metadata['section_id'],
'rating_key': metadata['rating_key'],
'parent_rating_key': metadata['parent_rating_key'],
@@ -598,9 +560,9 @@ def build_notify_text(session=None, timeline=None, state=None):
if state == 'play':
# Default body text
- body_text = '%s (%s) is watching %s' % (session['friendly_name'],
- session['player'],
- full_title)
+ body_text = '%s (%s) started playing %s' % (session['friendly_name'],
+ session['player'],
+ full_title)
if on_start_subject and on_start_body:
try:
@@ -767,6 +729,10 @@ def build_notify_text(session=None, timeline=None, state=None):
def build_server_notify_text(state=None):
+ # Get time formats
+ date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
+ time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
+
# Get the server name
server_name = plexpy.CONFIG.PMS_NAME
@@ -791,11 +757,12 @@ def build_server_notify_text(state=None):
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT
- available_params = {'server_name': server_name,
+ available_params = {# Global paramaters
+ 'server_name': server_name,
'server_uptime': server_uptime,
- 'action': state,
- 'datestamp': arrow.now().format(plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')),
- 'timestamp': arrow.now().format(plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz',''))}
+ 'action': state.title(),
+ 'datestamp': arrow.now().format(date_format),
+ 'timestamp': arrow.now().format(time_format)}
# Default text
subject_text = 'PlexPy (%s)' % server_name
@@ -907,4 +874,4 @@ def build_server_notify_text(state=None):
def strip_tag(data):
p = re.compile(r'<.*?>')
- return p.sub('', data)
+ return p.sub('', data)
\ No newline at end of file
diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py
index 1623cd8d..092eb9b9 100644
--- a/plexpy/notifiers.py
+++ b/plexpy/notifiers.py
@@ -34,7 +34,7 @@ from pynma import pynma
import gntp.notifier
import oauth2 as oauth
import pythontwitter as twitter
-import pythonfacebook as facebook
+import pythonfacebook as facebook
import plexpy
from plexpy import logger, helpers, request
@@ -58,7 +58,7 @@ AGENT_IDS = {"Growl": 0,
"Scripts": 15,
"Facebook": 16}
-
+
def available_notification_agents():
agents = [{'name': 'Growl',
'id': AGENT_IDS['Growl'],
@@ -1777,7 +1777,7 @@ class SLACK(object):
class Scripts(object):
def __init__(self, **kwargs):
- self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.py', '.pyw', '.rb', '.sh')
+ self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.ps1', '.py', '.pyw', '.rb', '.sh')
def conf(self, options):
return cherrypy.config['config'].get('Scripts', options)
@@ -1807,7 +1807,7 @@ class Scripts(object):
return scripts
- def notify(self, subject='', message='', notify_action='', script_args=[], *args, **kwargs):
+ def notify(self, subject='', message='', notify_action='', script_args=None, *args, **kwargs):
"""
Args:
subject(string, optional): Head text,
@@ -1817,7 +1817,10 @@ class Scripts(object):
"""
logger.debug(u"PlexPy Notifiers :: Trying to run notify script, action: %s, arguments: %s" %
(notify_action if notify_action else None, script_args if script_args else None))
-
+
+ if script_args is None:
+ script_args = []
+
if not plexpy.CONFIG.SCRIPTS_FOLDER:
return
@@ -1869,14 +1872,16 @@ class Scripts(object):
name, ext = os.path.splitext(script)
- if ext == '.py':
- prefix = 'python'
- elif ext == '.pyw':
- prefix = 'pythonw'
- elif ext == '.php':
+ if ext == '.php':
prefix = 'php'
elif ext == '.pl':
prefix = 'perl'
+ elif ext == '.ps1':
+ prefix = 'powershell -executionPolicy bypass -file'
+ elif ext == '.py':
+ prefix = 'python'
+ elif ext == '.pyw':
+ prefix = 'pythonw'
elif ext == '.rb':
prefix = 'ruby'
else:
@@ -1886,7 +1891,7 @@ class Scripts(object):
script = script.encode(plexpy.SYS_ENCODING, 'ignore')
if prefix:
- script = [prefix, script]
+ script = prefix.split() + [script]
else:
script = [script]
@@ -2025,7 +2030,7 @@ class Scripts(object):
return config_option
-
+
class FacebookNotifier(object):
def __init__(self):
@@ -2050,7 +2055,7 @@ class FacebookNotifier(object):
def _get_credentials(self, code):
logger.info(u"PlexPy Notifiers :: Requesting access token from Facebook")
-
+
try:
# Request user access token
api = facebook.GraphAPI(version='2.5')
@@ -2059,19 +2064,19 @@ class FacebookNotifier(object):
app_id=self.app_id,
app_secret=self.app_secret)
access_token = response['access_token']
-
+
# Request extended user access token
api = facebook.GraphAPI(access_token=access_token, version='2.5')
response = api.extend_access_token(app_id=self.app_id,
app_secret=self.app_secret)
access_token = response['access_token']
-
+
plexpy.CONFIG.FACEBOOK_TOKEN = access_token
plexpy.CONFIG.write()
except Exception as e:
logger.error(u"PlexPy Notifiers :: Error requesting Facebook access token: %s" % e)
return False
-
+
return True
def _post_facebook(self, message=None):
diff --git a/plexpy/plextv.py b/plexpy/plextv.py
index c45db03f..b3774342 100644
--- a/plexpy/plextv.py
+++ b/plexpy/plextv.py
@@ -383,7 +383,6 @@ class PlexTV(object):
return []
plextv_resources = self.get_plextv_resources(include_https=include_https)
- server_urls = []
try:
xml_parse = minidom.parseString(plextv_resources)
@@ -400,36 +399,51 @@ class PlexTV(object):
logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_server_urls: %s." % e)
return []
+ # Function to get all connections for a device
+ def get_connections(device):
+ conn = []
+ connections = device.getElementsByTagName('Connection')
+
+ for c in connections:
+ server_details = {"protocol": helpers.get_xml_attr(c, 'protocol'),
+ "address": helpers.get_xml_attr(c, 'address'),
+ "port": helpers.get_xml_attr(c, 'port'),
+ "uri": helpers.get_xml_attr(c, 'uri'),
+ "local": helpers.get_xml_attr(c, 'local')
+ }
+ conn.append(server_details)
+
+ return conn
+
+ server_urls = []
+
+ # Try to match the device
for a in xml_head:
if helpers.get_xml_attr(a, 'clientIdentifier') == server_id:
- connections = a.getElementsByTagName('Connection')
- for connection in connections:
- server_details = {"protocol": helpers.get_xml_attr(connection, 'protocol'),
- "address": helpers.get_xml_attr(connection, 'address'),
- "port": helpers.get_xml_attr(connection, 'port'),
- "uri": helpers.get_xml_attr(connection, 'uri'),
- "local": helpers.get_xml_attr(connection, 'local')
- }
+ server_urls = get_connections(a)
+ break
+
+ # Else no device match found
+ if not server_urls:
+ # Try to match the PMS_IP and PMS_PORT
+ for a in xml_head:
+ if helpers.get_xml_attr(a, 'provides') == 'server':
+ connections = a.getElementsByTagName('Connection')
- server_urls.append(server_details)
- # Else try to match the PMS_IP and PMS_PORT
- else:
- connections = a.getElementsByTagName('Connection')
- for connection in connections:
- if helpers.get_xml_attr(connection, 'address') == plexpy.CONFIG.PMS_IP and \
- int(helpers.get_xml_attr(connection, 'port')) == plexpy.CONFIG.PMS_PORT:
+ for connection in connections:
+ if helpers.get_xml_attr(connection, 'address') == plexpy.CONFIG.PMS_IP and \
+ int(helpers.get_xml_attr(connection, 'port')) == plexpy.CONFIG.PMS_PORT:
+
+ plexpy.CONFIG.PMS_IDENTIFIER = helpers.get_xml_attr(a, 'clientIdentifier')
+ plexpy.CONFIG.write()
+
+ logger.info(u"PlexPy PlexTV :: PMS identifier changed from %s to %s." % \
+ (server_id, plexpy.CONFIG.PMS_IDENTIFIER))
+
+ server_urls = get_connections(a)
+ break
- plexpy.CONFIG.PMS_IDENTIFIER = helpers.get_xml_attr(a, 'clientIdentifier')
-
- logger.info(u"PlexPy PlexTV :: PMS identifier changed from %s to %s." % \
- (server_id, plexpy.CONFIG.PMS_IDENTIFIER))
-
- server_details = {"protocol": helpers.get_xml_attr(connection, 'protocol'),
- "address": helpers.get_xml_attr(connection, 'address'),
- "port": helpers.get_xml_attr(connection, 'port'),
- "uri": helpers.get_xml_attr(connection, 'uri'),
- "local": helpers.get_xml_attr(connection, 'local')
- }
+ if server_urls:
break
return server_urls
diff --git a/plexpy/version.py b/plexpy/version.py
index 74c338e8..67235cab 100644
--- a/plexpy/version.py
+++ b/plexpy/version.py
@@ -1,2 +1,2 @@
PLEXPY_VERSION = "master"
-PLEXPY_RELEASE_VERSION = "1.3.4"
+PLEXPY_RELEASE_VERSION = "1.3.5"
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index a70b8252..831aaef0 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -492,7 +492,20 @@ class WebInterface(object):
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
else:
- return json.dumps({'message': 'Cannot refresh library while getting file sizes.'})
+ return json.dumps({'message': 'Cannot refresh library while getting file sizes.'})
+
+ @cherrypy.expose
+ def delete_duplicate_libraries(self):
+ library_data = libraries.Libraries()
+
+ result = library_data.delete_duplicate_libraries()
+
+ if result:
+ cherrypy.response.headers['Content-type'] = 'application/json'
+ return json.dumps({'message': result})
+ else:
+ cherrypy.response.headers['Content-type'] = 'application/json'
+ return json.dumps({'message': 'Unable to delete duplicate libraries from the database.'})
##### Users #####
@@ -1382,7 +1395,11 @@ class WebInterface(object):
@cherrypy.expose
def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, **kwargs):
- if not identifier:
+ from plexpy import http_handler
+
+ # Attempt to get the pms_identifier from plex.tv if the server is published
+ # Works for all PMS SSL settings
+ if not identifier and hostname and port:
plex_tv = plextv.PlexTV()
servers = plex_tv.discover()
@@ -1391,27 +1408,28 @@ class WebInterface(object):
identifier = server['clientIdentifier']
break
- if identifier and hostname and port:
- # Set PMS attributes to get the real PMS url
- plexpy.CONFIG.__setattr__('PMS_IP', hostname)
- plexpy.CONFIG.__setattr__('PMS_PORT', port)
- plexpy.CONFIG.__setattr__('PMS_IDENTIFIER', identifier)
- plexpy.CONFIG.__setattr__('PMS_SSL', ssl)
- plexpy.CONFIG.__setattr__('PMS_IS_REMOTE', remote)
- plexpy.CONFIG.write()
-
- plextv.get_real_pms_url()
-
- pms_connect = pmsconnect.PmsConnect()
- request = pms_connect.get_local_server_identity()
-
- if request:
- cherrypy.response.headers['Content-type'] = 'application/xml'
- return request
- else:
- logger.warn(u"Unable to retrieve data for get_server_id.")
- return None
+ # Fallback to checking /identity endpoint is server is unpublished
+ # Cannot set SSL settings on the PMS if unpublished so 'http' is okay
+ if not identifier:
+ request_handler = http_handler.HTTPHandler(host=hostname,
+ port=port,
+ token=None)
+ uri = '/identity'
+ request = request_handler.make_request(uri=uri,
+ proto='http',
+ request_type='GET',
+ output_format='xml',
+ no_token=True,
+ timeout=10)
+ if request:
+ xml_head = request.getElementsByTagName('MediaContainer')[0]
+ identifier = xml_head.getAttribute('machineIdentifier')
+
+ if identifier:
+ cherrypy.response.headers['Content-type'] = 'application/json'
+ return json.dumps(identifier)
else:
+ logger.warn('Unable to retrieve the PMS identifier.')
return None
@cherrypy.expose