diff --git a/CHANGELOG.md b/CHANGELOG.md index 418c496c..8be780f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## v2.0.28 (2018-04-02) + +* Monitoring: + * Fix: Homepage activity header text. + + +## v2.0.27 (2018-04-02) + +* Monitoring: + * Change: Move activity refresh interval setting to the settings page. + + +## v2.0.26-beta (2018-03-30) + +* Monitoring: + * New: Setting to change the refresh interval on the homepage. + * Fix: Identify extras correctly on the activity cards. +* Notifications: + * Change: Send Telegram image and text separately if the caption is longer than 200 characters. +* UI: + * Fix: Error when clicking on synced playlist links. + + ## v2.0.25 (2018-03-22) * Monitoring: diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html index 092dfba8..27e63a1c 100644 --- a/data/interfaces/default/current_activity_instance.html +++ b/data/interfaces/default/current_activity_instance.html @@ -64,7 +64,7 @@ DOCUMENTATION :: END from collections import defaultdict from urllib import quote from plexpy import helpers - from plexpy.common import VIDEO_RESOLUTION_OVERRIDES, AUDIO_CODEC_OVERRIDES + from plexpy.common import VIDEO_RESOLUTION_OVERRIDES, AUDIO_CODEC_OVERRIDES, EXTRA_TYPES import plexpy %> <% @@ -107,7 +107,11 @@ DOCUMENTATION :: END
% elif data['media_type'] in ('photo', 'clip'): -
+ % if data['extra_type']: +
+ % else: +
+ % endif % else:
% endif @@ -300,14 +304,13 @@ DOCUMENTATION :: END
  • Bandwidth
    - % if data['media_type'] != 'photo' and helpers.cast_to_int(data['bandwidth']): + % if data['media_type'] != 'photo' and data['bandwidth'] != 'Unknown': <% bw = helpers.cast_to_int(data['bandwidth']) - if bw != "Unknown": - if bw > 1000: - bw = str(round(bw / 1000.0, 1)) + ' Mbps' - else: - bw = str(bw) + ' kbps' + if bw > 1000: + bw = str(round(bw / 1000.0, 1)) + ' Mbps' + else: + bw = str(bw) + ' kbps' %> ${bw} @@ -439,7 +442,12 @@ DOCUMENTATION :: END % elif data['media_type'] == 'photo': ${data['title']} % else: - ${data['year']} + % if data['extra_type']: + <% extra_type = EXTRA_TYPES.get(data['extra_type'], data['sub_type'].capitalize()) %> + ${data['year']} (${extra_type}) + % else: + ${data['year']} + % endif % endif % elif data['channel_title']: ${data['channel_title']} diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index c34cf652..fe3436a0 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -113,7 +113,7 @@ // Load user ids and names (for the selector) $.ajax({ url: 'get_user_names', - type: 'get', + type: 'GET', dataType: 'json', success: function (data) { var select = $('#history-user'); @@ -130,6 +130,7 @@ function loadHistoryTable(media_type, selected_user_id) { history_table_options.ajax = { url: 'get_history', + type: 'POST', data: function (d) { return { json_data: JSON.stringify(d), diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 1a720124..2ad5b556 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -507,17 +507,15 @@ $('#location-' + key).html(s.location.toUpperCase()); - if (s.media_type !== 'photo' && parseInt(s.bandwidth)) { - var bw = parseInt(s.bandwidth); - if (bw !== "Unknown") { - if (bw > 1000) { - bw = (bw / 1000).toFixed(1) + ' Mbps'; - } else { - bw = bw + ' kbps' - } + if (s.media_type !== 'photo' && s.bandwidth !== 'Unknown') { + var bw = parseInt(s.bandwidth) || 0; + if (bw > 1000) { + bw = (bw / 1000).toFixed(1) + ' Mbps'; + } else { + bw = bw + ' kbps' } $('#stream-bandwidth-' + key).html(bw); - } + }; // Update the stream progress times $('#stream-eta-' + key).html(moment().add(parseInt(s.duration) - parseInt(s.view_offset), 'milliseconds').format(time_format)); @@ -589,7 +587,7 @@ if (!(create_instances.length) && activity_ready) { getCurrentActivity(); } - }, 2000); + }, ${config['home_refresh_interval'] * 1000}); setInterval(function(){ $('.progress_time_offset').each(function () { @@ -604,7 +602,7 @@ if ($(this).data('state') === 'playing' && $(this).data('view_offset') >= 0) { var view_offset = parseInt($(this).data('view_offset')); var stream_duration = parseInt($(this).data('stream_duration')); - var progress_percent = Math.min(Math.trunc(view_offset / stream_duration * 100), 100) + var progress_percent = Math.min(Math.trunc(view_offset / stream_duration * 100), 100); $(this).width(progress_percent - 3 + '%').html(progress_percent + '%') .attr('data-original-title', 'Stream Progress ' + progress_percent + '%') .data('view_offset', Math.min(view_offset + 1000, stream_duration)); diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 93632bcc..b5c0dcd8 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -547,7 +547,7 @@ DOCUMENTATION :: END function get_history() { history_table_options.ajax = { url: 'get_history', - type: 'post', + type: 'POST', data: function ( d ) { return { json_data: JSON.stringify( d ), @@ -563,7 +563,7 @@ DOCUMENTATION :: END function get_history() { history_table_options.ajax = { url: 'get_history', - type: 'post', + type: 'POST', data: function ( d ) { return { json_data: JSON.stringify( d ), @@ -579,7 +579,7 @@ DOCUMENTATION :: END function get_history() { history_table_options.ajax = { url: 'get_history', - type: 'post', + type: 'POST', data: function ( d ) { return { json_data: JSON.stringify( d ), diff --git a/data/interfaces/default/js/tables/sync_table.js b/data/interfaces/default/js/tables/sync_table.js index 942e37eb..748e0c51 100644 --- a/data/interfaces/default/js/tables/sync_table.js +++ b/data/interfaces/default/js/tables/sync_table.js @@ -37,7 +37,6 @@ sync_table_options = { "data": "state", "createdCell": function (td, cellData, rowData, row, col) { if (cellData === 'pending') { - $(td).addClass('currentlyWatching'); $(td).html('Pending...'); } else { $(td).html(cellData.toProperCase()); @@ -66,7 +65,7 @@ sync_table_options = { "data": "sync_title", "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { - if (rowData['metadata_type'] !== '') { + if (rowData['rating_key']) { $(td).html('' + cellData + ''); } else { $(td).html(cellData); @@ -74,7 +73,7 @@ sync_table_options = { } }, "className": "datatable-wrap" -}, + }, { "targets": [4], "data": "metadata_type", @@ -150,6 +149,11 @@ sync_table_options = { "preDrawCallback": function (settings) { var msg = " Fetching rows..."; showMsg(msg, false, false, 0) + }, + "rowCallback": function (row, rowData, rowIndex) { + if (rowData['state'] === 'pending') { + $(row).addClass('current-activity-row'); + } } }; diff --git a/data/interfaces/default/libraries.html b/data/interfaces/default/libraries.html index caf34c27..fb5b7d7d 100644 --- a/data/interfaces/default/libraries.html +++ b/data/interfaces/default/libraries.html @@ -91,7 +91,7 @@ json_data: JSON.stringify(d) }; } - } + }; libraries_list_table = $('#libraries_list_table').DataTable(libraries_list_table_options); var colvis = new $.fn.dataTable.ColVis(libraries_list_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 1] }); diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index 7d2b6481..1382d991 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -374,7 +374,7 @@ DOCUMENTATION :: END // Build watch history table history_table_options.ajax = { url: 'get_history', - type: 'post', + type: 'POST', data: function ( d ) { return { json_data: JSON.stringify( d ), @@ -406,7 +406,7 @@ DOCUMENTATION :: END // Build media info table media_info_table_options.ajax = { url: 'get_library_media_info', - type: 'post', + type: 'POST', data: function ( d ) { return { json_data: JSON.stringify( d ), diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index 22a5df0c..7b54a6de 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -251,8 +251,8 @@ var selected_log_level = null; function loadtautullilogs(logfile, selected_log_level) { log_table_options.ajax = { - url: "get_log", - type: 'post', + url: 'get_log', + type: 'POST', data: function (d) { return { logfile: logfile, @@ -271,7 +271,8 @@ function loadPlexLogs() { plex_log_table_options.ajax = { - url: "get_plex_log?log_type=server" + url: 'get_plex_log?log_type=server', + type: 'POST' }; plex_log_table_options.initComplete = bindLogLevelFilter; plex_log_table = $('#plex_log_table').DataTable(plex_log_table_options); @@ -279,7 +280,8 @@ function loadPlexScannerLogs() { plex_log_table_options.ajax = { - url: "get_plex_log?log_type=scanner" + url: 'get_plex_log?log_type=scanner', + type: 'POST' }; plex_log_table_options.initComplete = bindLogLevelFilter; plex_scanner_log_table = $('#plex_scanner_log_table').DataTable(plex_log_table_options); @@ -287,7 +289,8 @@ function loadNotificationLogs() { notification_log_table_options.ajax = { - url: "get_notification_log", + url: 'get_notification_log', + type: 'POST', data: function (d) { return { json_data: JSON.stringify(d) @@ -312,7 +315,8 @@ function loadLoginLogs() { login_log_table_options.pageLength = 50; login_log_table_options.ajax = { - url: "get_user_logins", + url: 'get_user_logins', + type: 'POST', data: function (d) { return { json_data: JSON.stringify(d) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index e6a345ea..a36d71b9 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -269,6 +269,21 @@
    +
    +

    Activity

    +
    + +
    + +
    +
    + +
    + +
    +

    Set the interval (in seconds) to refresh the current activity on the homepage. Minimum 2.

    +
    +

    Sections

    @@ -680,12 +695,7 @@
    -
    - - - - -
    +

    @@ -2031,6 +2041,9 @@ $(document).ready(function() { $('#pms_url_manual').prop('checked', false); $('#pms_url').val('Please verify your server above to retrieve the URL'); PMSCloudCheck(); + }, + onDropdownOpen: function() { + this.clear(); } }); var select_pms = $select_pms[0].selectize; @@ -2104,11 +2117,11 @@ $(document).ready(function() { data: { hostname: pms_ip, port: pms_port, - identifier: pms_identifier, ssl: pms_ssl, remote: pms_is_remote, manual: pms_url_manual, - get_url: serverChanged + get_url: true, + test_websocket: true }, cache: true, async: true, @@ -2121,15 +2134,23 @@ $(document).ready(function() { var result = xhr; var identifier = result.identifier; var url = result.url; + var ws = result.ws; if (identifier) { $("#pms_identifier").val(identifier); + if (url) { $("#pms_url").val(url); } - $("#pms_verify").html('').fadeIn('fast'); - $("#pms_ip_group").removeClass("has-error"); - serverChanged = false; + if (ws === false) { + $("#pms_verify").html('').fadeIn('fast'); + $("#pms_ip_group").addClass("has-error"); + showMsg(' Server found but unable to connect websocket.
    Check the logs for errors.', false, true, 5000, true) + } else { + $("#pms_verify").html('').fadeIn('fast'); + $("#pms_ip_group").removeClass("has-error"); + serverChanged = false; + } if (_callback) { _callback(); @@ -2157,13 +2178,6 @@ $(document).ready(function() { $("#pms_web_url").val(pms_web_url || 'https://app.plex.tv/desktop'); } - $('#test_pms_url_button').on('click', function(){ - var pms_url = $.trim($("#pms_url").val()); - if (pms_url.startsWith('http')) { - window.open(pms_url + '/web', '_blank'); - } - }); - $('#test_pms_web_button').on('click', function(){ var pms_web_url = $.trim($("#pms_web_url").val()); window.open(pms_web_url, '_blank'); diff --git a/data/interfaces/default/sync.html b/data/interfaces/default/sync.html index 3197b01b..e1d71656 100644 --- a/data/interfaces/default/sync.html +++ b/data/interfaces/default/sync.html @@ -100,7 +100,7 @@ // Load user ids and names (for the selector) $.ajax({ url: 'get_user_names', - type: 'get', + type: 'GET', dataType: 'json', success: function (data) { var select = $('#sync-user'); @@ -116,7 +116,8 @@ function loadSyncTable(selected_user_id) { sync_table_options.ajax = { - url: 'get_sync?user_id=' + selected_user_id + url: 'get_sync?user_id=' + selected_user_id, + type: 'POST' }; sync_table = $('#sync_table').DataTable(sync_table_options); var colvis = new $.fn.dataTable.ColVis(sync_table, { diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 1d76ed6f..251fec10 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -413,7 +413,7 @@ DOCUMENTATION :: END // Build watch history table history_table_options.ajax = { url: 'get_history', - type: 'post', + type: 'POST', data: function ( d ) { return { json_data: JSON.stringify( d ), @@ -442,7 +442,8 @@ DOCUMENTATION :: END function loadSyncTable() { // Build user sync table sync_table_options.ajax = { - url: 'get_sync?user_id=' + user_id + url: 'get_sync?user_id=' + user_id, + type: 'POST' }; sync_table = $('#sync_table-UID-${data["user_id"]}').DataTable(sync_table_options); sync_table.column(2).visible(false); @@ -457,7 +458,7 @@ DOCUMENTATION :: END // Build user IP table user_ip_table_options.ajax = { url: 'get_user_ips', - type: 'post', + type: 'POST', data: function ( d ) { return { json_data: JSON.stringify( d ), @@ -474,6 +475,7 @@ DOCUMENTATION :: END // Build user login table login_log_table_options.ajax = { url: 'get_user_logins', + type: 'POST', data: function(d) { return { json_data: JSON.stringify(d), diff --git a/data/interfaces/default/users.html b/data/interfaces/default/users.html index 6c0d5690..926dafe2 100644 --- a/data/interfaces/default/users.html +++ b/data/interfaces/default/users.html @@ -94,7 +94,7 @@ json_data: JSON.stringify(d) }; } - } + }; users_list_table = $('#users_list_table').DataTable(users_list_table_options); var colvis = new $.fn.dataTable.ColVis(users_list_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 1] }); diff --git a/data/interfaces/default/welcome.html b/data/interfaces/default/welcome.html index 2a7cff1c..d61900a9 100644 --- a/data/interfaces/default/welcome.html +++ b/data/interfaces/default/welcome.html @@ -374,6 +374,9 @@ $(document).ready(function() { $('#pms_is_remote_checkbox').prop('disabled', false); $('#pms_ssl_checkbox').prop('disabled', false); } + }, + onDropdownOpen: function() { + this.clear(); } }); var select_pms = $select_pms[0].selectize; diff --git a/plexpy/common.py b/plexpy/common.py index c69350d6..591b33fa 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -175,6 +175,16 @@ HW_ENCODERS = [ 'nvenc' ] +EXTRA_TYPES = { + '1': 'Trailer', + '2': 'Deleted Scene', + '3': 'Interview', + '5': 'Behind the Scenes', + '6': 'Scene', + '10': 'Featurette', + '11': 'Short' +} + SCHEDULER_LIST = [ 'Check GitHub for updates', 'Check for server response', diff --git a/plexpy/config.py b/plexpy/config.py index d3a6df68..57c92a22 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -209,6 +209,7 @@ _CONFIG_DEFINITIONS = { 'HOME_STATS_CARDS': (list, 'General', ['top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', \ 'popular_music', 'last_watched', 'top_users', 'top_platforms', 'most_concurrent']), 'HOME_STATS_RECENTLY_ADDED_COUNT': (int, 'General', 50), + 'HOME_REFRESH_INTERVAL': (int, 'General', 10), 'HTTPS_CREATE_CERT': (int, 'General', 1), 'HTTPS_CERT': (str, 'General', ''), 'HTTPS_CERT_CHAIN': (str, 'General', ''), diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 6d1ca7a4..51e94010 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -3321,10 +3321,17 @@ class TELEGRAM(Notifier): if poster_content: poster_filename = 'poster_{}.png'.format(pretty_metadata.parameters['rating_key']) files = {'photo': (poster_filename, poster_content, 'image/png')} - data['caption'] = text - return self.make_request('https://api.telegram.org/bot{}/sendPhoto'.format(self.config['bot_token']), - data=data, files=files) + if len(text) > 200: + data['disable_notification'] = True + else: + data['caption'] = text + + r = self.make_request('https://api.telegram.org/bot{}/sendPhoto'.format(self.config['bot_token']), + data=data, files=files) + + if not data.pop('disable_notification', None): + return r data['text'] = text diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 421527e7..5ea7d8dd 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1133,7 +1133,9 @@ class PmsConnect(object): 'genres': genres, 'labels': labels, 'collections': collections, - 'full_title': helpers.get_xml_attr(metadata_main, 'title') + 'full_title': helpers.get_xml_attr(metadata_main, 'title'), + 'extra_type': helpers.get_xml_attr(metadata_main, 'extraType'), + 'sub_type': helpers.get_xml_attr(metadata_main, 'subtype') } else: @@ -1724,7 +1726,9 @@ class PmsConnect(object): 'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels), 'channel_icon': helpers.get_xml_attr(session, 'sourceIcon'), 'channel_title': helpers.get_xml_attr(session, 'sourceTitle'), - 'live': int(helpers.get_xml_attr(session, 'live') == '1') + 'live': int(helpers.get_xml_attr(session, 'live') == '1'), + 'extra_type': helpers.get_xml_attr(session, 'extraType'), + 'sub_type': helpers.get_xml_attr(session, 'subtype') } else: channel_stream = 0 diff --git a/plexpy/version.py b/plexpy/version.py index 97c5050d..353a93e8 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -1,2 +1,2 @@ PLEXPY_BRANCH = "master" -PLEXPY_RELEASE_VERSION = "v2.0.25" +PLEXPY_RELEASE_VERSION = "v2.0.28" diff --git a/plexpy/webserve.py b/plexpy/webserve.py index b9aaaf4e..ed6e2b36 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -27,6 +27,8 @@ from hashing_passwords import make_hash from mako.lookup import TemplateLookup from mako import exceptions +import websocket + import plexpy import activity_pinger import common @@ -173,6 +175,7 @@ class WebInterface(object): "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE, "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, "home_stats_recently_added_count": plexpy.CONFIG.HOME_STATS_RECENTLY_ADDED_COUNT, + "home_refresh_interval": plexpy.CONFIG.HOME_REFRESH_INTERVAL, "pms_name": plexpy.CONFIG.PMS_NAME, "pms_is_cloud": plexpy.CONFIG.PMS_IS_CLOUD, "update_show_changelog": plexpy.CONFIG.UPDATE_SHOW_CHANGELOG @@ -2730,6 +2733,7 @@ class WebInterface(object): "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), + "home_refresh_interval": plexpy.CONFIG.HOME_REFRESH_INTERVAL, "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD, "buffer_wait": plexpy.CONFIG.BUFFER_WAIT, "group_history_tables": checked(plexpy.CONFIG.GROUP_HISTORY_TABLES), @@ -3556,7 +3560,7 @@ class WebInterface(object): @requireAuth(member_of("admin")) @addtoapi() def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, manual=0, - get_url=False, **kwargs): + get_url=False, test_websocket=False, **kwargs): """ Get the PMS server identifier. ``` @@ -3612,6 +3616,23 @@ class WebInterface(object): pms_url_manual=manual, pms_identifier=identifier) result['url'] = server['pms_url'] + result['ws'] = None + + if test_websocket == 'true': + # Quick test websocket connection + ws_url = result['url'].replace('http', 'ws', 1) + '/:/websockets/notifications' + header = ['X-Plex-Token: %s' % plexpy.CONFIG.PMS_TOKEN] + + logger.debug("Testing websocket connection...") + try: + test_ws = websocket.create_connection(ws_url, header=header) + test_ws.close() + logger.debug("Websocket connection test successful.") + result['ws'] = True + except (websocket.WebSocketException, IOError, Exception) as e: + logger.error("Websocket connection test failed: %s" % e) + result['ws'] = False + return result else: logger.warn('Unable to retrieve the PMS identifier.') @@ -3790,6 +3811,9 @@ class WebInterface(object): @cherrypy.expose @requireAuth() def info(self, rating_key=None, source=None, query=None, **kwargs): + if rating_key and not str(rating_key).isdigit(): + raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) + metadata = None config = {