Compare commits
14 Commits
v2.1.6-bet
...
v2.1.7-bet
Author | SHA1 | Date | |
---|---|---|---|
![]() |
908dbc3243 | ||
![]() |
14b6df8c25 | ||
![]() |
d3e53cb97f | ||
![]() |
445eea5c1e | ||
![]() |
c5918d7d6c | ||
![]() |
b8e025193e | ||
![]() |
85772cdd83 | ||
![]() |
7f2bab3082 | ||
![]() |
8185cc1c40 | ||
![]() |
63bfe96124 | ||
![]() |
88b640f5e2 | ||
![]() |
26b2342956 | ||
![]() |
883280be09 | ||
![]() |
7c76b0678a |
291
API.md
291
API.md
@@ -32,6 +32,21 @@ General optional parameters:
|
||||
|
||||
## API methods
|
||||
|
||||
### add_newsletter_config
|
||||
Add a new notification agent.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
agent_id (int): The newsletter type to add
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
|
||||
|
||||
### add_notifier_config
|
||||
Add a new notification agent.
|
||||
|
||||
@@ -93,27 +108,30 @@ Returns:
|
||||
Delete and recreate the cache directory.
|
||||
|
||||
|
||||
### delete_image_cache
|
||||
Delete and recreate the image cache directory.
|
||||
|
||||
|
||||
### delete_imgur_poster
|
||||
Delete the Imgur poster.
|
||||
### delete_hosted_images
|
||||
Delete the images uploaded to image hosting services.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
None
|
||||
|
||||
Optional parameters:
|
||||
rating_key (int): 1234
|
||||
(Note: Must be the movie, show, season, artist, or album rating key)
|
||||
Optional parameters:
|
||||
None
|
||||
service (str): 'imgur' or 'cloudinary'
|
||||
delete_all (bool): 'true' to delete all images form the service
|
||||
|
||||
Returns:
|
||||
json:
|
||||
{"result": "success",
|
||||
"message": "Deleted Imgur poster."}
|
||||
"message": "Deleted hosted images from Imgur."}
|
||||
```
|
||||
|
||||
|
||||
### delete_image_cache
|
||||
Delete and recreate the image cache directory.
|
||||
|
||||
|
||||
### delete_library
|
||||
Delete a library section from Tautulli. Also erases all history for the library.
|
||||
|
||||
@@ -191,6 +209,36 @@ Returns:
|
||||
```
|
||||
|
||||
|
||||
### delete_newsletter
|
||||
Remove a newsletter from the database.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
newsletter_id (int): The newsletter to delete
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
|
||||
|
||||
### delete_newsletter_log
|
||||
Delete the Tautulli newsletter logs.
|
||||
|
||||
```
|
||||
Required paramters:
|
||||
None
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
|
||||
|
||||
### delete_notification_log
|
||||
Delete the Tautulli notification logs.
|
||||
|
||||
@@ -916,9 +964,9 @@ Optional parameters:
|
||||
|
||||
Returns:
|
||||
json:
|
||||
[{"section_id": 1, "section_name": "Movies"},
|
||||
{"section_id": 7, "section_name": "Music"},
|
||||
{"section_id": 2, "section_name": "TV Shows"},
|
||||
[{"section_id": 1, "section_name": "Movies", "section_type": "movie"},
|
||||
{"section_id": 7, "section_name": "Music", "section_type": "artist"},
|
||||
{"section_id": 2, "section_name": "TV Shows", "section_type": "show"},
|
||||
{...}
|
||||
]
|
||||
```
|
||||
@@ -1166,6 +1214,109 @@ Returns:
|
||||
```
|
||||
|
||||
|
||||
### get_newsletter_config
|
||||
Get the configuration for an existing notification agent.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
newsletter_id (int): The newsletter config to retrieve
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
json:
|
||||
{"id": 1,
|
||||
"agent_id": 0,
|
||||
"agent_name": "recently_added",
|
||||
"agent_label": "Recently Added",
|
||||
"friendly_name": "",
|
||||
"id_name": "",
|
||||
"cron": "0 0 * * 1",
|
||||
"active": 1,
|
||||
"subject": "Recently Added to {server_name}! ({end_date})",
|
||||
"body": "View the newsletter here: {newsletter_url}",
|
||||
"message": "",
|
||||
"config": {"custom_cron": 0,
|
||||
"filename": "newsletter_{newsletter_uuid}.html",
|
||||
"formatted": 1,
|
||||
"incl_libraries": ["1", "2"],
|
||||
"notifier_id": 1,
|
||||
"save_only": 0,
|
||||
"time_frame": 7,
|
||||
"time_frame_units": "days"
|
||||
},
|
||||
"email_config": {...},
|
||||
"config_options": [{...}, ...],
|
||||
"email_config_options": [{...}, ...]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### get_newsletter_log
|
||||
Get the data on the Tautulli newsletter logs table.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
None
|
||||
|
||||
Optional parameters:
|
||||
order_column (str): "timestamp", "newsletter_id", "agent_name", "notify_action",
|
||||
"subject_text", "start_date", "end_date", "uuid"
|
||||
order_dir (str): "desc" or "asc"
|
||||
start (int): Row to start from, 0
|
||||
length (int): Number of items to return, 25
|
||||
search (str): A string to search for, "Telegram"
|
||||
|
||||
Returns:
|
||||
json:
|
||||
{"draw": 1,
|
||||
"recordsTotal": 1039,
|
||||
"recordsFiltered": 163,
|
||||
"data":
|
||||
[{"agent_id": 0,
|
||||
"agent_name": "recently_added",
|
||||
"end_date": "2018-03-18",
|
||||
"id": 7,
|
||||
"newsletter_id": 1,
|
||||
"notify_action": "on_cron",
|
||||
"start_date": "2018-03-05",
|
||||
"subject_text": "Recently Added to Plex (Winterfell-Server)! (2018-03-18)",
|
||||
"success": 1,
|
||||
"timestamp": 1462253821,
|
||||
"uuid": "7fe4g65i"
|
||||
},
|
||||
{...},
|
||||
{...}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### get_newsletters
|
||||
Get a list of configured newsletters.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
None
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
json:
|
||||
[{"id": 1,
|
||||
"agent_id": 0,
|
||||
"agent_name": "recently_added",
|
||||
"agent_label": "Recently Added",
|
||||
"friendly_name": "",
|
||||
"cron": "0 0 * * 1",
|
||||
"active": 1
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
### get_notification_log
|
||||
Get the data on the Tautulli notification logs table.
|
||||
|
||||
@@ -1174,8 +1325,8 @@ Required parameters:
|
||||
None
|
||||
|
||||
Optional parameters:
|
||||
order_column (str): "timestamp", "agent_name", "notify_action",
|
||||
"subject_text", "body_text", "script_args"
|
||||
order_column (str): "timestamp", "notifier_id", "agent_name", "notify_action",
|
||||
"subject_text", "body_text",
|
||||
order_dir (str): "desc" or "asc"
|
||||
start (int): Row to start from, 0
|
||||
length (int): Number of items to return, 25
|
||||
@@ -1188,15 +1339,14 @@ Returns:
|
||||
"recordsFiltered": 163,
|
||||
"data":
|
||||
[{"agent_id": 13,
|
||||
"agent_name": "Telegram",
|
||||
"body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].",
|
||||
"agent_name": "telegram",
|
||||
"body_text": "DanyKhaleesi69 started playing The Red Woman.",
|
||||
"id": 1000,
|
||||
"notify_action": "play",
|
||||
"poster_url": "http://i.imgur.com/ZSqS8Ri.jpg",
|
||||
"notify_action": "on_play",
|
||||
"rating_key": 153037,
|
||||
"script_args": "[]",
|
||||
"session_key": 147,
|
||||
"subject_text": "Tautulli (Winterfell-Server)",
|
||||
"success": 1,
|
||||
"timestamp": 1462253821,
|
||||
"user": "DanyKhaleesi69",
|
||||
"user_id": 8008135
|
||||
@@ -1777,6 +1927,68 @@ Returns:
|
||||
```
|
||||
|
||||
|
||||
### get_stream_data
|
||||
Get the stream details from history or current stream.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
row_id (int): The row ID number for the history item, OR
|
||||
session_key (int): The session key of the current stream
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
json:
|
||||
{"aspect_ratio": "2.35",
|
||||
"audio_bitrate": 231,
|
||||
"audio_channels": 6,
|
||||
"audio_codec": "aac",
|
||||
"audio_decision": "transcode",
|
||||
"bitrate": 2731,
|
||||
"container": "mp4",
|
||||
"current_session": "",
|
||||
"grandparent_title": "",
|
||||
"media_type": "movie",
|
||||
"optimized_version": "",
|
||||
"optimized_version_profile": "",
|
||||
"optimized_version_title": "",
|
||||
"pre_tautulli": "",
|
||||
"quality_profile": "1.5 Mbps 480p",
|
||||
"stream_audio_bitrate": 203,
|
||||
"stream_audio_channels": 2,
|
||||
"stream_audio_codec": "aac",
|
||||
"stream_audio_decision": "transcode",
|
||||
"stream_bitrate": 730,
|
||||
"stream_container": "mkv",
|
||||
"stream_container_decision": "transcode",
|
||||
"stream_subtitle_codec": "",
|
||||
"stream_subtitle_decision": "",
|
||||
"stream_video_bitrate": 527,
|
||||
"stream_video_codec": "h264",
|
||||
"stream_video_decision": "transcode",
|
||||
"stream_video_framerate": "24p",
|
||||
"stream_video_height": 306,
|
||||
"stream_video_resolution": "SD",
|
||||
"stream_video_width": 720,
|
||||
"subtitle_codec": "",
|
||||
"subtitles": "",
|
||||
"synced_version": "",
|
||||
"synced_version_profile": "",
|
||||
"title": "Frozen",
|
||||
"transcode_hw_decoding": "",
|
||||
"transcode_hw_encoding": "",
|
||||
"video_bitrate": 2500,
|
||||
"video_codec": "h264",
|
||||
"video_decision": "transcode",
|
||||
"video_framerate": "24p",
|
||||
"video_height": 816,
|
||||
"video_resolution": "1080",
|
||||
"video_width": 1920
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### get_stream_type_by_top_10_platforms
|
||||
Get graph data by stream type by top 10 platforms.
|
||||
|
||||
@@ -2215,6 +2427,23 @@ Returns:
|
||||
```
|
||||
|
||||
|
||||
### notify_newsletter
|
||||
Send a newsletter using Tautulli.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
newsletter_id (int): The ID number of the newsletter agent
|
||||
|
||||
Optional parameters:
|
||||
subject (str): The subject of the newsletter
|
||||
body (str): The body of the newsletter
|
||||
message (str): The message of the newsletter
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
|
||||
|
||||
### notify_recently_added
|
||||
Send a recently added notification using Tautulli.
|
||||
|
||||
@@ -2244,8 +2473,12 @@ Required parameters:
|
||||
rating_key (str): 54321
|
||||
|
||||
Optional parameters:
|
||||
width (str): 150
|
||||
height (str): 255
|
||||
width (str): 300
|
||||
height (str): 450
|
||||
opacity (str): 25
|
||||
background (str): 282828
|
||||
blur (str): 3
|
||||
img_format (str): png
|
||||
fallback (str): "poster", "cover", "art"
|
||||
refresh (bool): True or False whether to refresh the image cache
|
||||
|
||||
@@ -2326,6 +2559,22 @@ Returns:
|
||||
```
|
||||
|
||||
|
||||
### set_newsletter_config
|
||||
Configure an exisitng newsletter agent.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
newsletter_id (int): The newsletter config to update
|
||||
agent_id (int): The newsletter type of the newsletter
|
||||
|
||||
Optional parameters:
|
||||
Pass all the config options for the agent with the 'newsletter_config_' and 'newsletter_email_' prefix.
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
|
||||
|
||||
### set_notifier_config
|
||||
Configure an exisitng notificaiton agent.
|
||||
|
||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## v2.1.7-beta (2018-05-13)
|
||||
|
||||
* Newsletters:
|
||||
* New: Option to toggle between inline or internal CSS style templates.
|
||||
* New: Button to delete all uploaded images from Imgur/Cloudinary.
|
||||
* Fix: Long titles overflowing the newsletter cards.
|
||||
* Change: Self-hosted images on newsletters to use the /image endpoint instead of proxying through /newsletter/image.
|
||||
* Change: Strip whitespace from newsletter for smaller file size before sending to email.
|
||||
* API:
|
||||
* New: Added get_stream_data command to API.
|
||||
* New: Added newsletter API commands to documentation.
|
||||
|
||||
|
||||
## v2.1.6-beta (2018-05-09)
|
||||
|
||||
* Newsletters:
|
||||
|
@@ -69,7 +69,7 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
<tr>
|
||||
<td>Platform:</td>
|
||||
<td>${common.PLATFORM} ${common.PLATFORM_VERSION}</td>
|
||||
<td>${common.PLATFORM} ${common.PLATFORM_RELEASE} (${common.PLATFORM_VERSION + (' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else '')})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python Version:</td>
|
||||
|
@@ -70,6 +70,7 @@ div.form-control .selectize-input {
|
||||
background-color: #555;
|
||||
border-radius: 3px;
|
||||
transition: background-color .3s;
|
||||
height: 32px !important;
|
||||
}
|
||||
.react-selectize.root-node .react-selectize-control,
|
||||
.selectize-control.form-control .selectize-input {
|
||||
|
@@ -971,6 +971,15 @@
|
||||
<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 class="checkbox advanced-setting">
|
||||
<label>
|
||||
<input type="checkbox" id="newsletter_inline_styles" name="newsletter_inline_styles" value="1" ${config['newsletter_inline_styles']}> Use Inline Styles Template
|
||||
</label>
|
||||
<p class="help-block">
|
||||
Enable to use newsletter templates with inline CSS styles. Inline styles render better in email clients, but are larger in size which may cause long newsletters to be clipped.<br>
|
||||
Note: This setting does not affect custom templates. CSS styles will depend on your own template.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="newsletter_dir">Custom Newsletter Templates Folder</label>
|
||||
<div class="row">
|
||||
@@ -998,12 +1007,19 @@
|
||||
<label for="notify_upload_posters">Image Hosting</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<select class="form-control" id="notify_upload_posters" name="notify_upload_posters">
|
||||
<option value="0" ${'selected' if config['notify_upload_posters'] == 0 else ''}>Disabled</option>
|
||||
<option value="1" ${'selected' if config['notify_upload_posters'] == 1 else ''}>Imgur</option>
|
||||
<option value="3" ${'selected' if config['notify_upload_posters'] == 3 else ''}>Cloudinary</option>
|
||||
<option value="2" ${'selected' if config['notify_upload_posters'] == 2 else ''}>Self-hosted on public Tautulli domain</option>
|
||||
</select>
|
||||
<div class="${'input-group' if config['notify_upload_posters'] in (1, 3) else ''}">
|
||||
<select class="form-control" id="notify_upload_posters" name="notify_upload_posters">
|
||||
<option value="0" ${'selected' if config['notify_upload_posters'] == 0 else ''}>Disabled</option>
|
||||
<option value="1" ${'selected' if config['notify_upload_posters'] == 1 else ''}>Imgur</option>
|
||||
<option value="3" ${'selected' if config['notify_upload_posters'] == 3 else ''}>Cloudinary</option>
|
||||
<option value="2" ${'selected' if config['notify_upload_posters'] == 2 else ''}>Self-hosted on public domain</option>
|
||||
</select>
|
||||
% if config['notify_upload_posters'] in (1, 3):
|
||||
<span class="input-group-btn" id="delete_all_uploads_container">
|
||||
<button class="btn btn-form" type="button" id="delete_all_uploads">Delete All Uploads</button>
|
||||
</span>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Select where to host Plex images for notifications and newsletters.</p>
|
||||
@@ -2606,11 +2622,37 @@ $(document).ready(function() {
|
||||
} else {
|
||||
$('#cloudinary_upload_options').slideUp();
|
||||
}
|
||||
var parent;
|
||||
if (upload_val === '1' || upload_val === '3') {
|
||||
parent = $('#notify_upload_posters').parent();
|
||||
if ($('#delete_all_uploads_container').length === 0){
|
||||
parent.addClass('input-group');
|
||||
parent.append(
|
||||
'<span class="input-group-btn" id="delete_all_uploads_container">' +
|
||||
'<button class="btn btn-form" type="button" id="delete_all_uploads">Delete All Uploads</button>' +
|
||||
'</span>');
|
||||
}
|
||||
} else {
|
||||
parent = $('#notify_upload_posters').parent();
|
||||
parent.removeClass('input-group');
|
||||
$('#delete_all_uploads_container').remove();
|
||||
}
|
||||
}
|
||||
$('#notify_upload_posters').change(function () {
|
||||
imageUpload();
|
||||
});
|
||||
|
||||
$('body').on('click', '#delete_all_uploads', function () {
|
||||
var image_hosting_option = $('#notify_upload_posters').find(':selected');
|
||||
var name = image_hosting_option.text();
|
||||
|
||||
var msg = 'Are you sure you want to delete all uploaded images on <strong>' + name + '</strong>?' +
|
||||
'<br />All previous links to the images will no longer work. This cannot be undone!';
|
||||
var url = 'delete_hosted_images';
|
||||
var data = { service: name, delete_all: true };
|
||||
confirmAjaxCall(url, msg, data, false);
|
||||
});
|
||||
|
||||
function baseURLSet() {
|
||||
if ($('#http_base_url').val()) {
|
||||
$('.base-url-warning').hide();
|
||||
|
@@ -13,9 +13,9 @@
|
||||
|
||||
service = get_img_service(include_self=True)
|
||||
if service == 'self-hosted' and plexpy.CONFIG.HTTP_BASE_URL:
|
||||
base_url_image = plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'newsletter/image/'
|
||||
elif service and service != 'self-hosted' and preview:
|
||||
base_url_image = 'newsletter/image/'
|
||||
base_url_image = plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'image/'
|
||||
elif preview and service and service != 'self-hosted':
|
||||
base_url_image = 'image/'
|
||||
else:
|
||||
base_url_image = ''
|
||||
|
||||
@@ -68,17 +68,14 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 1042px;
|
||||
padding: 10px;
|
||||
width: 1042px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
@@ -172,88 +169,9 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn > tbody > tr > td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.mb5 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
@@ -263,9 +181,6 @@
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
@@ -324,6 +239,7 @@
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
MEDIA SECTIONS
|
||||
------------------------------------- */
|
||||
@@ -378,6 +294,7 @@
|
||||
font-size: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
MEDIA CARDS
|
||||
------------------------------------- */
|
||||
@@ -480,21 +397,21 @@
|
||||
padding-left: 5px;
|
||||
}
|
||||
.card-info-footer .badge-container {
|
||||
max-width: 265px;
|
||||
max-width: 260px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.card-info-footer .star-rating-container {
|
||||
width: 60px;
|
||||
vertical-align: bottom;
|
||||
padding-right: 5px;
|
||||
width: 65px;
|
||||
}
|
||||
.card-info-footer .star-rating {
|
||||
margin-left: 4px;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
width: 0.5rem;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.star-rating.full {
|
||||
color: #E5A00D;
|
||||
@@ -505,6 +422,7 @@
|
||||
.badge {
|
||||
display: inline-block;
|
||||
min-width: 10px;
|
||||
margin-right: 4px;
|
||||
padding: 3px 7px;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
@@ -609,21 +527,12 @@
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;font-size: 14px;line-height: 1.4;margin: 0;padding: 0;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
||||
% if preview and service:
|
||||
<body style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;font-size: 14px;line-height: 1.4;margin: 0;padding: 0;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
||||
% if preview and service and service != 'self-hosted':
|
||||
<div class="local-preview-note" style="text-align: center;padding-top: 10px;"><p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;color: #282A2D;font-size: 12px;">Note: Local preview images only - images will be uploaded to ${service.capitalize()} when the newsletter is sent.</p></div> <!-- IGNORE SAVE -->
|
||||
% elif preview and not service:
|
||||
<div class="local-preview-note" style="text-align: center;padding-top: 10px;"><p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;color: #282A2D;font-size: 12px;">Warning: The Image Hosting setting must be enabled for images to display on the newsletter.</p></div> <!-- IGNORE SAVE -->
|
||||
@@ -632,19 +541,13 @@
|
||||
<tr>
|
||||
<td class="container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;display: block;max-width: 1042px;padding: 10px;width: 1042px;margin: 0 auto !important;">
|
||||
<div class="content" style="box-sizing: border-box;display: block;margin: 0 auto;max-width: 1037px;padding: 10px;">
|
||||
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader" style="color: transparent;display: none;height: 0;max-height: 0;max-width: 0;opacity: 0;overflow: hidden;mso-hide: all;visibility: hidden;width: 0;">Tautulli Newsletter - ${subject}</span>
|
||||
|
||||
% if base_url and not preview:
|
||||
<div class="view-full" style="clear: both;color: #282A2D;font-size: 12px;margin-bottom: 10px;text-align: center;width: 100%;"> <!-- IGNORE SAVE -->
|
||||
<a href="${base_url + uuid}" title="View full newsletter" target="_blank" style="text-decoration: underline;color: #282A2D;">Click here to view the full newsletter.</a> <!-- IGNORE SAVE -->
|
||||
</div> <!-- IGNORE SAVE -->
|
||||
% endif
|
||||
|
||||
<table border="0" cellpadding="3" cellspacing="0" class="main" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background: #282A2D;border-radius: 3px;color: #ffffff;">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;box-sizing: border-box;padding: 5px;overflow: auto;">
|
||||
<div class="header" style="width: 100%;height: 90px;text-align: center;">
|
||||
@@ -654,7 +557,6 @@
|
||||
<div class="dates" style="color: #aaaaaa;font-size: 20px;text-align: center;">${parameters['start_date']} - ${parameters['end_date']}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
% if message:
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;box-sizing: border-box;padding: 5px;overflow: auto;">
|
||||
@@ -663,7 +565,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
|
||||
% if recently_added.get('movie'):
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;box-sizing: border-box;padding: 5px;overflow: auto;">
|
||||
@@ -679,109 +580,105 @@
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
% for movie_a, movie_b in grouper(recently_added['movie'], 2):
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
% for movie in (movie_a, movie_b):
|
||||
% if movie:
|
||||
% if not movie_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
<td align="center" valign="top" class="card-instance movie" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 3px;width: 502px;min-width: 502px;max-width: 502px;height: 233px;">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + movie['art_hash']) if base_url_image else movie['art_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #282828;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tbody>
|
||||
% for movie_a, movie_b in grouper(recently_added['movie'], 2):
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% for movie in (movie_a, movie_b):
|
||||
% if movie:
|
||||
% if not movie_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
<td align="center" valign="top" class="card-instance movie" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 3px;width: 502px;min-width: 502px;max-width: 502px;height: 233px;">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + movie['art_hash']) if base_url_image else movie['art_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #282828;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td class="card-poster-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 152px;min-width: 152px;height: 227px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + movie['thumb_hash']) if base_url_image else movie['poster_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td class="card-poster-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 152px;min-width: 152px;height: 227px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + movie['thumb_hash']) if base_url_image else movie['poster_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
<td class="card-info-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;padding-left: 4px;text-align: left;height: 227px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${movie['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.75rem;vertical-align: top;padding: 5px;height: 100%;">
|
||||
% if movie['tagline']:
|
||||
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
|
||||
<em>${movie['tagline']}</em>
|
||||
</p>
|
||||
% endif
|
||||
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;max-width: 325px;color: #ffffff;">
|
||||
${movie['summary'][:450] + (movie['summary'][450:] and '...')}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.6rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;padding-top: 0px;padding-right: 5px;padding-bottom: 5px;padding-left: 5px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="badge-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;max-width: 265px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
|
||||
% if movie['year']:
|
||||
<span class="badge" title="${movie['year']}" style="display: inline-block;min-width: 10px;padding: 3px 7px;font-size: 11px;line-height: 1;text-align: center;white-space: nowrap;vertical-align: middle;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${movie['year']}</span>
|
||||
% endif
|
||||
% if movie['duration']:
|
||||
<% duration = int(int(movie['duration'])/60000) %>
|
||||
<span class="badge" title="${duration} mins" style="display: inline-block;min-width: 10px;padding: 3px 7px;font-size: 11px;line-height: 1;text-align: center;white-space: nowrap;vertical-align: middle;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${duration} mins</span>
|
||||
% endif
|
||||
% if movie['genres']:
|
||||
% for genre in movie['genres'][:2]:
|
||||
<span class="badge" title="${genre}" style="display: inline-block;min-width: 10px;padding: 3px 7px;font-size: 11px;line-height: 1;text-align: center;white-space: nowrap;vertical-align: middle;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${genre}</span>
|
||||
% endfor
|
||||
% endif
|
||||
</td>
|
||||
% if movie['rating']:
|
||||
<% rating = int(round(float(movie['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(movie['rating'])/0.1)}%" align="right" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: bottom;width: 60px;padding-right: 5px;">
|
||||
% for _ in range(rating):
|
||||
<span class="star-rating full" style="font-size: 0.8rem;line-height: 1rem;width: 0.5rem;display: inline-block;color: #E5A00D;">★</span>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<span class="star-rating empty" style="font-size: 0.8rem;line-height: 1rem;width: 0.5rem;display: inline-block;color: #aaaaaa;">☆</span>
|
||||
% endfor
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="card-info-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;padding-left: 4px;text-align: left;height: 227px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;max-width: 320px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${movie['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.75rem;vertical-align: top;padding: 5px;height: 100%;">
|
||||
% if movie['tagline']:
|
||||
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
|
||||
<em>${movie['tagline']}</em>
|
||||
</p>
|
||||
% endif
|
||||
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;max-width: 325px;color: #ffffff;">
|
||||
${movie['summary'][:450] + (movie['summary'][450:] and '...')}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.6rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;padding-top: 0px;padding-right: 5px;padding-bottom: 5px;padding-left: 5px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
<td class="badge-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;max-width: 260px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% if movie['year']:
|
||||
<td class="badge" title="${movie['year']}" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${movie['year']}</td>
|
||||
% endif
|
||||
% if movie['duration']:
|
||||
<% duration = int(int(movie['duration'])/60000) %>
|
||||
<td class="badge" title="${duration} mins" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${duration} mins</td>
|
||||
% endif
|
||||
% if movie['genres']:
|
||||
% for genre in movie['genres'][:]:
|
||||
<td class="badge" title="${genre}" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${genre}</td>
|
||||
% endfor
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if movie['rating']:
|
||||
<% rating = int(round(float(movie['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(movie['rating'])/0.1)}%" align="right" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 65px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% for _ in range(rating):
|
||||
<td class="star-rating full" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.8rem;vertical-align: bottom;margin-left: 4px;line-height: 1rem;width: 0.5rem;display: inline-block;color: #E5A00D;">★</td>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<td class="star-rating empty" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.8rem;vertical-align: bottom;margin-left: 4px;line-height: 1rem;width: 0.5rem;display: inline-block;color: #aaaaaa;">☆</td>
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
% if not movie_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if not movie_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -803,142 +700,137 @@
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
% for show_a, show_b in grouper(recently_added['show'], 2):
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
% for show in (show_a, show_b):
|
||||
% if show:
|
||||
% if not show_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
<%
|
||||
if show['season_count'] == 1:
|
||||
if show['season'][0]['episode_count'] == 1:
|
||||
link_rating_key = show['season'][0]['episode'][0]['rating_key']
|
||||
else:
|
||||
link_rating_key = show['season'][0]['episode'][0]['parent_rating_key']
|
||||
else:
|
||||
link_rating_key = show['rating_key']
|
||||
%>
|
||||
<td align="center" valign="top" class="card-instance show" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 3px;width: 502px;min-width: 502px;max-width: 502px;height: 233px;">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + show['art_hash']) if base_url_image else show['art_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #282828;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tbody>
|
||||
% for show_a, show_b in grouper(recently_added['show'], 2):
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% for show in (show_a, show_b):
|
||||
% if show:
|
||||
% if not show_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
<%
|
||||
if show['season_count'] == 1:
|
||||
if show['season'][0]['episode_count'] == 1:
|
||||
link_rating_key = show['season'][0]['episode'][0]['rating_key']
|
||||
else:
|
||||
link_rating_key = show['season'][0]['episode'][0]['parent_rating_key']
|
||||
else:
|
||||
link_rating_key = show['rating_key']
|
||||
%>
|
||||
<td align="center" valign="top" class="card-instance show" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 3px;width: 502px;min-width: 502px;max-width: 502px;height: 233px;">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + show['art_hash']) if base_url_image else show['art_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #282828;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td class="card-poster-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 152px;min-width: 152px;height: 227px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + show['thumb_hash']) if base_url_image else show['poster_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td class="card-poster-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 152px;min-width: 152px;height: 227px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + show['thumb_hash']) if base_url_image else show['poster_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
<td class="card-info-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;padding-left: 4px;text-align: left;height: 227px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${show['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.75rem;vertical-align: top;padding: 5px;height: 100%;">
|
||||
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
|
||||
% if show['season_count'] > 1:
|
||||
<em>${show['season_count']} seasons /</em>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="card-info-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;padding-left: 4px;text-align: left;height: 227px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;max-width: 320px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${show['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.75rem;vertical-align: top;padding: 5px;height: 100%;">
|
||||
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
|
||||
% if show['season_count'] > 1:
|
||||
<em>${show['season_count']} seasons /</em>
|
||||
% endif
|
||||
<% total_show_episodes = sum(s['episode_count'] for s in show['season']) %>
|
||||
<em>${total_show_episodes} episode${'s' if total_show_episodes > 1 else ''}</em>
|
||||
</p>
|
||||
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
|
||||
% for i, season in enumerate(show['season'][:8]):
|
||||
% if season['episode_count'] == 1:
|
||||
Season ${season['media_index']} · Episode ${season['episode'][0]['media_index']} - ${season['episode'][0]['title']}
|
||||
% else:
|
||||
Season ${season['media_index']} · Episodes ${season['episode_range']}
|
||||
% endif
|
||||
% if i < min(show['season_count'], 7):
|
||||
<br>
|
||||
% elif i == 7 and show['season_count'] > 8:
|
||||
...plus ${show['season_count'] - 8} more seasons!
|
||||
% endif
|
||||
% endfor
|
||||
</p>
|
||||
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;max-width: 325px;color: #ffffff;">
|
||||
% if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
|
||||
${show['season'][0]['episode'][0]['summary'][:350] + (show['season'][0]['episode'][0]['summary'][350:] and '...')}
|
||||
% else:
|
||||
<% length = max(0, 350 - 50 * (show['season_count'] - 1)) %>
|
||||
% if length:
|
||||
${show['summary'][:length] + (show['summary'][length:] and '...')}
|
||||
% endif
|
||||
% endif
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.6rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;padding-top: 0px;padding-right: 5px;padding-bottom: 5px;padding-left: 5px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
<td class="badge-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;max-width: 260px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% if show['year']:
|
||||
<td class="badge" title="${show['year']}" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${show['year']}</td>
|
||||
% endif
|
||||
<% total_show_episodes = sum(s['episode_count'] for s in show['season']) %>
|
||||
<em>${total_show_episodes} episode${'s' if total_show_episodes > 1 else ''}</em>
|
||||
</p>
|
||||
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
|
||||
% for i, season in enumerate(show['season'][:8]):
|
||||
Season ${season['media_index']} ·
|
||||
% if season['episode_count'] == 1:
|
||||
Episode ${season['episode'][0]['media_index']} - ${season['episode'][0]['title']}
|
||||
% else:
|
||||
Episodes ${season['episode_range']}
|
||||
% endif
|
||||
% if i < min(show['season_count'], 7):
|
||||
<br>
|
||||
% elif i == 7 and show['season_count'] > 8:
|
||||
...plus ${show['season_count'] - 8} more seasons!
|
||||
% endif
|
||||
% if show['duration']:
|
||||
<% duration = int(int(show['duration'])/60000) %>
|
||||
<td class="badge" title="${duration} mins" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${duration} mins</td>
|
||||
% endif
|
||||
% if show['genres']:
|
||||
% for genre in show['genres'][:2]:
|
||||
<td class="badge" title="${genre}" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${genre}</td>
|
||||
% endfor
|
||||
</p>
|
||||
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;max-width: 325px;color: #ffffff;">
|
||||
% if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
|
||||
${show['season'][0]['episode'][0]['summary'][:350] + (show['season'][0]['episode'][0]['summary'][350:] and '...')}
|
||||
% else:
|
||||
<% length = max(0, 350 - 50 * (show['season_count'] - 1)) %>
|
||||
% if length:
|
||||
${show['summary'][:length] + (show['summary'][length:] and '...')}
|
||||
% endif
|
||||
% endif
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.6rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;padding-top: 0px;padding-right: 5px;padding-bottom: 5px;padding-left: 5px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="badge-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;max-width: 265px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
|
||||
% if show['year']:
|
||||
<span class="badge" title="${show['year']}" style="display: inline-block;min-width: 10px;padding: 3px 7px;font-size: 11px;line-height: 1;text-align: center;white-space: nowrap;vertical-align: middle;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${show['year']}</span>
|
||||
% endif
|
||||
% if show['duration']:
|
||||
<% duration = int(int(show['duration'])/60000) %>
|
||||
<span class="badge" title="${duration} mins" style="display: inline-block;min-width: 10px;padding: 3px 7px;font-size: 11px;line-height: 1;text-align: center;white-space: nowrap;vertical-align: middle;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${duration} mins</span>
|
||||
% endif
|
||||
% if show['genres']:
|
||||
% for genre in show['genres'][:2]:
|
||||
<span class="badge" title="${genre}" style="display: inline-block;min-width: 10px;padding: 3px 7px;font-size: 11px;line-height: 1;text-align: center;white-space: nowrap;vertical-align: middle;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${genre}</span>
|
||||
% endfor
|
||||
% endif
|
||||
</td>
|
||||
% if show['rating']:
|
||||
<% rating = int(round(float(show['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(show['rating'])/0.1)}%" align="right" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: bottom;width: 60px;padding-right: 5px;">
|
||||
% for _ in range(rating):
|
||||
<span class="star-rating full" style="font-size: 0.8rem;line-height: 1rem;width: 0.5rem;display: inline-block;color: #E5A00D;">★</span>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<span class="star-rating empty" style="font-size: 0.8rem;line-height: 1rem;width: 0.5rem;display: inline-block;color: #aaaaaa;">☆</span>
|
||||
% endfor
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if show['rating']:
|
||||
<% rating = int(round(float(show['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(show['rating'])/0.1)}%" align="right" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 65px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% for _ in range(rating):
|
||||
<td class="star-rating full" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.8rem;vertical-align: bottom;margin-left: 4px;line-height: 1rem;width: 0.5rem;display: inline-block;color: #E5A00D;">★</td>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<td class="star-rating empty" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.8rem;vertical-align: bottom;margin-left: 4px;line-height: 1rem;width: 0.5rem;display: inline-block;color: #aaaaaa;">☆</td>
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
% if not show_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if not show_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -960,124 +852,114 @@
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
% for album_a, album_b in grouper([a for artist in recently_added['artist'] for a in artist['album']], 2):
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
% for album in (album_a, album_b):
|
||||
% if album:
|
||||
% if not album_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
<td align="center" valign="top" class="card-instance album" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 3px;width: 502px;min-width: 502px;max-width: 502px;height: 158px;">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + album['art_hash']) if base_url_image else album['art_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #282828;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tbody>
|
||||
% for album_a, album_b in grouper([a for artist in recently_added['artist'] for a in artist['album']], 2):
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% for album in (album_a, album_b):
|
||||
% if album:
|
||||
% if not album_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
<td align="center" valign="top" class="card-instance album" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 3px;width: 502px;min-width: 502px;max-width: 502px;height: 158px;">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + album['art_hash']) if base_url_image else album['art_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #282828;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td class="card-poster-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 152px;min-width: 152px;height: 152px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + album['thumb_hash']) if base_url_image else album['poster_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td class="card-poster-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 152px;min-width: 152px;height: 152px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + album['thumb_hash']) if base_url_image else album['poster_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-cover.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-cover.png'}" width="150" height="150" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-cover.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-cover.png'}" width="150" height="150" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
<td class="card-info-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;padding-left: 4px;text-align: left;height: 152px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${album['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.75rem;vertical-align: top;padding: 5px;height: 82px;min-height: 82px;">
|
||||
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
|
||||
<em>${album['parent_title']} · ${album['track_count']} track${'s' if album['track_count'] > 1 else ''}</em>
|
||||
</p>
|
||||
% if artist['title'].lower() != 'various artists':
|
||||
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;max-width: 325px;color: #ffffff;">
|
||||
${album['summary'][:200] + (album['summary'][200:] and '...')}
|
||||
</p>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.6rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;padding-top: 0px;padding-right: 5px;padding-bottom: 5px;padding-left: 5px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="badge-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;max-width: 265px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
|
||||
% if album['year']:
|
||||
<span class="badge" title="${album['year']}" style="display: inline-block;min-width: 10px;padding: 3px 7px;font-size: 11px;line-height: 1;text-align: center;white-space: nowrap;vertical-align: middle;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${album['year']}</span>
|
||||
% endif
|
||||
% if album['genres']:
|
||||
% for genre in album['genres'][:2]:
|
||||
<span class="badge" title="${genre}" style="display: inline-block;min-width: 10px;padding: 3px 7px;font-size: 11px;line-height: 1;text-align: center;white-space: nowrap;vertical-align: middle;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${genre}</span>
|
||||
% endfor
|
||||
% endif
|
||||
</td>
|
||||
% if album['rating']:
|
||||
<% rating = int(round(float(album['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(album['rating'])/0.1)}%" align="right" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: bottom;width: 60px;padding-right: 5px;">
|
||||
% for _ in range(rating):
|
||||
<span class="star-rating full" style="font-size: 0.8rem;line-height: 1rem;width: 0.5rem;display: inline-block;color: #E5A00D;">★</span>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<span class="star-rating empty" style="font-size: 0.8rem;line-height: 1rem;width: 0.5rem;display: inline-block;color: #aaaaaa;">☆</span>
|
||||
% endfor
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="card-info-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;padding-left: 4px;text-align: left;height: 152px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;max-width: 320px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${album['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.75rem;vertical-align: top;padding: 5px;height: 82px;min-height: 82px;">
|
||||
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
|
||||
<em>${album['parent_title']} · ${album['track_count']} track${'s' if album['track_count'] > 1 else ''}</em>
|
||||
</p>
|
||||
% if artist['title'].lower() != 'various artists':
|
||||
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;max-width: 325px;color: #ffffff;">
|
||||
${album['summary'][:200] + (album['summary'][200:] and '...')}
|
||||
</p>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.6rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;padding-top: 0px;padding-right: 5px;padding-bottom: 5px;padding-left: 5px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
<td class="badge-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;max-width: 260px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% if album['year']:
|
||||
<td class="badge" title="${album['year']}" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${album['year']}</td>
|
||||
% endif
|
||||
% if album['genres']:
|
||||
% for genre in album['genres'][:2]:
|
||||
<td class="badge" title="${genre}" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${genre}</td>
|
||||
% endfor
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if album['rating']:
|
||||
<% rating = int(round(float(album['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(album['rating'])/0.1)}%" align="right" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 65px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
|
||||
<tr>
|
||||
% for _ in range(rating):
|
||||
<td class="star-rating full" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.8rem;vertical-align: bottom;margin-left: 4px;line-height: 1rem;width: 0.5rem;display: inline-block;color: #E5A00D;">★</td>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<td class="star-rating empty" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.8rem;vertical-align: bottom;margin-left: 4px;line-height: 1rem;width: 0.5rem;display: inline-block;color: #aaaaaa;">☆</td>
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
% if not album_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if not album_b:
|
||||
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr>
|
||||
<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%;">
|
||||
<!-- START FOOTER -->
|
||||
<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;">
|
||||
Newsletter generated by <a href="http://tautulli.com" target="_blank" style="text-decoration: underline;color: #fff;font-size: 12px;text-align: center;">Tautulli</a>.
|
||||
</div>
|
||||
<!-- END FOOTER -->
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@@ -13,9 +13,9 @@
|
||||
|
||||
service = get_img_service(include_self=True)
|
||||
if service == 'self-hosted' and plexpy.CONFIG.HTTP_BASE_URL:
|
||||
base_url_image = plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'newsletter/image/'
|
||||
elif service and service != 'self-hosted' and preview:
|
||||
base_url_image = 'newsletter/image/'
|
||||
base_url_image = plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'image/'
|
||||
elif preview and service and service != 'self-hosted':
|
||||
base_url_image = 'image/'
|
||||
else:
|
||||
base_url_image = ''
|
||||
|
||||
@@ -68,17 +68,14 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 1042px;
|
||||
padding: 10px;
|
||||
width: 1042px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
@@ -172,88 +169,9 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn > tbody > tr > td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.mb5 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
@@ -263,9 +181,6 @@
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
@@ -324,6 +239,7 @@
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
MEDIA SECTIONS
|
||||
------------------------------------- */
|
||||
@@ -378,6 +294,7 @@
|
||||
font-size: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
MEDIA CARDS
|
||||
------------------------------------- */
|
||||
@@ -451,6 +368,7 @@
|
||||
line-height: 1.2rem;
|
||||
font-size: 0.9rem;
|
||||
padding: 5px;
|
||||
max-width: 320px;
|
||||
}
|
||||
.card-info-title a {
|
||||
text-decoration: none;
|
||||
@@ -480,21 +398,21 @@
|
||||
padding-left: 5px;
|
||||
}
|
||||
.card-info-footer .badge-container {
|
||||
max-width: 265px;
|
||||
max-width: 260px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.card-info-footer .star-rating-container {
|
||||
width: 60px;
|
||||
vertical-align: bottom;
|
||||
padding-right: 5px;
|
||||
width: 65px;
|
||||
}
|
||||
.card-info-footer .star-rating {
|
||||
margin-left: 4px;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
width: 0.5rem;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.star-rating.full {
|
||||
color: #E5A00D;
|
||||
@@ -505,6 +423,7 @@
|
||||
.badge {
|
||||
display: inline-block;
|
||||
min-width: 10px;
|
||||
margin-right: 4px;
|
||||
padding: 3px 7px;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
@@ -609,21 +528,12 @@
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="">
|
||||
% if preview and service:
|
||||
<body>
|
||||
% if preview and service and service != 'self-hosted':
|
||||
<div class="local-preview-note"><p>Note: Local preview images only - images will be uploaded to ${service.capitalize()} when the newsletter is sent.</p></div> <!-- IGNORE SAVE -->
|
||||
% elif preview and not service:
|
||||
<div class="local-preview-note"><p>Warning: The Image Hosting setting must be enabled for images to display on the newsletter.</p></div> <!-- IGNORE SAVE -->
|
||||
@@ -632,19 +542,13 @@
|
||||
<tr>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader">Tautulli Newsletter - ${subject}</span>
|
||||
|
||||
% if base_url and not preview:
|
||||
<div class="view-full"> <!-- IGNORE SAVE -->
|
||||
<a href="${base_url + uuid}" title="View full newsletter" target="_blank">Click here to view the full newsletter.</a> <!-- IGNORE SAVE -->
|
||||
</div> <!-- IGNORE SAVE -->
|
||||
% endif
|
||||
|
||||
<table border="0" cellpadding="3" cellspacing="0" class="main">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
<div class="header">
|
||||
@@ -654,7 +558,6 @@
|
||||
<div class="dates">${parameters['start_date']} - ${parameters['end_date']}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
% if message:
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
@@ -663,7 +566,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
|
||||
% if recently_added.get('movie'):
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
@@ -679,109 +581,105 @@
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
% for movie_a, movie_b in grouper(recently_added['movie'], 2):
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
% for movie in (movie_a, movie_b):
|
||||
% if movie:
|
||||
% if not movie_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
<td align="center" valign="top" class="card-instance movie">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + movie['art_hash']) if base_url_image else movie['art_url']});">
|
||||
<tbody>
|
||||
% for movie_a, movie_b in grouper(recently_added['movie'], 2):
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
% for movie in (movie_a, movie_b):
|
||||
% if movie:
|
||||
% if not movie_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
<td align="center" valign="top" class="card-instance movie">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + movie['art_hash']) if base_url_image else movie['art_url']});">
|
||||
<tr>
|
||||
<td class="card-poster-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + movie['thumb_hash']) if base_url_image else movie['poster_url']})">
|
||||
<tr>
|
||||
<td class="card-poster-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + movie['thumb_hash']) if base_url_image else movie['poster_url']})">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<td>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225">
|
||||
</a>
|
||||
</td>
|
||||
<td class="card-info-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="card-info-title nowrap">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank">${movie['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body">
|
||||
% if movie['tagline']:
|
||||
<p class="nowrap mb5">
|
||||
<em>${movie['tagline']}</em>
|
||||
</p>
|
||||
% endif
|
||||
<p>
|
||||
${movie['summary'][:450] + (movie['summary'][450:] and '...')}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="badge-container">
|
||||
% if movie['year']:
|
||||
<span class="badge" title="${movie['year']}">${movie['year']}</span>
|
||||
% endif
|
||||
% if movie['duration']:
|
||||
<% duration = int(int(movie['duration'])/60000) %>
|
||||
<span class="badge" title="${duration} mins">${duration} mins</span>
|
||||
% endif
|
||||
% if movie['genres']:
|
||||
% for genre in movie['genres'][:2]:
|
||||
<span class="badge" title="${genre}">${genre}</span>
|
||||
% endfor
|
||||
% endif
|
||||
</td>
|
||||
% if movie['rating']:
|
||||
<% rating = int(round(float(movie['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(movie['rating'])/0.1)}%" align="right">
|
||||
% for _ in range(rating):
|
||||
<span class="star-rating full">★</span>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<span class="star-rating empty">☆</span>
|
||||
% endfor
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="card-info-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank">${movie['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body">
|
||||
% if movie['tagline']:
|
||||
<p class="nowrap mb5">
|
||||
<em>${movie['tagline']}</em>
|
||||
</p>
|
||||
% endif
|
||||
<p>
|
||||
${movie['summary'][:450] + (movie['summary'][450:] and '...')}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="badge-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
% if movie['year']:
|
||||
<td class="badge" title="${movie['year']}">${movie['year']}</td>
|
||||
% endif
|
||||
% if movie['duration']:
|
||||
<% duration = int(int(movie['duration'])/60000) %>
|
||||
<td class="badge" title="${duration} mins">${duration} mins</td>
|
||||
% endif
|
||||
% if movie['genres']:
|
||||
% for genre in movie['genres'][:]:
|
||||
<td class="badge" title="${genre}">${genre}</td>
|
||||
% endfor
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if movie['rating']:
|
||||
<% rating = int(round(float(movie['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(movie['rating'])/0.1)}%" align="right">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
% for _ in range(rating):
|
||||
<td class="star-rating full">★</td>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<td class="star-rating empty">☆</td>
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
% if not movie_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if not movie_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -803,142 +701,137 @@
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
% for show_a, show_b in grouper(recently_added['show'], 2):
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
% for show in (show_a, show_b):
|
||||
% if show:
|
||||
% if not show_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
<%
|
||||
if show['season_count'] == 1:
|
||||
if show['season'][0]['episode_count'] == 1:
|
||||
link_rating_key = show['season'][0]['episode'][0]['rating_key']
|
||||
else:
|
||||
link_rating_key = show['season'][0]['episode'][0]['parent_rating_key']
|
||||
else:
|
||||
link_rating_key = show['rating_key']
|
||||
%>
|
||||
<td align="center" valign="top" class="card-instance show">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + show['art_hash']) if base_url_image else show['art_url']});">
|
||||
<tbody>
|
||||
% for show_a, show_b in grouper(recently_added['show'], 2):
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
% for show in (show_a, show_b):
|
||||
% if show:
|
||||
% if not show_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
<%
|
||||
if show['season_count'] == 1:
|
||||
if show['season'][0]['episode_count'] == 1:
|
||||
link_rating_key = show['season'][0]['episode'][0]['rating_key']
|
||||
else:
|
||||
link_rating_key = show['season'][0]['episode'][0]['parent_rating_key']
|
||||
else:
|
||||
link_rating_key = show['rating_key']
|
||||
%>
|
||||
<td align="center" valign="top" class="card-instance show">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + show['art_hash']) if base_url_image else show['art_url']});">
|
||||
<tr>
|
||||
<td class="card-poster-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + show['thumb_hash']) if base_url_image else show['poster_url']})">
|
||||
<tr>
|
||||
<td class="card-poster-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + show['thumb_hash']) if base_url_image else show['poster_url']})">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<td>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225">
|
||||
</a>
|
||||
</td>
|
||||
<td class="card-info-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="card-info-title nowrap">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank">${show['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body">
|
||||
<p class="nowrap mb5">
|
||||
% if show['season_count'] > 1:
|
||||
<em>${show['season_count']} seasons /</em>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="card-info-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank">${show['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body">
|
||||
<p class="nowrap mb5">
|
||||
% if show['season_count'] > 1:
|
||||
<em>${show['season_count']} seasons /</em>
|
||||
% endif
|
||||
<% total_show_episodes = sum(s['episode_count'] for s in show['season']) %>
|
||||
<em>${total_show_episodes} episode${'s' if total_show_episodes > 1 else ''}</em>
|
||||
</p>
|
||||
<p class="nowrap mb5">
|
||||
% for i, season in enumerate(show['season'][:8]):
|
||||
% if season['episode_count'] == 1:
|
||||
Season ${season['media_index']} · Episode ${season['episode'][0]['media_index']} - ${season['episode'][0]['title']}
|
||||
% else:
|
||||
Season ${season['media_index']} · Episodes ${season['episode_range']}
|
||||
% endif
|
||||
% if i < min(show['season_count'], 7):
|
||||
<br>
|
||||
% elif i == 7 and show['season_count'] > 8:
|
||||
...plus ${show['season_count'] - 8} more seasons!
|
||||
% endif
|
||||
% endfor
|
||||
</p>
|
||||
<p>
|
||||
% if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
|
||||
${show['season'][0]['episode'][0]['summary'][:350] + (show['season'][0]['episode'][0]['summary'][350:] and '...')}
|
||||
% else:
|
||||
<% length = max(0, 350 - 50 * (show['season_count'] - 1)) %>
|
||||
% if length:
|
||||
${show['summary'][:length] + (show['summary'][length:] and '...')}
|
||||
% endif
|
||||
% endif
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="badge-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
% if show['year']:
|
||||
<td class="badge" title="${show['year']}">${show['year']}</td>
|
||||
% endif
|
||||
<% total_show_episodes = sum(s['episode_count'] for s in show['season']) %>
|
||||
<em>${total_show_episodes} episode${'s' if total_show_episodes > 1 else ''}</em>
|
||||
</p>
|
||||
<p class="nowrap mb5">
|
||||
% for i, season in enumerate(show['season'][:8]):
|
||||
Season ${season['media_index']} ·
|
||||
% if season['episode_count'] == 1:
|
||||
Episode ${season['episode'][0]['media_index']} - ${season['episode'][0]['title']}
|
||||
% else:
|
||||
Episodes ${season['episode_range']}
|
||||
% endif
|
||||
% if i < min(show['season_count'], 7):
|
||||
<br>
|
||||
% elif i == 7 and show['season_count'] > 8:
|
||||
...plus ${show['season_count'] - 8} more seasons!
|
||||
% endif
|
||||
% if show['duration']:
|
||||
<% duration = int(int(show['duration'])/60000) %>
|
||||
<td class="badge" title="${duration} mins">${duration} mins</td>
|
||||
% endif
|
||||
% if show['genres']:
|
||||
% for genre in show['genres'][:2]:
|
||||
<td class="badge" title="${genre}">${genre}</td>
|
||||
% endfor
|
||||
</p>
|
||||
<p>
|
||||
% if show['season_count'] == 1 and show['season'][0]['episode_count'] == 1:
|
||||
${show['season'][0]['episode'][0]['summary'][:350] + (show['season'][0]['episode'][0]['summary'][350:] and '...')}
|
||||
% else:
|
||||
<% length = max(0, 350 - 50 * (show['season_count'] - 1)) %>
|
||||
% if length:
|
||||
${show['summary'][:length] + (show['summary'][length:] and '...')}
|
||||
% endif
|
||||
% endif
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="badge-container">
|
||||
% if show['year']:
|
||||
<span class="badge" title="${show['year']}">${show['year']}</span>
|
||||
% endif
|
||||
% if show['duration']:
|
||||
<% duration = int(int(show['duration'])/60000) %>
|
||||
<span class="badge" title="${duration} mins">${duration} mins</span>
|
||||
% endif
|
||||
% if show['genres']:
|
||||
% for genre in show['genres'][:2]:
|
||||
<span class="badge" title="${genre}">${genre}</span>
|
||||
% endfor
|
||||
% endif
|
||||
</td>
|
||||
% if show['rating']:
|
||||
<% rating = int(round(float(show['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(show['rating'])/0.1)}%" align="right">
|
||||
% for _ in range(rating):
|
||||
<span class="star-rating full">★</span>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<span class="star-rating empty">☆</span>
|
||||
% endfor
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if show['rating']:
|
||||
<% rating = int(round(float(show['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(show['rating'])/0.1)}%" align="right">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
% for _ in range(rating):
|
||||
<td class="star-rating full">★</td>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<td class="star-rating empty">☆</td>
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
% if not show_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if not show_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -960,124 +853,114 @@
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
% for album_a, album_b in grouper([a for artist in recently_added['artist'] for a in artist['album']], 2):
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
% for album in (album_a, album_b):
|
||||
% if album:
|
||||
% if not album_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
<td align="center" valign="top" class="card-instance album">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + album['art_hash']) if base_url_image else album['art_url']});">
|
||||
<tbody>
|
||||
% for album_a, album_b in grouper([a for artist in recently_added['artist'] for a in artist['album']], 2):
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
% for album in (album_a, album_b):
|
||||
% if album:
|
||||
% if not album_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
<td align="center" valign="top" class="card-instance album">
|
||||
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + album['art_hash']) if base_url_image else album['art_url']});">
|
||||
<tr>
|
||||
<td class="card-poster-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + album['thumb_hash']) if base_url_image else album['poster_url']})">
|
||||
<tr>
|
||||
<td class="card-poster-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + album['thumb_hash']) if base_url_image else album['poster_url']})">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-cover.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-cover.png'}" width="150" height="150">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<td>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-cover.png' if base_url_image else 'http://tautulli.com/images/newsletter/view-on-plex-cover.png'}" width="150" height="150">
|
||||
</a>
|
||||
</td>
|
||||
<td class="card-info-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="card-info-title nowrap">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank">${album['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body">
|
||||
<p class="nowrap mb5">
|
||||
<em>${album['parent_title']} · ${album['track_count']} track${'s' if album['track_count'] > 1 else ''}</em>
|
||||
</p>
|
||||
% if artist['title'].lower() != 'various artists':
|
||||
<p>
|
||||
${album['summary'][:200] + (album['summary'][200:] and '...')}
|
||||
</p>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="badge-container">
|
||||
% if album['year']:
|
||||
<span class="badge" title="${album['year']}">${album['year']}</span>
|
||||
% endif
|
||||
% if album['genres']:
|
||||
% for genre in album['genres'][:2]:
|
||||
<span class="badge" title="${genre}">${genre}</span>
|
||||
% endfor
|
||||
% endif
|
||||
</td>
|
||||
% if album['rating']:
|
||||
<% rating = int(round(float(album['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(album['rating'])/0.1)}%" align="right">
|
||||
% for _ in range(rating):
|
||||
<span class="star-rating full">★</span>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<span class="star-rating empty">☆</span>
|
||||
% endfor
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="card-info-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank">${album['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-body">
|
||||
<p class="nowrap mb5">
|
||||
<em>${album['parent_title']} · ${album['track_count']} track${'s' if album['track_count'] > 1 else ''}</em>
|
||||
</p>
|
||||
% if artist['title'].lower() != 'various artists':
|
||||
<p>
|
||||
${album['summary'][:200] + (album['summary'][200:] and '...')}
|
||||
</p>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="card-info-footer nowrap">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="badge-container">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
% if album['year']:
|
||||
<td class="badge" title="${album['year']}">${album['year']}</td>
|
||||
% endif
|
||||
% if album['genres']:
|
||||
% for genre in album['genres'][:2]:
|
||||
<td class="badge" title="${genre}">${genre}</td>
|
||||
% endfor
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if album['rating']:
|
||||
<% rating = int(round(float(album['rating']) / 2)) %>
|
||||
<td class="star-rating-container" title="${int(float(album['rating'])/0.1)}%" align="right">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
% for _ in range(rating):
|
||||
<td class="star-rating full">★</td>
|
||||
% endfor
|
||||
% for _ in range(5-rating):
|
||||
<td class="star-rating empty">☆</td>
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% endif
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
% if not album_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
% if not album_b:
|
||||
<td align="center" valign="top" class="card-instance pad"></td>
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr>
|
||||
<td class="footer">
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer-bar"></div>
|
||||
<div class="content-block powered-by">
|
||||
Newsletter generated by <a href="http://tautulli.com" target="_blank">Tautulli</a>.
|
||||
</div>
|
||||
<!-- END FOOTER -->
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
@@ -152,6 +152,17 @@ def initialize(config_file):
|
||||
logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR,
|
||||
verbose=VERBOSE)
|
||||
|
||||
logger.info(u"Starting Tautulli {}".format(
|
||||
common.RELEASE
|
||||
))
|
||||
logger.info(u"{} {} ({}{})".format(
|
||||
common.PLATFORM, common.PLATFORM_RELEASE, common.PLATFORM_VERSION,
|
||||
' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else ''
|
||||
))
|
||||
logger.info(u"Python {}".format(
|
||||
sys.version
|
||||
))
|
||||
|
||||
if not CONFIG.BACKUP_DIR:
|
||||
CONFIG.BACKUP_DIR = os.path.join(DATA_DIR, 'backups')
|
||||
if not os.path.exists(CONFIG.BACKUP_DIR):
|
||||
@@ -1812,7 +1823,8 @@ def initialize_tracker():
|
||||
'appVersion': common.RELEASE,
|
||||
'appId': plexpy.INSTALL_TYPE,
|
||||
'appInstallerId': plexpy.CONFIG.GIT_BRANCH,
|
||||
'dimension1': '{} {}'.format(common.PLATFORM, common.PLATFORM_VERSION), # App Platform
|
||||
'dimension1': '{} {}'.format(common.PLATFORM, common.PLATFORM_RELEASE), # App Platform
|
||||
'dimension2': common.PLATFORM_LINUX_DISTRO, # Linux Distro
|
||||
'userLanguage': plexpy.SYS_LANGUAGE,
|
||||
'documentEncoding': plexpy.SYS_ENCODING,
|
||||
'noninteractive': True
|
||||
|
@@ -37,6 +37,8 @@ import logger
|
||||
import mobile_app
|
||||
import notification_handler
|
||||
import notifiers
|
||||
import newsletter_handler
|
||||
import newsletters
|
||||
import users
|
||||
|
||||
|
||||
@@ -443,6 +445,51 @@ class API2:
|
||||
|
||||
return
|
||||
|
||||
def notify_newsletter(self, newsletter_id='', subject='', body='', message='', **kwargs):
|
||||
""" Send a newsletter using Tautulli.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
newsletter_id (int): The ID number of the newsletter agent
|
||||
|
||||
Optional parameters:
|
||||
subject (str): The subject of the newsletter
|
||||
body (str): The body of the newsletter
|
||||
message (str): The message of the newsletter
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
"""
|
||||
if not newsletter_id:
|
||||
self._api_msg = 'Newsletter failed: no newsletter id provided.'
|
||||
self._api_result_type = 'error'
|
||||
return
|
||||
|
||||
newsletter = newsletters.get_newsletter_config(newsletter_id=newsletter_id)
|
||||
|
||||
if not newsletter:
|
||||
self._api_msg = 'Newsletter failed: invalid newsletter_id provided %s.' % newsletter_id
|
||||
self._api_result_type = 'error'
|
||||
return
|
||||
|
||||
logger.api_debug(u'Tautulli APIv2 :: Sending newsletter.')
|
||||
success = newsletter_handler.notify(newsletter_id=newsletter_id,
|
||||
notify_action='api',
|
||||
subject=subject,
|
||||
body=body,
|
||||
message=message,
|
||||
**kwargs)
|
||||
|
||||
if success:
|
||||
self._api_msg = 'Newsletter sent.'
|
||||
self._api_result_type = 'success'
|
||||
else:
|
||||
self._api_msg = 'Newsletter failed.'
|
||||
self._api_result_type = 'error'
|
||||
|
||||
return
|
||||
|
||||
def _api_make_md(self):
|
||||
""" Tries to make a API.md to simplify the api docs. """
|
||||
|
||||
|
@@ -20,11 +20,13 @@ import version
|
||||
|
||||
# Identify Our Application
|
||||
PLATFORM = platform.system()
|
||||
PLATFORM_VERSION = platform.release()
|
||||
PLATFORM_RELEASE = platform.release()
|
||||
PLATFORM_VERSION = platform.version()
|
||||
PLATFORM_LINUX_DISTRO = ' '.join(x for x in platform.linux_distribution() if x)
|
||||
BRANCH = version.PLEXPY_BRANCH
|
||||
RELEASE = version.PLEXPY_RELEASE_VERSION
|
||||
|
||||
USER_AGENT = 'Tautulli/{} ({} {})'.format(RELEASE, PLATFORM, PLATFORM_VERSION)
|
||||
USER_AGENT = 'Tautulli/{} ({} {})'.format(RELEASE, PLATFORM, PLATFORM_RELEASE)
|
||||
|
||||
DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png"
|
||||
DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
|
||||
|
@@ -313,6 +313,7 @@ _CONFIG_DEFINITIONS = {
|
||||
'MONITORING_INTERVAL': (int, 'Monitoring', 60),
|
||||
'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0),
|
||||
'NEWSLETTER_CUSTOM_DIR': (str, 'Newsletter', ''),
|
||||
'NEWSLETTER_INLINE_STYLES': (int, 'Newsletter', 1),
|
||||
'NEWSLETTER_TEMPLATES': (str, 'Newsletter', 'newsletters'),
|
||||
'NEWSLETTER_DIR': (str, 'Newsletter', ''),
|
||||
'NEWSLETTER_SELF_HOSTED': (int, 'Newsletter', 0),
|
||||
|
@@ -1215,52 +1215,64 @@ class DataFactory(object):
|
||||
|
||||
monitor_db.upsert(table, key_dict=keys, value_dict=values)
|
||||
|
||||
def delete_img_info(self, rating_key=None, service=None):
|
||||
def delete_img_info(self, rating_key=None, service='', delete_all=False):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
if rating_key:
|
||||
service = service or helpers.get_img_service()
|
||||
if not delete_all:
|
||||
service = helpers.get_img_service()
|
||||
|
||||
if service == 'imgur':
|
||||
# Delete from Imgur
|
||||
query = 'SELECT imgur_title, delete_hash, fallback FROM imgur_lookup ' \
|
||||
'JOIN image_hash_lookup ON imgur_lookup.img_hash = image_hash_lookup.img_hash ' \
|
||||
'WHERE rating_key = ? '
|
||||
args = [rating_key]
|
||||
results = monitor_db.select(query, args=args)
|
||||
|
||||
for imgur_info in results:
|
||||
if imgur_info['delete_hash']:
|
||||
helpers.delete_from_imgur(delete_hash=imgur_info['delete_hash'],
|
||||
img_title=imgur_info['imgur_title'],
|
||||
fallback=imgur_info['fallback'])
|
||||
|
||||
logger.info(u"Tautulli DataFactory :: Deleting Imgur info for rating_key %s from the database."
|
||||
% rating_key)
|
||||
result = monitor_db.action('DELETE FROM imgur_lookup WHERE img_hash '
|
||||
'IN (SELECT img_hash FROM image_hash_lookup WHERE rating_key = ?)',
|
||||
[rating_key])
|
||||
|
||||
elif service == 'cloudinary':
|
||||
# Delete from Cloudinary
|
||||
helpers.delete_from_cloudinary(rating_key=rating_key)
|
||||
|
||||
logger.info(u"Tautulli DataFactory :: Deleting Cloudinary info for rating_key %s from the database."
|
||||
% rating_key)
|
||||
result = monitor_db.action('DELETE FROM cloudinary_lookup WHERE img_hash '
|
||||
'IN (SELECT img_hash FROM image_hash_lookup WHERE rating_key = ?)',
|
||||
[rating_key])
|
||||
|
||||
else:
|
||||
logger.error(u"Tautulli DataFactory :: Unable to delete hosted images: invalid service '%s' provided."
|
||||
% service)
|
||||
|
||||
return service
|
||||
|
||||
else:
|
||||
if not rating_key and not delete_all:
|
||||
logger.error(u"Tautulli DataFactory :: Unable to delete hosted images: rating_key not provided.")
|
||||
return False
|
||||
|
||||
where = ''
|
||||
args = []
|
||||
log_msg = ''
|
||||
if rating_key:
|
||||
where = 'WHERE rating_key = ?'
|
||||
args = [rating_key]
|
||||
log_msg = ' for rating_key %s' % rating_key
|
||||
|
||||
if service.lower() == 'imgur':
|
||||
# Delete from Imgur
|
||||
query = 'SELECT imgur_title, delete_hash, fallback FROM imgur_lookup ' \
|
||||
'JOIN image_hash_lookup ON imgur_lookup.img_hash = image_hash_lookup.img_hash %s' % where
|
||||
results = monitor_db.select(query, args=args)
|
||||
|
||||
for imgur_info in results:
|
||||
if imgur_info['delete_hash']:
|
||||
helpers.delete_from_imgur(delete_hash=imgur_info['delete_hash'],
|
||||
img_title=imgur_info['imgur_title'],
|
||||
fallback=imgur_info['fallback'])
|
||||
|
||||
logger.info(u"Tautulli DataFactory :: Deleting Imgur info%s from the database."
|
||||
% log_msg)
|
||||
result = monitor_db.action('DELETE FROM imgur_lookup WHERE img_hash '
|
||||
'IN (SELECT img_hash FROM image_hash_lookup %s)' % where,
|
||||
args)
|
||||
|
||||
elif service.lower() == 'cloudinary':
|
||||
# Delete from Cloudinary
|
||||
query = 'SELECT cloudinary_title, rating_key, fallback FROM cloudinary_lookup ' \
|
||||
'JOIN image_hash_lookup ON cloudinary_lookup.img_hash = image_hash_lookup.img_hash %s ' \
|
||||
'GROUP BY rating_key' % where
|
||||
results = monitor_db.select(query, args=args)
|
||||
|
||||
for cloudinary_info in results:
|
||||
helpers.delete_from_cloudinary(rating_key=cloudinary_info['rating_key'])
|
||||
|
||||
logger.info(u"Tautulli DataFactory :: Deleting Cloudinary info%s from the database."
|
||||
% log_msg)
|
||||
result = monitor_db.action('DELETE FROM cloudinary_lookup WHERE img_hash '
|
||||
'IN (SELECT img_hash FROM image_hash_lookup %s)' % where,
|
||||
args)
|
||||
|
||||
else:
|
||||
logger.error(u"Tautulli DataFactory :: Unable to delete hosted images: invalid service '%s' provided."
|
||||
% service)
|
||||
|
||||
return service
|
||||
|
||||
def get_poster_info(self, rating_key='', metadata=None, service=None):
|
||||
poster_key = ''
|
||||
if str(rating_key).isdigit():
|
||||
|
@@ -43,7 +43,7 @@ class HTTPHandler(object):
|
||||
'X-Plex-Product': 'Tautulli',
|
||||
'X-Plex-Version': plexpy.common.RELEASE,
|
||||
'X-Plex-Platform': plexpy.common.PLATFORM,
|
||||
'X-Plex-Platform-Version': plexpy.common.PLATFORM_VERSION,
|
||||
'X-Plex-Platform-Version': plexpy.common.PLATFORM_RELEASE,
|
||||
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID,
|
||||
}
|
||||
|
||||
|
@@ -280,6 +280,9 @@ def serve_template(templatename, **kwargs):
|
||||
interface_dir = os.path.join(str(plexpy.PROG_DIR), 'data/interfaces/')
|
||||
template_dir = os.path.join(str(interface_dir), plexpy.CONFIG.NEWSLETTER_TEMPLATES)
|
||||
|
||||
if not plexpy.CONFIG.NEWSLETTER_INLINE_STYLES:
|
||||
templatename = templatename.replace('.html', '.internal.html')
|
||||
|
||||
_hplookup = TemplateLookup(directories=[template_dir], default_filters=['unicode', 'h'])
|
||||
|
||||
try:
|
||||
@@ -319,7 +322,6 @@ class Newsletter(object):
|
||||
_DEFAULT_BODY = 'View the newsletter here: {newsletter_url}'
|
||||
_DEFAULT_MESSAGE = ''
|
||||
_DEFAULT_FILENAME = 'newsletter_{newsletter_uuid}.html'
|
||||
_TEMPLATE_MASTER = ''
|
||||
_TEMPLATE = ''
|
||||
|
||||
def __init__(self, newsletter_id=None, newsletter_id_name=None, config=None, email_config=None,
|
||||
@@ -412,19 +414,14 @@ class Newsletter(object):
|
||||
'parameters': self.parameters,
|
||||
'data': self.data}
|
||||
|
||||
def generate_newsletter(self, preview=False, master=False):
|
||||
def generate_newsletter(self, preview=False):
|
||||
if preview:
|
||||
self.is_preview = True
|
||||
|
||||
if master:
|
||||
template = self._TEMPLATE_MASTER
|
||||
else:
|
||||
template = self._TEMPLATE
|
||||
|
||||
self.retrieve_data()
|
||||
|
||||
return serve_template(
|
||||
templatename=template,
|
||||
templatename=self._TEMPLATE,
|
||||
uuid=self.uuid,
|
||||
subject=self.subject_formatted,
|
||||
body=self.body_formatted,
|
||||
@@ -462,6 +459,7 @@ class Newsletter(object):
|
||||
for line in self.newsletter.encode('utf-8').splitlines():
|
||||
if '<!-- IGNORE SAVE -->' not in line:
|
||||
n_file.write(line + '\r\n')
|
||||
#n_file.write(line.strip())
|
||||
|
||||
logger.info(u"Tautulli Newsletters :: %s newsletter saved to '%s'" % (self.NAME, newsletter_file))
|
||||
except OSError as e:
|
||||
@@ -470,18 +468,26 @@ class Newsletter(object):
|
||||
|
||||
def _send(self):
|
||||
if self.config['formatted']:
|
||||
newsletter_stripped = ''.join(l.strip() for l in self.newsletter.splitlines())
|
||||
|
||||
plaintext = 'HTML email support is required to view the newsletter.\n'
|
||||
if plexpy.CONFIG.NEWSLETTER_SELF_HOSTED and plexpy.CONFIG.HTTP_BASE_URL:
|
||||
plaintext += self._DEFAULT_BODY.format(**self.parameters)
|
||||
|
||||
if self.email_config['notifier_id']:
|
||||
return send_notification(
|
||||
notifier_id=self.email_config['notifier_id'],
|
||||
subject=self.subject_formatted,
|
||||
body=self.newsletter
|
||||
body=newsletter_stripped,
|
||||
plaintext=plaintext
|
||||
)
|
||||
|
||||
else:
|
||||
email = EMAIL(config=self.email_config)
|
||||
return email.notify(
|
||||
subject=self.subject_formatted,
|
||||
body=self.newsletter
|
||||
body=newsletter_stripped,
|
||||
plaintext=plaintext
|
||||
)
|
||||
elif self.config['notifier_id']:
|
||||
return send_notification(
|
||||
@@ -600,7 +606,6 @@ class RecentlyAdded(Newsletter):
|
||||
_DEFAULT_SUBJECT = 'Recently Added to {server_name}! ({end_date})'
|
||||
_DEFAULT_BODY = 'View the newsletter here: {newsletter_url}'
|
||||
_DEFAULT_MESSAGE = ''
|
||||
_TEMPLATE_MASTER = 'recently_added_master.html'
|
||||
_TEMPLATE = 'recently_added.html'
|
||||
|
||||
def _get_recently_added(self, media_type=None):
|
||||
|
@@ -1295,11 +1295,19 @@ class EMAIL(Notifier):
|
||||
|
||||
def agent_notify(self, subject='', body='', action='', **kwargs):
|
||||
if self.config['html_support']:
|
||||
plain = MIMEText(None, 'plain', 'utf-8')
|
||||
plain.replace_header('Content-Transfer-Encoding', 'quoted-printable')
|
||||
plain.set_payload(kwargs.get('plaintext', bleach.clean(body, strip=True)), 'utf-8')
|
||||
|
||||
html = MIMEText(body, 'html', 'utf-8')
|
||||
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg.attach(MIMEText(bleach.clean(body, strip=True), 'plain', 'utf-8'))
|
||||
msg.attach(MIMEText(body, 'html', 'utf-8'))
|
||||
msg.attach(plain)
|
||||
msg.attach(html)
|
||||
else:
|
||||
msg = MIMEText(body, 'plain', 'utf-8')
|
||||
msg = MIMEText(None, 'plain', 'utf-8')
|
||||
msg.replace_header('Content-Transfer-Encoding', 'quoted-printable')
|
||||
msg.set_payload(body, 'utf-8')
|
||||
|
||||
msg['Message-ID'] = email.utils.make_msgid()
|
||||
msg['Date'] = email.utils.formatdate(localtime=True)
|
||||
|
@@ -1,2 +1,2 @@
|
||||
PLEXPY_BRANCH = "beta"
|
||||
PLEXPY_RELEASE_VERSION = "v2.1.6-beta"
|
||||
PLEXPY_RELEASE_VERSION = "v2.1.7-beta"
|
||||
|
@@ -1715,6 +1715,76 @@ class WebInterface(object):
|
||||
|
||||
return serve_template(templatename="stream_data.html", title="Stream Data", data=stream_data, user=user)
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth()
|
||||
@addtoapi('get_stream_data')
|
||||
def get_stream_data_api(self, row_id=None, session_key=None, **kwargs):
|
||||
""" Get the stream details from history or current stream.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
row_id (int): The row ID number for the history item, OR
|
||||
session_key (int): The session key of the current stream
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
json:
|
||||
{"aspect_ratio": "2.35",
|
||||
"audio_bitrate": 231,
|
||||
"audio_channels": 6,
|
||||
"audio_codec": "aac",
|
||||
"audio_decision": "transcode",
|
||||
"bitrate": 2731,
|
||||
"container": "mp4",
|
||||
"current_session": "",
|
||||
"grandparent_title": "",
|
||||
"media_type": "movie",
|
||||
"optimized_version": "",
|
||||
"optimized_version_profile": "",
|
||||
"optimized_version_title": "",
|
||||
"pre_tautulli": "",
|
||||
"quality_profile": "1.5 Mbps 480p",
|
||||
"stream_audio_bitrate": 203,
|
||||
"stream_audio_channels": 2,
|
||||
"stream_audio_codec": "aac",
|
||||
"stream_audio_decision": "transcode",
|
||||
"stream_bitrate": 730,
|
||||
"stream_container": "mkv",
|
||||
"stream_container_decision": "transcode",
|
||||
"stream_subtitle_codec": "",
|
||||
"stream_subtitle_decision": "",
|
||||
"stream_video_bitrate": 527,
|
||||
"stream_video_codec": "h264",
|
||||
"stream_video_decision": "transcode",
|
||||
"stream_video_framerate": "24p",
|
||||
"stream_video_height": 306,
|
||||
"stream_video_resolution": "SD",
|
||||
"stream_video_width": 720,
|
||||
"subtitle_codec": "",
|
||||
"subtitles": "",
|
||||
"synced_version": "",
|
||||
"synced_version_profile": "",
|
||||
"title": "Frozen",
|
||||
"transcode_hw_decoding": "",
|
||||
"transcode_hw_encoding": "",
|
||||
"video_bitrate": 2500,
|
||||
"video_codec": "h264",
|
||||
"video_decision": "transcode",
|
||||
"video_framerate": "24p",
|
||||
"video_height": 816,
|
||||
"video_resolution": "1080",
|
||||
"video_width": 1920
|
||||
}
|
||||
```
|
||||
"""
|
||||
data_factory = datafactory.DataFactory()
|
||||
stream_data = data_factory.get_stream_details(row_id, session_key)
|
||||
|
||||
return stream_data
|
||||
|
||||
@cherrypy.expose
|
||||
@requireAuth()
|
||||
def get_ip_address_details(self, ip_address=None, **kwargs):
|
||||
@@ -2756,6 +2826,7 @@ class WebInterface(object):
|
||||
"show_advanced_settings": plexpy.CONFIG.SHOW_ADVANCED_SETTINGS,
|
||||
"newsletter_dir": plexpy.CONFIG.NEWSLETTER_DIR,
|
||||
"newsletter_self_hosted": checked(plexpy.CONFIG.NEWSLETTER_SELF_HOSTED),
|
||||
"newsletter_inline_styles": checked(plexpy.CONFIG.NEWSLETTER_INLINE_STYLES),
|
||||
"newsletter_custom_dir": plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR
|
||||
}
|
||||
|
||||
@@ -2778,7 +2849,7 @@ class WebInterface(object):
|
||||
"allow_guest_access", "cache_images", "http_proxy", "http_basic_auth", "notify_concurrent_by_ip",
|
||||
"history_table_activity", "plexpy_auto_update",
|
||||
"themoviedb_lookup", "tvmaze_lookup", "http_plex_admin",
|
||||
"newsletter_self_hosted"
|
||||
"newsletter_self_hosted", "newsletter_inline_styles"
|
||||
]
|
||||
for checked_config in checked_configs:
|
||||
if checked_config not in kwargs:
|
||||
@@ -4032,6 +4103,15 @@ class WebInterface(object):
|
||||
@cherrypy.expose
|
||||
def image(self, *args, **kwargs):
|
||||
if args:
|
||||
cherrypy.response.headers['Cache-Control'] = 'max-age=3600' # 1 hour
|
||||
|
||||
if len(args) >= 2 and args[0] == 'images':
|
||||
resource_dir = os.path.join(str(plexpy.PROG_DIR), 'data/interfaces/default/')
|
||||
try:
|
||||
return serve_file(path=os.path.join(resource_dir, *args), content_type='image/png')
|
||||
except NotFound:
|
||||
return
|
||||
|
||||
img_hash = args[0].split('.')[0]
|
||||
|
||||
if img_hash in ('poster', 'cover', 'art'):
|
||||
@@ -4049,7 +4129,7 @@ class WebInterface(object):
|
||||
|
||||
if img_info:
|
||||
kwargs.update(img_info)
|
||||
return self.real_pms_image_proxy(**kwargs)
|
||||
return self.real_pms_image_proxy(refresh=True, **kwargs)
|
||||
|
||||
return
|
||||
|
||||
@@ -4170,16 +4250,18 @@ class WebInterface(object):
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def delete_hosted_images(self, rating_key='', service='', **kwargs):
|
||||
def delete_hosted_images(self, rating_key='', service='', delete_all=False, **kwargs):
|
||||
""" Delete the images uploaded to image hosting services.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
None
|
||||
|
||||
Optional parameters:
|
||||
rating_key (int): 1234
|
||||
(Note: Must be the movie, show, season, artist, or album rating key)
|
||||
Optional parameters:
|
||||
service (str): imgur or cloudinary
|
||||
(Note: Defaults to service in Image Hosting setting)
|
||||
service (str): 'imgur' or 'cloudinary'
|
||||
delete_all (bool): 'true' to delete all images form the service
|
||||
|
||||
Returns:
|
||||
json:
|
||||
@@ -4188,8 +4270,10 @@ class WebInterface(object):
|
||||
```
|
||||
"""
|
||||
|
||||
delete_all = (delete_all == 'true')
|
||||
|
||||
data_factory = datafactory.DataFactory()
|
||||
result = data_factory.delete_img_info(rating_key=rating_key, service=service)
|
||||
result = data_factory.delete_img_info(rating_key=rating_key, service=service, delete_all=delete_all)
|
||||
|
||||
if result:
|
||||
return {'result': 'success', 'message': 'Deleted hosted images from %s.' % result.capitalize()}
|
||||
@@ -5468,7 +5552,7 @@ class WebInterface(object):
|
||||
Returns:
|
||||
json:
|
||||
[{"id": 1,
|
||||
"agent_id": 13,
|
||||
"agent_id": 0,
|
||||
"agent_name": "recently_added",
|
||||
"agent_label": "Recently Added",
|
||||
"friendly_name": "",
|
||||
@@ -5528,15 +5612,24 @@ class WebInterface(object):
|
||||
Returns:
|
||||
json:
|
||||
{"id": 1,
|
||||
"agent_id": 13,
|
||||
"agent_id": 0,
|
||||
"agent_name": "recently_added",
|
||||
"agent_label": "Recently Added",
|
||||
"friendly_name": "",
|
||||
"id_name": "",
|
||||
"cron": "0 0 * * 1",
|
||||
"active": 1
|
||||
"config": {"time_frame": 7,
|
||||
"time_frame_units": "days",
|
||||
"incl_libraries": [1, 2]
|
||||
"active": 1,
|
||||
"subject": "Recently Added to {server_name}! ({end_date})",
|
||||
"body": "View the newsletter here: {newsletter_url}",
|
||||
"message": "",
|
||||
"config": {"custom_cron": 0,
|
||||
"filename": "newsletter_{newsletter_uuid}.html",
|
||||
"formatted": 1,
|
||||
"incl_libraries": ["1", "2"],
|
||||
"notifier_id": 1,
|
||||
"save_only": 0,
|
||||
"time_frame": 7,
|
||||
"time_frame_units": "days"
|
||||
},
|
||||
"email_config": {...},
|
||||
"config_options": [{...}, ...],
|
||||
@@ -5583,19 +5676,15 @@ class WebInterface(object):
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def set_newsletter_config(self, newsletter_id=None, agent_id=None, **kwargs):
|
||||
""" Configure an exisitng notificaiton agent.
|
||||
""" Configure an exisitng newsletter agent.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
newsletter_id (int): The newsletter config to update
|
||||
agent_id (int): The newsletter type of the newsletter
|
||||
newsletter_id (int): The newsletter config to update
|
||||
agent_id (int): The newsletter type of the newsletter
|
||||
|
||||
Optional parameters:
|
||||
Pass all the config options for the agent with the agent prefix:
|
||||
e.g. For Recently Added: recently_added_last_days
|
||||
recently_added_incl_movies
|
||||
recently_added_incl_shows
|
||||
recently_added_incl_artists
|
||||
Pass all the config options for the agent with the 'newsletter_config_' and 'newsletter_email_' prefix.
|
||||
|
||||
Returns:
|
||||
None
|
||||
@@ -5613,7 +5702,6 @@ class WebInterface(object):
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi("notify_newsletter")
|
||||
def send_newsletter(self, newsletter_id=None, subject='', body='', message='', notify_action='', **kwargs):
|
||||
""" Send a newsletter using Tautulli.
|
||||
|
||||
@@ -5654,6 +5742,7 @@ class WebInterface(object):
|
||||
@cherrypy.expose
|
||||
def newsletter(self, *args, **kwargs):
|
||||
if args:
|
||||
# Keep this for backwards compatibility for images through /newsletter/image
|
||||
if len(args) >= 2 and args[0] == 'image':
|
||||
if args[1] == 'images':
|
||||
resource_dir = os.path.join(str(plexpy.PROG_DIR), 'data/interfaces/default/')
|
||||
@@ -5662,8 +5751,7 @@ class WebInterface(object):
|
||||
except NotFound:
|
||||
return
|
||||
|
||||
cherrypy.response.headers['Cache-Control'] = 'max-age=2592000' # 30 days
|
||||
return self.image(args[1], refresh=True)
|
||||
return self.image(args[1])
|
||||
|
||||
if len(args) >= 2 and args[0] == 'id':
|
||||
newsletter_id_name = args[1]
|
||||
@@ -5687,7 +5775,7 @@ class WebInterface(object):
|
||||
@cherrypy.expose
|
||||
@requireAuth(member_of("admin"))
|
||||
def real_newsletter(self, newsletter_id=None, start_date=None, end_date=None,
|
||||
preview=False, master=False, raw=False, **kwargs):
|
||||
preview=False, raw=False, **kwargs):
|
||||
if newsletter_id and newsletter_id != 'None':
|
||||
newsletter = newsletters.get_newsletter_config(newsletter_id=newsletter_id)
|
||||
|
||||
@@ -5702,14 +5790,13 @@ class WebInterface(object):
|
||||
body=newsletter['body'],
|
||||
message=newsletter['message'])
|
||||
preview = (preview == 'true')
|
||||
master = (master == 'true')
|
||||
raw = (raw == 'true')
|
||||
|
||||
if raw:
|
||||
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
|
||||
return json.dumps(newsletter_agent.raw_data(preview=preview))
|
||||
|
||||
return newsletter_agent.generate_newsletter(preview=preview, master=master)
|
||||
return newsletter_agent.generate_newsletter(preview=preview)
|
||||
|
||||
logger.error(u"Failed to retrieve newsletter: Invalid newsletter_id %s" % newsletter_id)
|
||||
return "Failed to retrieve newsletter: invalid newsletter_id parameter"
|
||||
|
Reference in New Issue
Block a user