Skip to content

Instantly share code, notes, and snippets.

@blacktwin
Last active December 18, 2018 16:06
Show Gist options
  • Save blacktwin/d100b4841510006263af8fa9767da5c9 to your computer and use it in GitHub Desktop.
Save blacktwin/d100b4841510006263af8fa9767da5c9 to your computer and use it in GitHub Desktop.
Send an email with what was added to Plex in the past week using PlexPy.
"""
Send an email with what was added to Plex in the past week using PlexPy.
Email includes title (TV: Show Name: Episode Name; Movie: Movie Title), time added, image, and summary.
"""
import requests
import sys
import time
import os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
import email.utils
import smtplib
import urllib
import cgi
import uuid
TODAY = int(time.time())
LASTWEEK = int(TODAY - 7 * 24 * 60 * 60)
## EDIT THESE SETTINGS ##
PLEXPY_APIKEY = 'XXXXX' # Your PlexPy API key
PLEXPY_URL = 'http://localhost:8181/' # Your PlexPy URL
LIBRARY_NAMES = ['My TV Shows', 'My Movies'] # Name of libraries you want to check.
# Email settings
name = '' # Your name
sender = '' # From email address
to = [sender, ''] # Whoever you want to email [sender, 'name@example.com']
# Emails will be sent as BCC.
email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
email_port = 587 # Email port (Gmail: 587)
email_username = '' # Your email username
email_password = '' # Your email password
email_subject = 'PlexPy Added Last Week Notification' # The email subject
class METAINFO(object):
def __init__(self, data=None):
d = data or {}
self.added_at = d['added_at']
self.parent_rating_key = d['parent_rating_key']
self.title = d['title']
self.rating_key = d['rating_key']
self.media_type = d['media_type']
self.grandparent_title = d['grandparent_title']
self.file_size = d['file_size']
self.thumb = d['art']
self.summary = d['summary']
def get_get_recent(section_id, start, count):
# Get the metadata for a media item. Count matters!
payload = {'apikey': PLEXPY_APIKEY,
'start': str(start),
'count': str(count),
'section_id': section_id,
'cmd': 'get_recently_added'}
try:
r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload)
response = r.json()
if response['response']['result'] == 'success':
res_data = response['response']['data']['recently_added']
return res_data
except Exception as e:
sys.stderr.write("PlexPy API 'get_recently_added' request failed: {0}.".format(e))
def get_get_metadata(rating_key):
# Get the metadata for a media item.
payload = {'apikey': PLEXPY_APIKEY,
'rating_key': rating_key,
'cmd': 'get_metadata',
'media_info': True}
try:
r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload)
response = r.json()
if response['response']['result'] == 'success':
res_data = response['response']['data']['metadata']
return METAINFO(data=res_data)
except Exception as e:
sys.stderr.write("PlexPy API 'get_metadata' request failed: {0}.".format(e))
def get_get_libraries_table():
# Get the data on the PlexPy libraries table.
payload = {'apikey': PLEXPY_APIKEY,
'cmd': 'get_libraries_table'}
try:
r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload)
response = r.json()
res_data = response['response']['data']['data']
return [d['section_id'] for d in res_data if d['section_name'] in LIBRARY_NAMES]
except Exception as e:
sys.stderr.write("PlexPy API 'get_libraries_table' request failed: {0}.".format(e))
def update_library_media_info(section_id):
# Get the data on the PlexPy media info tables.
payload = {'apikey': PLEXPY_APIKEY,
'cmd': 'get_library_media_info',
'section_id': section_id,
'refresh': True}
try:
r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload)
response = r.status_code
if response != 200:
print(r.content)
except Exception as e:
sys.stderr.write("PlexPy API 'update_library_media_info' request failed: {0}.".format(e))
def get_pms_image_proxy(thumb):
# Gets an image from the PMS and saves it to the image cache directory.
payload = {'apikey': PLEXPY_APIKEY,
'cmd': 'pms_image_proxy',
'img': thumb,
'fallback': 'art'}
try:
r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload, stream=True)
return r.url
except Exception as e:
sys.stderr.write("PlexPy API 'pms_image_proxy' request failed: {0}.".format(e))
recent_lst = []
notify_lst = []
image_lst = []
msg_text_lst = []
message_image_lst = []
# Find the libraries from LIBRARY_NAMES
glt = [lib for lib in get_get_libraries_table()]
# Update media info for libraries.
[update_library_media_info(i) for i in glt]
# Get the rating_key for what was recently added
count = 25
for section_id in glt:
start = 0
while True:
# Assume all items will be returned in descending order of added_at
recent_items = get_get_recent(section_id, start, count)
if all([recent_items]):
start += count
for item in recent_items:
if LASTWEEK <= int(item['added_at']) <= TODAY:
recent_lst.append(item['rating_key'])
continue
elif not all([recent_items]):
break
start += count
# Gather metadata on recently added media.
for i in sorted(recent_lst):
meta = get_get_metadata(str(i))
added = time.ctime(float(meta.added_at))
# Check if media was added between TODAY and LASTWEEK
if LASTWEEK <= int(meta.added_at) <= TODAY:
# Pull image url
thumb_url = "{}.jpeg".format(get_pms_image_proxy(meta.thumb))
image = "{}.jpg".format(meta.rating_key)
# Saving image in current path
urllib.urlretrieve(thumb_url, image)
image = dict(title=meta.rating_key, path=image, cid=str(uuid.uuid4()))
if meta.grandparent_title == '' or meta.media_type == 'movie':
# Movies
notify_lst += [u"<dt>{x.title} ({x.rating_key}) was added {when}.</dt>"
u"</dt> <dd> <table> <tr> <th>"
'<img src="cid:{cid}" alt="{alt}" width="205" height="100"> </th>'
u" <th id=t11> {x.summary} </th> </tr> </table> </dd> <br>"
.format(x=meta, when=added, alt=cgi.escape(meta.rating_key), quote=True, **image)]
else:
# Shows
notify_lst += [u"<dt>{x.grandparent_title}: {x.title} ({x.rating_key}) was added {when}."
u"</dt> <dd> <table> <tr> <th>"
'<img src="cid:{cid}" alt="{alt}" width="205" height="100"> </th>'
u" <th id=t11> {x.summary} </th> </tr> </table> </dd> <br>"
.format(x=meta, when=added, alt=cgi.escape(meta.rating_key), quote=True, **image)]
msg_text_lst += [MIMEText(u'[image: {title}]'.format(**image), 'plain', 'utf-8')]
image_lst += [image]
"""
Using info found here: http://stackoverflow.com/a/20485764/7286812
to accomplish emailing inline images
"""
msg_html = MIMEText("""\
<html>
<head>
<style>
th#t11 {{ padding: 6px; vertical-align: top; text-align: left; }}
</style>
</head>
<body>
<p>Hi!<br>
<br>Below is the list of content added to Plex's {LIBRARY_NAMES} this week.<br>
<dl>
{notify_lst}
</dl>
</p>
</body>
</html>
""".format(notify_lst="\n".join(notify_lst).encode("utf-8"), LIBRARY_NAMES=" & ".join(LIBRARY_NAMES)
, quote=True, ), 'html', 'utf-8')
message = MIMEMultipart('related')
message['Subject'] = email_subject
message['From'] = email.utils.formataddr((name, sender))
message_alternative = MIMEMultipart('alternative')
message.attach(message_alternative)
for msg_text in msg_text_lst:
message_alternative.attach(msg_text)
message_alternative.attach(msg_html)
for img in image_lst:
with open(img['path'], 'rb') as file:
message_image_lst = [MIMEImage(file.read(), name=os.path.basename(img['path']))]
for msg in message_image_lst:
message.attach(msg)
msg.add_header('Content-ID', '<{}>'.format(img['cid']))
mailserver = smtplib.SMTP(email_server, email_port)
mailserver.ehlo()
mailserver.starttls()
mailserver.ehlo()
mailserver.login(email_username, email_password)
mailserver.sendmail(sender, to, message.as_string())
mailserver.quit()
print 'Email sent'
# Delete images in current path
for img in image_lst:
os.remove(img['path'])
@fkick
Copy link

fkick commented Feb 13, 2017

Love the idea of this script but I'm getting the following error when trying to run it:

PlexPy Notifiers :: Script error: 
    Traceback (most recent call last): 
        File "/notify_added_lastweek.py", line 6, in 
            import requests 
    ImportError: No module named requests

Any thoughts?

Thanks

@starbuck93
Copy link

@fkick you should install Python Requests. Use sudo pip install requests

@starbuck93
Copy link

I am also getting a bug.

Traceback (most recent call last):
  File "notify_added_lastweek.py", line 176, in <module>
    added = time.ctime(float(meta.added_at))
AttributeError: 'NoneType' object has no attribute 'added_at'

Any ideas? The class objects clearly have an 'added_at' attribute as far as I can tell.

@blacktwin
Copy link
Author

@starbuck93 What I would do to troubleshoot is add:
print(json.dumps(response, indent=4, sort_keys=True))
to line 84. Make sure to add import json to the top. This will display what is being grabbed during the metadata call. If the added_at is Nonetype it could be that the call isn't grabbing anything. The json print will shed some light on whether that is true or not.

@fkick
Copy link

fkick commented Feb 16, 2017

Thanks @starbuck93, took care of the issue. :)

As far as your bug, check your media. I was getting those errors when a tv show didn't have any art associated (either it wasn't matching properly in Plex with thetvdb or it hadn't had time yet).

@cdchris12
Copy link

Thanks for the script! I made some slight modifications to remove the rating key and also add season/episode information to the TV show listings. You can see my gist here: https://gist.github.com/cdchris12/65bd50d85635753cabe5c860c5091a48

@blacktwin
Copy link
Author

blacktwin commented Feb 22, 2017

@cdchris12 cool. Change the contents to what you want. The html styling needs some help but it suited my needs.

@starbuck93 did you get it resolved?

@GrantX360
Copy link

I am getting the following error:
PlexPy Notifiers :: Script error:
    PlexPy API 'get_libraries_table' request failed: 'data'.Traceback (most recent call last):
        File "C:\PlexPy\scripts\notify_added_lastweek.py", line 148, in
            glt = [lib for lib in get_get_libraries_table()]
    TypeError: 'NoneType' object is not iterable

I understand this isn't picking up my libraries. I have configured the following:
LIBRARY_NAMES = ['TV Shows', 'Movies'] # Name of libraries you want to check.

These are the correct names. Can anyone help?

@blacktwin
Copy link
Author

@GrantX360 Check your API KEY and URL.

@brooklyn11218
Copy link

I'm trying to get this to work but all it does is show a blank command prompt and then timeout. I've edited the .py file to add my api key, url, library names, and added my gmail information. I have it set as the "recently added" notification on plexpy and it is active. As far as I know that's all i need to do, but when I test it, it just times out.

@infamousdanno
Copy link

Out of curiosity, how are you triggering this script? Presumably you would want to run it once a week, but how do you achieve that through PlexPy?

@blacktwin
Copy link
Author

@infamousdanno Manually once a week or whenever or setup a cron or task.

@brooklyn11218 PlexPy will kill a script if it takes longer than 30 seconds (by default). Either change that or don't have it triggered by PlexPy, set as a cron or task.

@Qosmo
Copy link

Qosmo commented Mar 25, 2017

Anyone know how to use Posters instead of Banners for the email notification?

@adexcide
Copy link

Would also like to use posters instead of the backdrops.

@blacktwin
Copy link
Author

For those seeking posters, change thumb_url from
thumb_url = "{}.jpeg".format(get_pms_image_proxy(meta.thumb))
to
thumb_url = "{}.jpeg".format(get_pms_image_proxy(meta.thumb).replace('%2Fart%', '%2Fposter%'))

@loudambiance
Copy link

Is there a way to automatically add your other plex users emails to the bcc?

@blacktwin
Copy link
Author

blacktwin commented Jun 26, 2017

@loudambiance yes. You can pull all your users emails using the get_users API command. Add that call into the script to have the email addresses append to to.

Clean line 30

to = [sender] 

Insert to line 140

def get_get_users(user_id):
    # Get the user list from PlexPy.
    payload = {'apikey': PLEXPY_APIKEY,
               'cmd': 'get_users'}

    try:
        r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload)
        response = r.json()
        res_data = response['response']['data']
        return [d['email'] for d in res_data]

    except Exception as e:
        sys.stderr.write("PlexPy API 'get_user' request failed: {0}.".format(e))

Then insert into line 152

# Gather all users email addresses
[to.append(x) for x in get_get_users()]

@blacktwin
Copy link
Author

So I rewrote this one to allow more customization. Here is the newer version. Please try.

@Rizzz85
Copy link

Rizzz85 commented Oct 30, 2017

Hi Blacktwin,

Assuming that the PMS agent metadata is already updated, how do I add the year the movie was released and the IMDB rating (1-10) to the email using this script?

Many Thanks!

@blacktwin
Copy link
Author

@Rizzz85 use the API call for get_metadata as a reference for what can be pulled. Released should be originally_available_at. So add that to the METAINFO class:

class METAINFO(object):
    def __init__(self, data=None):
        d = data or {}
        self.added_at = d['added_at']
        self.parent_rating_key = d['parent_rating_key']
        self.title = d['title']
        self.rating_key = d['rating_key']
        self.media_type = d['media_type']
        self.grandparent_title = d['grandparent_title']
        self.file_size = d['file_size']
        self.thumb = d['art']
        self.summary = d['summary']
        self.originally_available_at = d['originally_available_at']   # <----this

Then call it in the notify_lst:

            # Movies
            notify_lst += [u"<dt>{x.title}: ({x.originally_available_at}) ({x.rating_key}) was added {when}.</dt>"
                           u"</dt> <dd> <table> <tr> <th>"
                           '<img src="cid:{cid}" alt="{alt}" width="205" height="100"> </th>'
                           u" <th id=t11> {x.summary} </th> </tr> </table> </dd> <br>"
                               .format(x=meta, when=added, alt=cgi.escape(meta.rating_key), quote=True, **image)]

As for the IMDB rating you'd have to find where what that is called in the get_metadata call.

import json

def get_get_metadata(rating_key):
    # Get the metadata for a media item.
    payload = {'apikey': PLEXPY_APIKEY,
               'rating_key': rating_key,
               'cmd': 'get_metadata',
               'media_info': True}

    try:
        r = requests.get(PLEXPY_URL.rstrip('/') + '/api/v2', params=payload)
        response = r.json()
        print(json.dumps(response, indent=4, sort_keys=True))  # <------This will print out the output of the get_metadata
        if response['response']['result'] == 'success':
            res_data = response['response']['data']['metadata']
            return METAINFO(data=res_data)

    except Exception as e:
        sys.stderr.write("PlexPy API 'get_metadata' request failed: {0}.".format(e))

Add import json to the top of the script and the print mentioned above. This will give you the output of was is available.

Also please use my repo for any additional questions or issues.

@infamousdanno
Copy link

There are some changes in PlexPy 2.0 (which is apparently now called Tautulli?) which break this script.

First, the meta data is no longer nested under the "metadata" key, so I changed line 86 to:

res_data = response['response']['data']

Second, the file_size key in line 48 has been further nested in the metadata. I just commented this line out since I don't use it anymore and the script runs as usual. I believe file_size is now located in ['response']['data']['media_info']['parts']['file_size'] but it wasn't working when I changed line 46 to the following (sorry, I'm new to python):

self.file_size = d['media_info']['parts']['file_size']

https://www.reddit.com/r/PleX/comments/7kmhl3/its_finally_here_tautulli_v2_beta_formerly_plexpy/

@blacktwin
Copy link
Author

@infamousdanno this has been addressed in the updated version found on the repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment