Compare commits

...

7 Commits

Author SHA1 Message Date
JonnyWong16
5d2219f2f8 v2.0.17-beta 2018-02-03 09:35:05 -08:00
JonnyWong16
56dc28eed3 Clear session metadata cache on startup 2018-02-03 09:06:05 -08:00
JonnyWong16
3e723d4373 Fix photo album media type 2018-02-02 23:49:38 -08:00
JonnyWong16
f5e341e655 Don't sanitize tags for Slack and Discord 2018-02-02 23:22:41 -08:00
JonnyWong16
3c81100957 Fix media info table sorting 2018-02-02 23:03:48 -08:00
JonnyWong16
304378f93b Add Zapier notification agent 2018-02-01 22:11:33 -08:00
JonnyWong16
de6b6e8124 Check for any falsy value in sync item filters 2018-01-31 08:59:37 -08:00
11 changed files with 200 additions and 31 deletions

View File

@@ -1,5 +1,16 @@
# Changelog
## v2.0.17-beta (2018-02-03)
* Notifications:
* Fix: Unable to use @ mentions tags for Discord and Slack.
* New: Added Zapier notification agent.
* API:
* Fix: get_synced_items returning no results.
* Fix: get_library_media_info returning incorrect media type for photo albums.
* Fix: get_library_media_info not being able to sort by title.
## v2.0.16-beta (2018-01-30)
* Monitoring:

View File

@@ -580,6 +580,18 @@
});
var join_device_names = $join_device_names[0].selectize;
join_device_names.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'join_device_names'), [])) | n});
% elif notifier['agent_name'] == 'zapier':
$('#zapier_test_hook').click(function () {
$.get('zapier_test_hook', { 'zapier_hook': $('#zapier_hook').val() }, function (data) {
if (data.result === 'success') {
showMsg('<i class="fa fa-check"></i> ' + data.msg, false, true, 5000);
} else {
showMsg('<i class="fa fa-times"></i> ' + data.msg, false, true, 5000, true);
}
});
});
% endif
function validateLogic() {

View File

@@ -15,6 +15,7 @@
import os
from Queue import Queue
import shutil
import sqlite3
import sys
import subprocess
@@ -156,6 +157,16 @@ def initialize(config_file):
except OSError as e:
logger.error(u"Could not create cache dir '%s': %s" % (CONFIG.CACHE_DIR, e))
if CONFIG.CACHE_DIR:
session_metadata_folder = os.path.join(CONFIG.CACHE_DIR, 'session_metadata')
try:
shutil.rmtree(session_metadata_folder, ignore_errors=True)
except OSError as e:
pass
if not os.path.exists(session_metadata_folder):
os.mkdir(session_metadata_folder)
# Initialize the database
logger.info(u"Checking if the database upgrades are required...")
try:

View File

@@ -546,7 +546,7 @@ def on_created(rating_key, **kwargs):
def delete_metadata_cache(session_key):
try:
os.remove(os.path.join(plexpy.CONFIG.CACHE_DIR, 'metadata-sessionKey-%s.json' % session_key))
os.remove(os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata/metadata-sessionKey-%s.json' % session_key))
except IOError as e:
logger.error(u"Tautulli ActivityHandler :: Failed to remove metadata cache file (sessionKey %s): %s"
% (session_key, e))

View File

@@ -544,19 +544,19 @@ class Libraries(object):
filtered_count = len(results)
# Sort results
results = sorted(results, key=lambda k: k['sort_title'])
results = sorted(results, key=lambda k: k['sort_title'].lower())
sort_order = json_data['order']
for order in reversed(sort_order):
sort_key = json_data['columns'][int(order['column'])]['data']
reverse = True if order['dir'] == 'desc' else False
if rating_key and sort_key == 'sort_title':
results = sorted(results, key=lambda k: helpers.cast_to_int(k['media_index']), reverse=reverse)
elif sort_key == 'file_size' or sort_key == 'bitrate':
elif sort_key in ('file_size', 'bitrate', 'added_at', 'last_played', 'play_count'):
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key]), reverse=reverse)
elif sort_key == 'video_resolution':
results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key].replace('4k', '2160p').rstrip('p')), reverse=reverse)
else:
results = sorted(results, key=lambda k: k[sort_key], reverse=reverse)
results = sorted(results, key=lambda k: k[sort_key].lower(), reverse=reverse)
total_file_size = sum([helpers.cast_to_int(d['file_size']) for d in results])

View File

@@ -980,8 +980,8 @@ def strip_tag(data, agent_id=None):
'font': ['color']}
return bleach.clean(data, tags=whitelist.keys(), attributes=whitelist, strip=True)
elif agent_id == 10:
# Don't remove tags for email
elif agent_id in (10, 14, 20):
# Don't remove tags for Email, Slack, and Discord
return data
elif agent_id == 13:

View File

@@ -90,7 +90,8 @@ AGENT_IDS = {'growl': 0,
'discord': 20,
'androidapp': 21,
'groupme': 22,
'mqtt': 23
'mqtt': 23,
'zapier': 24
}
@@ -186,6 +187,10 @@ def available_notification_agents():
{'label': 'XBMC',
'name': 'xbmc',
'id': AGENT_IDS['xbmc']
},
{'label': 'Zapier',
'name': 'zapier',
'id': AGENT_IDS['zapier']
}
]
@@ -377,6 +382,8 @@ def get_agent_class(agent_id=None, config=None):
return GROUPME(config=config)
elif agent_id == 23:
return MQTT(config=config)
elif agent_id == 24:
return ZAPIER(config=config)
else:
return Notifier(config=config)
else:
@@ -652,13 +659,28 @@ class PrettyMetadata(object):
provider_name = 'Trakt.tv'
elif provider == 'lastfm':
provider_name = 'Last.fm'
else:
if self.media_type == 'movie':
provider_name = 'IMDb'
elif self.media_type in ('show', 'season', 'episode'):
provider_name = 'TheTVDB'
elif self.media_type in ('artist', 'album', 'track'):
provider_name = 'Last.fm'
return provider_name
def get_provider_link(self, provider=None):
provider_link = ''
if provider == 'plexweb':
provider_link = self.get_plex_url()
else:
elif provider:
provider_link = self.parameters.get(provider + '_url', '')
else:
if self.media_type == 'movie':
provider_link = self.parameters.get('imdb_url', '')
elif self.media_type in ('show', 'season', 'episode'):
provider_link = self.parameters.get('thetvdb_url', '')
elif self.media_type in ('artist', 'album', 'track'):
provider_link = self.parameters.get('lastfm_url', '')
return provider_link
def get_caption(self, provider):
@@ -1919,23 +1941,24 @@ class IFTTT(Notifier):
headers=headers, json=data)
def return_config_options(self):
config_option = [{'label': 'Ifttt Maker Channel Key',
config_option = [{'label': 'IFTTT Webhook Key',
'value': self.config['key'],
'name': 'ifttt_key',
'description': 'Your Ifttt key. You can get a key from'
' <a href="' + helpers.anon_url('https://ifttt.com/maker') + '" target="_blank">here</a>.',
'description': 'Your IFTTT webhook key. You can get a key from'
' <a href="' + helpers.anon_url('https://ifttt.com/maker_webhooks') + '" target="_blank">here</a>.',
'input_type': 'text'
},
{'label': 'Ifttt Event',
{'label': 'IFTTT Event',
'value': self.config['event'],
'name': 'ifttt_event',
'description': 'The Ifttt maker event to fire. You can include'
' the {action} to be substituted with the action name.'
'description': 'The IFTTT maker event to fire. You can include'
' <span class="inline-pre">{action}</span>'
' to be substituted with the action name.'
' The notification subject and body will be sent'
' as value1 and value2 respectively.',
' as <span class="inline-pre">value1</span>'
' and <span class="inline-pre">value2</span> respectively.',
'input_type': 'text'
}
]
return config_option
@@ -2072,7 +2095,7 @@ class JOIN(Notifier):
{'label': 'Movie Link Source',
'value': self.config['movie_provider'],
'name': 'join_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br>'
'description': 'Select the source for movie links in the notificaation. Leave blank for default.<br>'
'3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers()
@@ -2080,7 +2103,7 @@ class JOIN(Notifier):
{'label': 'TV Show Link Source',
'value': self.config['tv_provider'],
'name': 'join_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br>'
'description': 'Select the source for tv show links in the notificaation. Leave blank for default.<br>'
'3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers()
@@ -2088,7 +2111,7 @@ class JOIN(Notifier):
{'label': 'Music Link Source',
'value': self.config['music_provider'],
'name': 'join_music_provider',
'description': 'Select the source for music links on the info cards. Leave blank for default.',
'description': 'Select the source for music links in the notificaation. Leave blank for default.',
'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers()
}
@@ -2714,7 +2737,7 @@ class PUSHOVER(Notifier):
{'label': 'Movie Link Source',
'value': self.config['movie_provider'],
'name': 'pushover_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br>'
'description': 'Select the source for movie links in the notification. Leave blank for default.<br>'
'3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers()
@@ -2722,7 +2745,7 @@ class PUSHOVER(Notifier):
{'label': 'TV Show Link Source',
'value': self.config['tv_provider'],
'name': 'pushover_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br>'
'description': 'Select the source for tv show links in the notification. Leave blank for default.<br>'
'3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers()
@@ -2730,7 +2753,7 @@ class PUSHOVER(Notifier):
{'label': 'Music Link Source',
'value': self.config['music_provider'],
'name': 'pushover_music_provider',
'description': 'Select the source for music links on the info cards. Leave blank for default.',
'description': 'Select the source for music links in the notification. Leave blank for default.',
'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers()
}
@@ -3403,6 +3426,104 @@ class XBMC(Notifier):
return config_option
class ZAPIER(Notifier):
"""
Zapier notifications
"""
NAME = 'Zapier'
_DEFAULT_CONFIG = {'hook': '',
'movie_provider': '',
'tv_provider': '',
'music_provider': ''
}
def _test_hook(self):
_test_data = {'subject': 'Subject',
'body': 'Body',
'action': 'Action',
'poster_url': 'https://i.imgur.com',
'provider_name': 'Provider Name',
'provider_link': 'http://www.imdb.com',
'plex_url': 'https://app.plex.tv/desktop'}
return self.agent_notify(_test_data=_test_data)
def agent_notify(self, subject='', body='', action='', **kwargs):
data = {'subject': subject.encode("utf-8"),
'body': body.encode("utf-8"),
'action': action.encode("utf-8")}
if kwargs.get('parameters', {}).get('media_type'):
# Grab formatted metadata
pretty_metadata = PrettyMetadata(kwargs['parameters'])
if pretty_metadata.media_type == 'movie':
provider = self.config['movie_provider']
elif pretty_metadata.media_type in ('show', 'season', 'episode'):
provider = self.config['tv_provider']
elif pretty_metadata.media_type in ('artist', 'album', 'track'):
provider = self.config['music_provider']
else:
provider = None
poster_url = pretty_metadata.get_poster_url()
provider_name = pretty_metadata.get_provider_name(provider)
provider_link = pretty_metadata.get_provider_link(provider)
plex_url = pretty_metadata.get_plex_url()
data['poster_url'] = poster_url
data['provider_name'] = provider_name
data['provider_link'] = provider_link
data['plex_url'] = plex_url
if kwargs.get('_test_data'):
data.update(kwargs['_test_data'])
headers = {'Content-type': 'application/json'}
return self.make_request(self.config['hook'], headers=headers, json=data)
def return_config_options(self):
config_option = [{'label': 'Zapier Webhook URL',
'value': self.config['hook'],
'name': 'zapier_hook',
'description': 'Your Zapier webhook URL.',
'input_type': 'text'
},
{'label': 'Test Zapier Webhook',
'value': 'Send Test Data',
'name': 'zapier_test_hook',
'description': 'Click this button when prompted on then "Test Webhooks by Zapier" step.',
'input_type': 'button'
},
{'label': 'Movie Link Source',
'value': self.config['movie_provider'],
'name': 'zapier_movie_provider',
'description': 'Select the source for movie links in the notification. Leave blank for default.<br>'
'3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers()
},
{'label': 'TV Show Link Source',
'value': self.config['tv_provider'],
'name': 'zapier_tv_provider',
'description': 'Select the source for tv show links in the notification. Leave blank for default.<br>'
'3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers()
},
{'label': 'Music Link Source',
'value': self.config['music_provider'],
'name': 'zapier_music_provider',
'description': 'Select the source for music links in the notification. Leave blank for default.',
'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers()
}
]
return config_option
def upgrade_config_to_db():
logger.info(u"Tautulli Notifiers :: Upgrading to new notification system...")

View File

@@ -376,17 +376,17 @@ class PlexTV(object):
def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None,
rating_key_filter=None, sync_id_filter=None):
if machine_id is None:
if not machine_id:
machine_id = plexpy.CONFIG.PMS_IDENTIFIER
if isinstance(rating_key_filter, list):
rating_key_filter = [str(k) for k in rating_key_filter]
elif rating_key_filter is not None:
elif rating_key_filter:
rating_key_filter = [str(rating_key_filter)]
if isinstance(user_id_filter, list):
user_id_filter = [str(k) for k in user_id_filter]
elif user_id_filter is not None:
elif user_id_filter:
user_id_filter = [str(user_id_filter)]
sync_list = self.get_plextv_sync_lists(machine_id, output_format='xml')

View File

@@ -533,7 +533,7 @@ class PmsConnect(object):
metadata = {}
if cache_key:
in_file_path = os.path.join(plexpy.CONFIG.CACHE_DIR, 'metadata-sessionKey-%s.json' % cache_key)
in_file_path = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata/metadata-sessionKey-%s.json' % cache_key)
try:
with open(in_file_path, 'r') as inFile:
metadata = json.load(inFile)
@@ -582,7 +582,7 @@ class PmsConnect(object):
metadata_main = metadata_main_list[0]
metadata_type = helpers.get_xml_attr(metadata_main, 'type')
if metadata_type == 'photo':
if metadata_main.nodeName == 'Directory' and metadata_type == 'photo':
metadata_type = 'photo_album'
section_id = helpers.get_xml_attr(a, 'librarySectionID')
@@ -1179,7 +1179,7 @@ class PmsConnect(object):
if cache_key:
metadata['_cache_time'] = int(time.time())
out_file_path = os.path.join(plexpy.CONFIG.CACHE_DIR, 'metadata-sessionKey-%s.json' % cache_key)
out_file_path = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata/metadata-sessionKey-%s.json' % cache_key)
try:
with open(out_file_path, 'w') as outFile:
json.dump(metadata, outFile)
@@ -2175,8 +2175,12 @@ class PmsConnect(object):
item_main += a.getElementsByTagName('Photo')
for item in item_main:
media_type = helpers.get_xml_attr(item, 'type')
if item.nodeName == 'Directory' and media_type == 'photo':
media_type = 'photo_album'
item_info = {'section_id': helpers.get_xml_attr(a, 'librarySectionID'),
'media_type': helpers.get_xml_attr(item, 'type'),
'media_type': media_type,
'rating_key': helpers.get_xml_attr(item, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(item, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(item, 'grandparentRatingKey'),

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.0.16-beta"
PLEXPY_RELEASE_VERSION = "v2.0.17-beta"

View File

@@ -674,7 +674,7 @@ class WebInterface(object):
if not kwargs.get('json_data'):
# Alias 'title' to 'sort_title'
if kwargs.get('order_column') == 'title':
kwargs['order_column'] == 'sort_title'
kwargs['order_column'] = 'sort_title'
# TODO: Find some one way to automatically get the columns
dt_columns = [("added_at", True, False),
@@ -3200,6 +3200,16 @@ class WebInterface(object):
logger.warn(msg)
return msg
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def zapier_test_hook(self, zapier_hook='', **kwargs):
success = notifiers.ZAPIER(config={'hook': zapier_hook})._test_hook()
if success:
return {'result': 'success', 'msg': 'Test Zapier webhook sent.'}
else:
return {'result': 'error', 'msg': 'Failed to send test Zapier webhook.'}
@cherrypy.expose
@requireAuth(member_of("admin"))
def set_notification_config(self, **kwargs):