Files
Magent/backend/app/services/newsletter_email.py
T

75 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import base64
import html
from urllib.parse import urlencode
from .recap_email import document
def description(entry):
if entry['type'] == 'movie':
return f"Movie · {entry['year']}" if entry.get('year') else 'Movie'
seasons = sorted({item['season'] for item in entry['items'] if isinstance(item.get('season'), int)})
count = len(entry['items'])
labels = ', '.join('Specials' if value == 0 else str(value) for value in seasons[:8])
suffix = f" · {'Season' if len(seasons) == 1 else 'Seasons'} {labels}" if labels else ''
return f"{count} new {'episode' if count == 1 else 'episodes'}{suffix}"
def render_confirmation(username, url):
intro = f"Hi {username}, confirm your email to receive new arrivals, featured picks and announcements from your media library."
return {'subject': 'Confirm your Magent newsletter subscription',
'body_text': f'{intro}\n\nConfirm newsletter subscription: {url}\n\nThis link expires in 24 hours. If you did not request this, ignore this email.',
'body_html': document(title='Your next watch starts here.', intro=intro,
content='<p style="color:#bdb6c3;font-size:14px;line-height:1.7">A weekly look at new movies and TV updates, with posters and links to watch.</p>',
action='Confirm newsletter subscription', url=url, kicker='NEW IN YOUR LIBRARY',
footer='This link expires in 24 hours. If you did not request this, ignore this email.')}
def render(content, images, public_url, playback_url, unsubscribe_url, *, preview=False, test=False):
esc = html.escape
titles = [entry for entry in content['titles'] if entry['selected']]
body, lines, attachments = [], [], []
intro = str(content.get('intro') or '').strip()
if intro:
body.append(f'<p style="font-size:15px;line-height:1.8;color:#e5e1e4;overflow-wrap:anywhere">{esc(intro).replace(chr(10), "<br>")}</p>')
lines += [intro, '']
sections = [('Featured picks', [entry for entry in titles if entry['featured']]),
('New movies', [entry for entry in titles if not entry['featured'] and entry['type'] == 'movie']),
('Fresh episodes', [entry for entry in titles if not entry['featured'] and entry['type'] == 'series'])]
for heading, entries in sections:
if not entries:
continue
body.append(f'<h2 style="font-size:20px;margin:28px 0 8px;color:#e5e1e4">{heading}</h2>')
lines += [heading, '']
for entry in entries:
watch = playback_url + '/web/index.html#!/details?' + urlencode({'id': entry['id'], 'serverId': content['server_id']})
image_data = images.get(entry['id'])
cid = f"newsletter-{entry['id']}@magent"
if image_data:
source = 'data:image/jpeg;base64,' + base64.b64encode(image_data).decode() if preview else 'cid:' + cid
poster = f'<img src="{source}" width="80" alt="{esc(entry["title"], quote=True)}" style="display:block;width:80px;height:auto;border-radius:7px;border:0">'
if not preview:
attachments.append({'cid': cid, 'data': image_data})
else:
poster = f'<div style="width:80px;height:112px;line-height:112px;background:#353039;color:#c7bdff;text-align:center;border-radius:7px;font-size:11px">{"TV" if entry["type"] == "series" else "MOVIE"}</div>'
details = description(entry)
overview = str(entry.get('overview') or '')[:180]
copy = f'<p style="margin:8px 0;font-size:12px;line-height:1.6;color:#bdb6c3">{esc(overview)}</p>' if overview and entry['featured'] else ''
body.append(f'''<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="table-layout:fixed;border-bottom:1px solid #363338"><tr>
<td width="92" valign="top" style="padding:18px 12px 18px 0">{poster}</td><td valign="top" style="padding:18px 0;overflow-wrap:anywhere">
<h3 style="margin:0 0 8px;font-size:16px;line-height:1.4;color:#eee8f2">{esc(entry['title'])}</h3><p style="font-size:12px;line-height:1.6;color:#a69fac;margin:0 0 10px">{esc(details)}</p>{copy}
<a href="{esc(watch, quote=True)}" style="display:inline-block;padding:8px 0;color:#c7bdff;text-decoration:none;font-size:13px;font-weight:bold">Watch on Jellyfin &#8599;</a></td></tr></table>''')
lines += [entry['title'], details, watch, '']
if not titles:
body.append('<p style="font-size:14px;line-height:1.7;color:#bdb6c3">Your next discovery is waiting in your media library.</p>')
period = f"{content['period_start'][:10]} to {content['period_end'][:10]} · UTC"
footer = f'You subscribed to the Magent newsletter.<br>Arrivals recorded by Jellyfin · {esc(period)}<br><a href="{esc(unsubscribe_url, quote=True)}" style="color:#c7bdff">Unsubscribe from newsletters</a> · <a href="{esc(public_url + "/profile#newsletters", quote=True)}" style="color:#c7bdff">Email preferences</a>'
subject = ('[Test] ' if test else '') + content['subject']
return {'subject': subject, 'body_text': '\n'.join([subject, '', *lines, f'Browse Jellyfin: {playback_url}', '',
f'Arrivals recorded by Jellyfin: {period}', f'Unsubscribe from newsletters: {unsubscribe_url}',
f'Email preferences: {public_url}/profile#newsletters']),
'body_html': document(title='Whats new in your library',
intro=('This is your test edition. ' if test else '') + 'New stories for your watchlist. Find your next movie or catch up on fresh episodes.',
content=''.join(body), action='Explore Jellyfin', url=playback_url, footer=footer, kicker='YOUR NEXT WATCH'),
'inline_images': attachments}