Compare commits

..

16 Commits

Author SHA1 Message Date
JonnyWong16
f336782fc1 v2.1.8-beta 2018-05-19 09:07:18 -07:00
JonnyWong16
c19afa06de Fallback to originally available at for episode number on info pages 2018-05-18 17:47:19 -07:00
JonnyWong16
e003850d31 Update Facebook permissions scope 2018-05-18 17:41:42 -07:00
JonnyWong16
23cf790079 Return proper status codes for API (Fixes Tautulli/Tautulli-Issues#82) 2018-05-18 17:41:23 -07:00
JonnyWong16
e7f930bd0f Check for Tautulli footer in newsletters 2018-05-17 10:31:55 -07:00
JonnyWong16
348707b6b9 Revert back to HTTP newsletter images from tautulli.com 2018-05-17 09:30:34 -07:00
JonnyWong16
7ad78b4536 Allow images through newsletter password auth 2018-05-17 08:40:58 -07:00
JonnyWong16
a408a62234 Check newsletter auth setting when checking guest access enabled 2018-05-17 08:34:36 -07:00
JonnyWong16
a1e9e7e87f Add newsletter password to newsletter parameters 2018-05-16 23:20:53 -07:00
JonnyWong16
fa99f6e684 Add self-hosted newsletter authentication metnods 2018-05-16 23:11:28 -07:00
JonnyWong16
11e9bd2d54 Fix incorrect <div> tag 2018-05-16 21:59:15 -07:00
JonnyWong16
50165af4b7 Update tautulli.com URLs to HTTPS 2018-05-15 20:38:25 -07:00
JonnyWong16
5dd22c23f2 Patch Twitter str encoding for Python 2 2018-05-15 08:44:13 -07:00
JonnyWong16
79b45c1c46 Auto quality when fetching cloudinary transform 2018-05-15 08:43:20 -07:00
JonnyWong16
af917c4915 Add session key to activity processor log messages 2018-05-14 09:03:18 -07:00
JonnyWong16
c3238b5a83 Fix Imgur database migration again 2018-05-14 09:02:32 -07:00
20 changed files with 213 additions and 45 deletions

View File

@@ -1,5 +1,16 @@
# Changelog # Changelog
## v2.1.8-beta (2018-05-19)
* Newsletters:
* New: Added authentication options for self-hosted newsletters.
* Change: Check if the Tautulli footer has been removed in custom newsletter templates.
* Notifications:
* Fix: Cloudinary images not working for Twitter notifications.
* API:
* Fix: Return proper HTTP status codes for errors.
## v2.1.7-beta (2018-05-13) ## v2.1.7-beta (2018-05-13)
* Newsletters: * Newsletters:

View File

@@ -27,9 +27,9 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
## Preview ## Preview
* [Full preview gallery available on our website](http://tautulli.com) * [Full preview gallery available on our website](https://tautulli.com)
![Tautulli Homepage](http://tautulli.com/images/screenshots/activity-compressed.jpg?v=2) ![Tautulli Homepage](https://tautulli.com/images/screenshots/activity-compressed.jpg?v=2)
## Installation and Support ## Installation and Support

View File

@@ -78,7 +78,7 @@ DOCUMENTATION :: END
<tr> <tr>
<td class="top-line">Resources:</td> <td class="top-line">Resources:</td>
<td class="top-line"> <td class="top-line">
<a class="no-highlight" href="${anon_url('http://tautulli.com')}" target="_blank">Tautulli Website</a> | <a class="no-highlight" href="${anon_url('https://tautulli.com')}" target="_blank">Tautulli Website</a> |
<a class="no-highlight" href="${anon_url('https://github.com/%s/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">GitHub Source</a> | <a class="no-highlight" href="${anon_url('https://github.com/%s/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">GitHub Source</a> |
<a class="no-highlight guidelines-modal-link" href="${anon_url('https://github.com/%s/%s-Issues' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" data-id="issue">GitHub Issues</a> | <a class="no-highlight guidelines-modal-link" href="${anon_url('https://github.com/%s/%s-Issues' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" data-id="issue">GitHub Issues</a> |
<a class="no-highlight" href="${anon_url('https://github.com/%s/%s-Wiki' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">GitHub Wiki</a> | <a class="no-highlight" href="${anon_url('https://github.com/%s/%s-Wiki' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">GitHub Wiki</a> |

View File

@@ -3525,8 +3525,7 @@ a.no-highlight:hover {
} }
.login-logo { .login-logo {
margin: 0 auto 50px auto; margin: 0 auto 50px auto;
width: 340px; text-align: center;
height: 100px;
} }
.login-container .form-group { .login-container .form-group {
margin-bottom: 20px; margin-bottom: 20px;
@@ -4099,3 +4098,7 @@ a[data-tab-destination] {
padding-top: 10px; padding-top: 10px;
border-top: 1px solid #444; border-top: 1px solid #444;
} }
.newsletter-logo {
margin: 0 auto 50px auto;
text-align: center;
}

View File

@@ -91,7 +91,7 @@ DOCUMENTATION :: END
<div class="item-children-poster-face episode-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"> <div class="item-children-poster-face episode-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);">
<div class="item-children-card-overlay"> <div class="item-children-card-overlay">
<div class="item-children-overlay-text"> <div class="item-children-overlay-text">
Episode ${child['media_index']} Episode ${child['media_index'] or child['originally_available_at']}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,43 @@
<%
import urllib
%>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tautulli - ${title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="body-container">
<div class="container-fluid">
<div class="row">
<div class="login-container">
<div class="newsletter-logo">
<img src="${http_root}images/newsletter/newsletter-header.png" height="100" alt="PlexPy">
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<form action="${uri}" method="post" id="newsletter-form">
<div class="form-group">
<label for="password" class="control-label">
Password
</label>
<input type="password" id="key" name="key" class="form-control" autofocus>
</div>
<button id="enter" type="submit" class="btn btn-bright login-button"><i class="fa fa-sign-in"></i>&nbsp; Enter</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -965,11 +965,36 @@
<p class="help-block">Enable to host newsletters on your own domain. This will generate a link to an HTML page where you can view the newsletter.</p> <p class="help-block">Enable to host newsletters on your own domain. This will generate a link to an HTML page where you can view the newsletter.</p>
</div> </div>
<div id="self_host_newsletter_options" style="overlfow: hidden; display: ${'block' if config['newsletter_self_hosted'] == 'checked' else 'none'}"> <div id="self_host_newsletter_options" style="overlfow: hidden; display: ${'block' if config['newsletter_self_hosted'] == 'checked' else 'none'}">
<div class="form-group">
<p class="help-block" id="self_host_newsletter_message"> <p class="help-block" id="self_host_newsletter_message">
Note: The <span class="inline-pre">${http_root}newsletter</span> endpoint on your domain must be publicly accessible from the internet. Note: The <span class="inline-pre">${http_root}newsletter</span> endpoint on your domain must be publicly accessible from the internet.
</p> </p>
<p class="help-block settings-warning base-url-warning">Warning: Public Tautulli domain not set under <a data-tab-destination="tabs-web_interface" data-target="#http_base_url">Web Interface</a>.</p> <p class="help-block settings-warning base-url-warning">Warning: Public Tautulli domain not set under <a data-tab-destination="tabs-web_interface" data-target="#http_base_url">Web Interface</a>.</p>
</div> </div>
<div class="form-group">
<label for="newsletter_auth">Newsletter Authentication</label>
<div class="row">
<div class="col-md-6">
<select class="form-control" id="newsletter_auth" name="newsletter_auth">
<option value="0" ${'selected' if config['newsletter_auth'] == 0 else ''}>Disabled</option>
<option value="1" ${'selected' if config['newsletter_auth'] == 1 else ''}>Password</option>
<option value="2" ${'selected' if config['newsletter_auth'] == 2 else ''}>Tautulli Guest Access</option>
</select>
</div>
</div>
<p class="help-block">Select the authentication method to use for self-hosted newsletters.</p>
<p class="help-block settings-warning newsletter-guest-access-warning">Warning: Guest Access is not enabled under <a data-tab-destination="tabs-web_interface" data-target="#allow_guest_access">Web Interface</a>.</p>
</div>
<div class="form-group" id="newsletter_password_option">
<label for="newsletter_password">Newsletter Password</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="newsletter_password" name="newsletter_password" value="${config['newsletter_password']}">
</div>
</div>
<p class="help-block">Enter the password that will be required to view self-hosted newsletters.</p>
</div>
</div>
<div class="checkbox advanced-setting"> <div class="checkbox advanced-setting">
<label> <label>
@@ -1025,10 +1050,12 @@
<p class="help-block">Select where to host Plex images for notifications and newsletters.</p> <p class="help-block">Select where to host Plex images for notifications and newsletters.</p>
</div> </div>
<div id="imgur_upload_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 1 else 'block'}"> <div id="imgur_upload_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 1 else 'block'}">
<div class="form-group">
<p class="help-block" id="imgur_upload_message"> <p class="help-block" id="imgur_upload_message">
You can register a new Imgur application <a href="${anon_url('https://api.imgur.com/oauth2/addclient')}" target="_blank">here</a>.<br> You can register a new Imgur application <a href="${anon_url('https://api.imgur.com/oauth2/addclient')}" target="_blank">here</a>.<br>
Warning: Imgur uploads are rate-limited and newsletters may exceed the limit. Please use Cloudinary for newsletters instead. Warning: Imgur uploads are rate-limited and newsletters may exceed the limit. Please use Cloudinary for newsletters instead.
</p> </p>
</div>
<div class="form-group"> <div class="form-group">
<label for="imgur_client_id">Imgur Client ID</label> <label for="imgur_client_id">Imgur Client ID</label>
<div class="row"> <div class="row">
@@ -1040,13 +1067,17 @@
</div> </div>
</div> </div>
<div id="self_host_image_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 2 else 'block'}"> <div id="self_host_image_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 2 else 'block'}">
<div class="form-group">
<p class="help-block" id="self_host_image_message">Note: The <span class="inline-pre">${http_root}image</span> endpoint on your domain must be publicly accessible from the internet.</p> <p class="help-block" id="self_host_image_message">Note: The <span class="inline-pre">${http_root}image</span> endpoint on your domain must be publicly accessible from the internet.</p>
<p class="help-block settings-warning base-url-warning">Warning: Public Tautulli domain not set under <a data-tab-destination="tabs-web_interface" data-target="#http_base_url">Web Interface</a>.</p> <p class="help-block settings-warning base-url-warning">Warning: Public Tautulli domain not set under <a data-tab-destination="tabs-web_interface" data-target="#http_base_url">Web Interface</a>.</p>
</div> </div>
</div>
<div id="cloudinary_upload_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 3 else 'block'}"> <div id="cloudinary_upload_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 3 else 'block'}">
<div class="form-group">
<p class="help-block" id="imgur_upload_message"> <p class="help-block" id="imgur_upload_message">
You can sign up for Cloudinary <a href="${anon_url('https://cloudinary.com')}" target="_blank">here</a>.<br> You can sign up for Cloudinary <a href="${anon_url('https://cloudinary.com')}" target="_blank">here</a>.<br>
</p> </p>
</div>
<div class="form-group"> <div class="form-group">
<label for="cloudinary_cloud_name">Cloudinary Cloud Name</label> <label for="cloudinary_cloud_name">Cloudinary Cloud Name</label>
<div class="row"> <div class="row">
@@ -1237,7 +1268,7 @@
</span> </span>
</p> </p>
</div> </div>
<p class="form-group"> <div class="form-group">
<label>Registered Devices</label> <label>Registered Devices</label>
<p class="help-block">Register a new device using a QR code, or configure an existing device by clicking the settings icon on the right.</p> <p class="help-block">Register a new device using a QR code, or configure an existing device by clicking the settings icon on the right.</p>
<p id="app_api_msg" style="color: #eb8600;">The API must be enabled under <a data-tab-destination="tabs-web_interface" data-target="#api_enabled">Web Interface</a> to use the app.</p> <p id="app_api_msg" style="color: #eb8600;">The API must be enabled under <a data-tab-destination="tabs-web_interface" data-target="#api_enabled">Web Interface</a> to use the app.</p>
@@ -2456,6 +2487,7 @@ $(document).ready(function() {
$("#allow_guest_access").attr("disabled", false); $("#allow_guest_access").attr("disabled", false);
$("#allowGuestCheck").html(""); $("#allowGuestCheck").html("");
} }
newsletterPasswordEnabled();
} }
allowGuestAccessCheck(); allowGuestAccessCheck();
@@ -2680,6 +2712,28 @@ $(document).ready(function() {
newsletterUploadEnabled(); newsletterUploadEnabled();
}); });
function newsletterPasswordEnabled() {
if ($('#newsletter_auth').val() === '1') {
$('#newsletter_password_option').slideDown();
} else {
$('#newsletter_password_option').slideUp();
}
if ($('#newsletter_auth').val() === '2' && !($('#allow_guest_access').is(':checked'))) {
$('.newsletter-guest-access-warning').show();
} else {
$('.newsletter-guest-access-warning').hide();
}
}
newsletterPasswordEnabled();
$('#newsletter_auth').change(function () {
newsletterPasswordEnabled();
});
$('#allow_guest_access').click(function () {
newsletterPasswordEnabled();
})
$('body').on('click', 'a[data-tab-destination]', function () { $('body').on('click', 'a[data-tab-destination]', function () {
var tab = $(this).data('tab-destination'); var tab = $(this).data('tab-destination');
$("a[href=#" + tab + "]").click(); $("a[href=#" + tab + "]").click();

View File

@@ -955,7 +955,7 @@
<td class="footer" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;clear: both;margin-top: 10px;text-align: center;width: 100%;"> <td class="footer" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;clear: both;margin-top: 10px;text-align: center;width: 100%;">
<div class="footer-bar" style="margin-left: auto;margin-right: auto;width: 200px;border-top: 1px solid #E5A00D;margin-top: 25px;"></div> <div class="footer-bar" style="margin-left: auto;margin-right: auto;width: 200px;border-top: 1px solid #E5A00D;margin-top: 25px;"></div>
<div class="content-block powered-by" style="padding-bottom: 10px;padding-top: 10px;"> <div class="content-block powered-by" style="padding-bottom: 10px;padding-top: 10px;">
Newsletter generated by <a href="http://tautulli.com" target="_blank" style="text-decoration: underline;color: #fff;font-size: 12px;text-align: center;">Tautulli</a>. <!-- FOOTER MESSAGE - DO NOT REMOVE -->
</div> </div>
</td> </td>
</tr> </tr>

View File

@@ -956,7 +956,7 @@
<td class="footer"> <td class="footer">
<div class="footer-bar"></div> <div class="footer-bar"></div>
<div class="content-block powered-by"> <div class="content-block powered-by">
Newsletter generated by <a href="http://tautulli.com" target="_blank">Tautulli</a>. <!-- FOOTER MESSAGE - DO NOT REMOVE -->
</div> </div>
</td> </td>
</tr> </tr>

View File

@@ -1103,10 +1103,10 @@ class Api(object):
except KeyError: except KeyError:
raise TwitterError({'message': 'Media could not be uploaded'}) raise TwitterError({'message': 'Media could not be uploaded'})
boundary = bytes("--{0}".format(uuid4()), 'utf-8') boundary = bytes("--{0}".format(uuid4())).encode('utf-8')
media_id_bytes = bytes(str(media_id).encode('utf-8')) media_id_bytes = bytes(str(media_id).encode('utf-8'))
headers = {'Content-Type': 'multipart/form-data; boundary={0}'.format( headers = {'Content-Type': 'multipart/form-data; boundary={0}'.format(
str(boundary[2:], 'utf-8'))} str(boundary[2:]).encode('utf-8'))}
segment_id = 0 segment_id = 0
while True: while True:

View File

@@ -1723,8 +1723,8 @@ def dbcheck():
for row in result: for row in result:
img_hash = notification_handler.set_hash_image_info( img_hash = notification_handler.set_hash_image_info(
rating_key=row['rating_key'], width=1000, height=1500, fallback='poster') rating_key=row['rating_key'], width=1000, height=1500, fallback='poster')
data_factory.set_img_info(img_hash=img_hash, imgur_title=row['poster_title'], data_factory.set_img_info(img_hash=img_hash, img_title=row['poster_title'],
imgur_url=row['poster_url'], delete_hash=row['delete_hash'], img_url=row['poster_url'], delete_hash=row['delete_hash'],
service='imgur') service='imgur')
db.action('DROP TABLE poster_urls') db.action('DROP TABLE poster_urls')

View File

@@ -180,8 +180,9 @@ class ActivityProcessor(object):
if str(session['rating_key']).isdigit() and session['media_type'] in ('movie', 'episode', 'track'): if str(session['rating_key']).isdigit() and session['media_type'] in ('movie', 'episode', 'track'):
logging_enabled = True logging_enabled = True
else: else:
logger.debug(u"Tautulli ActivityProcessor :: ratingKey %s not logged. Does not meet logging criteria. " logger.debug(u"Tautulli ActivityProcessor :: Session %s ratingKey %s not logged. "
u"Media type is '%s'" % (session['rating_key'], session['media_type'])) u"Does not meet logging criteria. Media type is '%s'" %
(session['session_key'], session['rating_key'], session['media_type']))
return session['id'] return session['id']
if str(session['paused_counter']).isdigit(): if str(session['paused_counter']).isdigit():
@@ -193,15 +194,16 @@ class ActivityProcessor(object):
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \ if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(real_play_time < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)): (real_play_time < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)):
logging_enabled = False logging_enabled = False
logger.debug(u"Tautulli ActivityProcessor :: Play duration for ratingKey %s is %s secs which is less than %s " logger.debug(u"Tautulli ActivityProcessor :: Play duration for session %s ratingKey %s is %s secs "
u"seconds, so we're not logging it." % u"which is less than %s seconds, so we're not logging it." %
(session['rating_key'], str(real_play_time), plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)) (session['session_key'], session['rating_key'], str(real_play_time),
plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
if not is_import and session['media_type'] == 'track': if not is_import and session['media_type'] == 'track':
if real_play_time < 15 and session['duration'] >= 30: if real_play_time < 15 and session['duration'] >= 30:
logging_enabled = False logging_enabled = False
logger.debug(u"Tautulli ActivityProcessor :: Play duration for ratingKey %s is %s secs, " logger.debug(u"Tautulli ActivityProcessor :: Play duration for session %s ratingKey %s is %s secs, "
u"looks like it was skipped so we're not logging it" % u"looks like it was skipped so we're not logging it" %
(session['rating_key'], str(real_play_time))) (session['session_key'], session['rating_key'], str(real_play_time)))
elif is_import and import_ignore_interval: elif is_import and import_ignore_interval:
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \ if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(real_play_time < int(import_ignore_interval)): (real_play_time < int(import_ignore_interval)):

View File

@@ -611,6 +611,7 @@ General optional parameters:
# if we fail to generate the output fake an error # if we fail to generate the output fake an error
except Exception as e: except Exception as e:
logger.api_exception(u'Tautulli APIv2 :: ' + traceback.format_exc()) logger.api_exception(u'Tautulli APIv2 :: ' + traceback.format_exc())
cherrypy.response.status = 500
out['message'] = traceback.format_exc() out['message'] = traceback.format_exc()
out['result'] = 'error' out['result'] = 'error'
@@ -620,6 +621,7 @@ General optional parameters:
out = xmltodict.unparse(out, pretty=True) out = xmltodict.unparse(out, pretty=True)
except Exception as e: except Exception as e:
logger.api_error(u'Tautulli APIv2 :: Failed to parse xml result') logger.api_error(u'Tautulli APIv2 :: Failed to parse xml result')
cherrypy.response.status = 500
try: try:
out['message'] = e out['message'] = e
out['result'] = 'error' out['result'] = 'error'
@@ -660,6 +662,7 @@ General optional parameters:
result = call(**self._api_kwargs) result = call(**self._api_kwargs)
except Exception as e: except Exception as e:
logger.api_error(u'Tautulli APIv2 :: Failed to run %s with %s: %s' % (self._api_cmd, self._api_kwargs, e)) logger.api_error(u'Tautulli APIv2 :: Failed to run %s with %s: %s' % (self._api_cmd, self._api_kwargs, e))
cherrypy.response.status = 400
if self._api_debug: if self._api_debug:
cherrypy.request.show_tracebacks = True cherrypy.request.show_tracebacks = True
# Reraise the exception so the traceback hits the browser # Reraise the exception so the traceback hits the browser
@@ -704,4 +707,7 @@ General optional parameters:
if ret.get('result'): if ret.get('result'):
self._api_result_type = ret.pop('result', None) self._api_result_type = ret.pop('result', None)
if self._api_result_type == 'error':
cherrypy.response.status = 500
return self._api_out_as(self._api_responds(result_type=self._api_result_type, msg=self._api_msg, data=ret)) return self._api_out_as(self._api_responds(result_type=self._api_result_type, msg=self._api_msg, data=ret))

View File

@@ -33,9 +33,9 @@ DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png" DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
DEFAULT_ART = "interfaces/default/images/art.png" DEFAULT_ART = "interfaces/default/images/art.png"
ONLINE_POSTER_THUMB = "http://tautulli.com/images/poster.png" ONLINE_POSTER_THUMB = "https://tautulli.com/images/poster.png"
ONLINE_COVER_THUMB = "http://tautulli.com/images/cover.png" ONLINE_COVER_THUMB = "https://tautulli.com/images/cover.png"
ONLINE_ART = "http://tautulli.com/images/art.png" ONLINE_ART = "https://tautulli.com/images/art.png"
MEDIA_TYPE_HEADERS = { MEDIA_TYPE_HEADERS = {
'movie': 'Movies', 'movie': 'Movies',
@@ -531,6 +531,7 @@ NEWSLETTER_PARAMETERS = [
{'name': 'Newsletter UUID', 'type': 'str', 'value': 'newsletter_uuid', 'description': 'The unique identifier for the newsletter.'}, {'name': 'Newsletter UUID', 'type': 'str', 'value': 'newsletter_uuid', 'description': 'The unique identifier for the newsletter.'},
{'name': 'Newsletter ID', 'type': 'int', 'value': 'newsletter_id', 'description': 'The unique ID number for the newsletter agent.'}, {'name': 'Newsletter ID', 'type': 'int', 'value': 'newsletter_id', 'description': 'The unique ID number for the newsletter agent.'},
{'name': 'Newsletter ID Name', 'type': 'int', 'value': 'newsletter_id_name', 'description': 'The unique ID name for the newsletter agent.'}, {'name': 'Newsletter ID Name', 'type': 'int', 'value': 'newsletter_id_name', 'description': 'The unique ID name for the newsletter agent.'},
{'name': 'Newsletter Password', 'type': 'str', 'value': 'newsletter_password', 'description': 'The password required to view the newsletter if enabled.'},
] ]
}, },
{ {

View File

@@ -312,6 +312,8 @@ _CONFIG_DEFINITIONS = {
'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0), 'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0),
'MONITORING_INTERVAL': (int, 'Monitoring', 60), 'MONITORING_INTERVAL': (int, 'Monitoring', 60),
'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0), 'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0),
'NEWSLETTER_AUTH': (int, 'Newsletter', 0),
'NEWSLETTER_PASSWORD': (str, 'Newsletter', ''),
'NEWSLETTER_CUSTOM_DIR': (str, 'Newsletter', ''), 'NEWSLETTER_CUSTOM_DIR': (str, 'Newsletter', ''),
'NEWSLETTER_INLINE_STYLES': (int, 'Newsletter', 1), 'NEWSLETTER_INLINE_STYLES': (int, 'Newsletter', 1),
'NEWSLETTER_TEMPLATES': (str, 'Newsletter', 'newsletters'), 'NEWSLETTER_TEMPLATES': (str, 'Newsletter', 'newsletters'),

View File

@@ -835,6 +835,8 @@ def cloudinary_transform(rating_key=None, width=1000, height=1500, opacity=100,
) )
img_options = {'format': img_format, img_options = {'format': img_format,
'fetch_format': 'auto',
'quality': 'auto',
'version': int(time.time())} 'version': int(time.time())}
if width != 1000: if width != 1000:

View File

@@ -19,6 +19,7 @@ from itertools import groupby
from mako.lookup import TemplateLookup from mako.lookup import TemplateLookup
from mako import exceptions from mako import exceptions
import os import os
import re
import plexpy import plexpy
import common import common
@@ -420,7 +421,7 @@ class Newsletter(object):
self.retrieve_data() self.retrieve_data()
return serve_template( newsletter_rendered = serve_template(
templatename=self._TEMPLATE, templatename=self._TEMPLATE,
uuid=self.uuid, uuid=self.uuid,
subject=self.subject_formatted, subject=self.subject_formatted,
@@ -431,6 +432,25 @@ class Newsletter(object):
preview=self.is_preview preview=self.is_preview
) )
# Force Tautulli footer
if '<!-- FOOTER MESSAGE - DO NOT REMOVE -->' in newsletter_rendered:
newsletter_rendered = newsletter_rendered.replace(
'<!-- FOOTER MESSAGE - DO NOT REMOVE -->',
'Newsletter generated by <a href="https://tautulli.com" target="_blank" '
'style="text-decoration: underline;color: #fff;font-size: 12px;">Tautulli</a>.'
)
return newsletter_rendered
else:
msg = ('<div style="text-align: center;padding-top: 100px;padding-bottom: 100px;">'
'<p style="font-family: \'Open Sans\', Helvetica, Arial, sans-serif;color: #282A2D;'
'font-size: 18px;line-height: 30px;">'
'The Tautulli newsletter footer was removed from the newsletter template.<br>'
'Please leave the footer in place as it is unobtrusive and supports '
'<a href="https://tautulli.com" target="_blank">Tautulli</a>.<br>Thank you.'
'</p></div>')
newsletter_rendered = re.sub(r'(<body.*?>)', r'\1' + msg, newsletter_rendered)
return newsletter_rendered
def send(self): def send(self):
self.newsletter = self.generate_newsletter() self.newsletter = self.generate_newsletter()
@@ -520,7 +540,8 @@ class Newsletter(object):
'newsletter_static_url': base_url + 'id/' + self.newsletter_id_name, 'newsletter_static_url': base_url + 'id/' + self.newsletter_id_name,
'newsletter_uuid': self.uuid, 'newsletter_uuid': self.uuid,
'newsletter_id': self.newsletter_id, 'newsletter_id': self.newsletter_id,
'newsletter_id_name': self.newsletter_id_name 'newsletter_id_name': self.newsletter_id_name,
'newsletter_password': plexpy.CONFIG.NEWSLETTER_PASSWORD
} }
return parameters return parameters

View File

@@ -661,9 +661,9 @@ class PrettyMetadata(object):
poster_url = self.parameters['poster_url'] poster_url = self.parameters['poster_url']
if not poster_url: if not poster_url:
if self.media_type in ('artist', 'album', 'track'): if self.media_type in ('artist', 'album', 'track'):
poster_url = 'http://tautulli.com/images/cover.png' poster_url = common.ONLINE_COVER_THUMB
else: else:
poster_url = 'http://tautulli.com/images/poster.png' poster_url = common.ONLINE_POSTER_THUMB
return poster_url return poster_url
def get_provider_name(self, provider): def get_provider_name(self, provider):
@@ -1464,7 +1464,7 @@ class FACEBOOK(Notifier):
return facebook.auth_url(app_id=app_id, return facebook.auth_url(app_id=app_id,
canvas_url=redirect_uri, canvas_url=redirect_uri,
perms=['user_managed_groups','publish_actions']) perms=['publish_to_groups'])
def _get_credentials(self, code=''): def _get_credentials(self, code=''):
logger.info(u"Tautulli Notifiers :: Requesting access token from {name}.".format(name=self.NAME)) logger.info(u"Tautulli Notifiers :: Requesting access token from {name}.".format(name=self.NAME))
@@ -3476,7 +3476,7 @@ class TWITTER(Notifier):
poster_url = parameters.get('poster_url','') poster_url = parameters.get('poster_url','')
# Hack to add media type to attachment # Hack to add media type to attachment
if poster_url: if poster_url and not helpers.get_img_service():
poster_url += '.png' poster_url += '.png'
if self.config['incl_subject']: if self.config['incl_subject']:

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta" PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.1.7-beta" PLEXPY_RELEASE_VERSION = "v2.1.8-beta"

View File

@@ -56,7 +56,7 @@ import web_socket
from plexpy.api2 import API2 from plexpy.api2 import API2
from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates, build_datatables_json from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates, build_datatables_json
from plexpy.session import get_session_info, get_session_user_id, allow_session_user, allow_session_library from plexpy.session import get_session_info, get_session_user_id, allow_session_user, allow_session_library
from plexpy.webauth import AuthController, requireAuth, member_of, name_is from plexpy.webauth import AuthController, requireAuth, member_of
def serve_template(templatename, **kwargs): def serve_template(templatename, **kwargs):
@@ -2826,6 +2826,8 @@ class WebInterface(object):
"show_advanced_settings": plexpy.CONFIG.SHOW_ADVANCED_SETTINGS, "show_advanced_settings": plexpy.CONFIG.SHOW_ADVANCED_SETTINGS,
"newsletter_dir": plexpy.CONFIG.NEWSLETTER_DIR, "newsletter_dir": plexpy.CONFIG.NEWSLETTER_DIR,
"newsletter_self_hosted": checked(plexpy.CONFIG.NEWSLETTER_SELF_HOSTED), "newsletter_self_hosted": checked(plexpy.CONFIG.NEWSLETTER_SELF_HOSTED),
"newsletter_auth": plexpy.CONFIG.NEWSLETTER_AUTH,
"newsletter_password": plexpy.CONFIG.NEWSLETTER_PASSWORD,
"newsletter_inline_styles": checked(plexpy.CONFIG.NEWSLETTER_INLINE_STYLES), "newsletter_inline_styles": checked(plexpy.CONFIG.NEWSLETTER_INLINE_STYLES),
"newsletter_custom_dir": plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR "newsletter_custom_dir": plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR
} }
@@ -5741,6 +5743,27 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
def newsletter(self, *args, **kwargs): def newsletter(self, *args, **kwargs):
request_uri = cherrypy.request.wsgi_environ['REQUEST_URI']
if plexpy.CONFIG.NEWSLETTER_AUTH == 2:
redirect_uri = request_uri.replace('/newsletter', '/newsletter_auth')
raise cherrypy.HTTPRedirect(redirect_uri)
elif plexpy.CONFIG.NEWSLETTER_AUTH == 1 and plexpy.CONFIG.NEWSLETTER_PASSWORD:
if len(args) >= 2 and args[0] == 'image':
return self.newsletter_auth(*args, **kwargs)
elif kwargs.pop('key', None) == plexpy.CONFIG.NEWSLETTER_PASSWORD:
return self.newsletter_auth(*args, **kwargs)
else:
return serve_template(templatename="newsletter_auth.html",
title="Newsletter Login",
uri=request_uri)
else:
return self.newsletter_auth(*args, **kwargs)
@cherrypy.expose
@requireAuth()
def newsletter_auth(self, *args, **kwargs):
if args: if args:
# Keep this for backwards compatibility for images through /newsletter/image # Keep this for backwards compatibility for images through /newsletter/image
if len(args) >= 2 and args[0] == 'image': if len(args) >= 2 and args[0] == 'image':