diff --git a/fun/playlist_manager.py b/fun/playlist_manager.py
index c1314c7..d9c1619 100644
--- a/fun/playlist_manager.py
+++ b/fun/playlist_manager.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
"""
Description: Create and share playlists based on Most Popular TV/Movies from Tautulli
and Aired this day in history.
@@ -43,7 +46,7 @@ optional arguments:
Example:
Use with cron or task to schedule runs
-
+
Create Aired Today Playlist from Movies and TV Shows libraries for admin user
python playlist_manager.py --jbop historyToday --libraries Movies "TV Shows" --action add
@@ -61,29 +64,29 @@ optional arguments:
Create 10 Most Popular Movies (60 days) Playlist and share to users bob and Black Twin
python playlist_manager.py --jbop popularMovies --action add --users bob "Black Twin" --days 60 --top 10
-
+
Show 5 Most Popular TV Shows (30 days) Playlist
python playlist_manager.py --jbop popularTv --action show
-
+
Show all users current playlists
python playlist_manager.py --action show --allUsers
-
+
Share existing admin Playlists "My Custom Playlist" and "Another Playlist" with all users
python playlist_manager.py --action share --allUsers --playlists "My Custom Playlist" "Another Playlist"
-
+
Search and Filter;
-
+
metadata_field_name = title, summary, etc.
-
+
--search {metadata_field_name}=value
search through metadata field for existence of value.
-
+
--search {metadata_field_name}=value1,value2,*
search through metadata field for existence of values.
*comma separated for AND (value1 AND value2 AND *)
-
-
-
+
+
+
Excluding;
--user becomes excluded if --allUsers is set
@@ -106,14 +109,14 @@ import unicodedata
from collections import Counter
from plexapi.server import PlexServer, CONFIG
-### EDIT SETTINGS ###
+# ### EDIT SETTINGS ###
PLEX_URL = ''
PLEX_TOKEN = ''
TAUTULLI_URL = ''
TAUTULLI_APIKEY = ''
-## CODE BELOW ##
+# ## CODE BELOW ##
if not PLEX_URL:
PLEX_URL = CONFIG.data['auth'].get('server_baseurl')
@@ -150,6 +153,7 @@ playlist_lst = [x.title for x in plex.playlists()]
today = datetime.datetime.now().date()
weeknum = datetime.date(today.year, today.month, today.day).isocalendar()[1]
+
def actions():
"""
add - create new playlist for admin or users
@@ -172,7 +176,7 @@ def selectors():
'custom': '{custom} Playlist',
'random': '{count} Random {libraries} Playlist'
}
-
+
return selections
@@ -203,7 +207,7 @@ def exclusions(all_true, select, all_items):
for x in select:
all_items.remove(x)
output = all_items
-
+
elif isinstance(all_items, dict):
output = {}
if all_true and not select:
@@ -216,7 +220,7 @@ def exclusions(all_true, select, all_items):
for key, value in all_items.items():
if value not in select:
output[key] = value
-
+
return output
@@ -226,7 +230,7 @@ def get_home_stats(time_range, stats_count):
'cmd': 'get_home_stats',
'time_range': time_range,
'stats_count': stats_count,
- 'stats_type': 0} # stats_type = plays
+ 'stats_type': 0} # stats_type = plays
try:
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
@@ -269,14 +273,14 @@ def sort_by_dates(video, date_type):
return [[video.ratingKey] + [str(video.originallyAvailableAt)]]
# todo-me return object
- except Exception as e:
+ except Exception:
# print(e)
return
def multi_filter_search(keyword_dict, library, search_eps=None):
"""Allowing for multiple filter or search values
-
+
Parameters
----------
keyword_dict: dict
@@ -318,17 +322,18 @@ def multi_filter_search(keyword_dict, library, search_eps=None):
for episode in show.episodes(**{key: values}):
ep_lst += [episode.ratingKey]
multi_lst += ep_lst
-
+
else:
multi_lst += [item.ratingKey for item in library.all(**{key: values})]
counts = Counter(multi_lst)
# Use amount of keywords to check that all keywords were found in results
search_lst = [id for id in multi_lst if counts[id] >= keyword_count]
-
+
return list(set(search_lst))
+
def get_content(libraries, jbop, filters=None, search=None, limit=None):
- """Get all movies or episodes from LIBRARY_NAME
+ """Get all movies or episodes from LIBRARY_NAME.
Parameters
----------
@@ -342,6 +347,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
list
Sorted list of Movie and episodes that
aired on today's date.
+
"""
child_lst = []
filter_lst = []
@@ -378,7 +384,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
child_lst += filter_lst
if keywords and filters:
child_lst += list(set(filter_lst) & set(search_lst))
-
+
elif library_type == 'show':
# Decisions to stack filter and search
if keywords:
@@ -402,7 +408,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
for episode in show.episodes():
filter_lst += [episode.ratingKey]
child_lst += filter_lst
-
+
if keywords and filters:
child_lst += list(set(filter_lst) & set(search_lst))
else:
@@ -410,9 +416,9 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
# Keep only results found from both search and filters
if keywords and filters:
child_lst = list(set(i for i in child_lst if child_lst.count(i) > 1))
-
+
play_lst = child_lst
-
+
else:
for library in libraries.keys():
plex_library = plex.library.sectionByID(library)
@@ -440,7 +446,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
# Sort by original air date, oldest first
# todo-me move sorting and add more sorting options
aired_lst = sorted(child_lst, key=operator.itemgetter(1))
-
+
# Remove date used for sorting
play_lst = [x[0] for x in aired_lst]
else:
@@ -474,7 +480,7 @@ def build_playlist(jbop, libraries=None, days=None, top=None, filters=None, sear
for stat in home_stats:
if stat['stat_id'] in ['popular_tv', 'popular_movies']:
keys_list += [x['rating_key'] for x in stat['rows'] if
- str(x['section_id']) in libraries.keys()]
+ str(x['section_id']) in libraries.keys()]
else:
try:
keys_list = get_content(libraries, jbop, filters, search, limit)
@@ -525,11 +531,12 @@ def show_playlist(playlist_title, playlist_keys):
title = unicodedata.normalize('NFKD', title).encode('ascii', 'ignore').translate(None, "'")
playlist_list.append(title)
- print(u"Contents of Playlist {title}:\n{playlist}".format(title=playlist_title,
- playlist=', '.join(playlist_list)))
+ print(u"Contents of Playlist {title}:\n{playlist}".format(
+ title=playlist_title,
+ playlist=', '.join(playlist_list)))
exit()
-
-
+
+
def create_playlist(playlist_title, playlist_keys, server, user):
"""
Parameters
@@ -552,13 +559,13 @@ def create_playlist(playlist_title, playlist_keys, server, user):
playlist_list.append(episode)
else:
playlist_list.append(plex_obj)
- except Exception as e:
+ except Exception:
try:
obj = plex.fetchItem(key)
print("{} may not have permission to this title: {}".format(user, obj.title))
# print("Error: {}".format(e))
pass
- except Exception as e:
+ except Exception:
print('Rating Key: {}, may have been deleted or moved.'.format(key))
# print("Error: {}".format(e))
@@ -578,7 +585,7 @@ def delete_playlist(playlist_dict, title):
"""
server = playlist_dict['server']
user = playlist_dict['user']
-
+
try:
# todo-me this needs improvement
for playlist in server.playlists():
@@ -595,14 +602,14 @@ def delete_playlist(playlist_dict, title):
print("...Deleted Playlist: {playlist.title} for '{user}'."
.format(playlist=playlist, user=user))
- except:
+ except Exception:
# print("Playlist not found on '{user}' account".format(user=user))
pass
def create_title(jbop, libraries, days, filters, search, limit):
"""
-
+
Parameters
----------
jbop: str
@@ -672,9 +679,9 @@ def create_title(jbop, libraries, days, filters, search, limit):
if __name__ == "__main__":
-
- parser = argparse.ArgumentParser(description="Create, share, and clean Playlists for users.",
- formatter_class = argparse.RawTextHelpFormatter)
+ parser = argparse.ArgumentParser(
+ description="Create, share, and clean Playlists for users.",
+ formatter_class=argparse.RawTextHelpFormatter)
# todo-me use parser grouping instead of choices for action and jbop?
parser.add_argument('--jbop', choices=selectors().keys(), metavar='',
help='Playlist selector.\n'
@@ -715,7 +722,7 @@ if __name__ == "__main__":
parser.add_argument('--search', action='append', type=lambda kv: kv.split("="),
help='Search non-filtered metadata fields for keywords '
'in title, summary, etc.')
-
+
opts = parser.parse_args()
title = ''
@@ -739,7 +746,7 @@ if __name__ == "__main__":
# If filter key used more than once than consider filtering values with OR statement
if filter_count > 1:
filters_lst = []
-
+
filters = dict(opts.filter)
for k, v in filters.items():
# If comma separated filter then consider filtering values with AND statement
@@ -756,10 +763,10 @@ if __name__ == "__main__":
# Defining libraries
libraries = exclusions(opts.allLibraries, opts.libraries, sections_dict)
-
+
# Defining selected playlists
selected_playlists = exclusions(opts.allPlaylists, opts.playlists, playlist_lst)
-
+
# Create user server objects
if users:
for user in users:
@@ -776,12 +783,13 @@ if __name__ == "__main__":
'user': user,
'user_selected': user_selected,
'all_playlists': all_playlists})
-
+
if opts.self or not users:
- playlist_dict['data'].append({'server': plex,
- 'user': 'admin',
- 'user_selected': selected_playlists,
- 'all_playlists': playlist_lst})
+ playlist_dict['data'].append({
+ 'server': plex,
+ 'user': 'admin',
+ 'user_selected': selected_playlists,
+ 'all_playlists': playlist_lst})
if not opts.jbop and opts.action == 'show':
print("Displaying the user's playlist(s)...")
@@ -790,7 +798,7 @@ if __name__ == "__main__":
playlists = data['all_playlists']
print("{}'s current playlist(s): {}".format(user, ', '.join(playlists)))
exit()
-
+
if libraries:
title = create_title(opts.jbop, libraries, opts.days, filters, search, opts.limit)
keys_list = build_playlist(opts.jbop, libraries, opts.days, opts.top, filters, search, opts.limit)
@@ -801,18 +809,18 @@ if __name__ == "__main__":
for data in playlist_dict['data']:
titles = data['user_selected']
delete_playlist(data, titles)
-
+
# Check if limit exist and if it's greater than the pulled list of rating keys
if opts.limit and len(keys_list) > int(opts.limit):
if opts.jbop == 'random':
keys_list = random.sample((keys_list), opts.limit)
else:
keys_list = keys_list[:opts.limit]
-
+
# Setting custom name if provided
if opts.name:
title = opts.name
-
+
if opts.jbop and opts.action == 'show':
show_playlist(title, keys_list)
@@ -823,7 +831,7 @@ if __name__ == "__main__":
print('Creating playlist(s)...')
for data in playlist_dict['data']:
create_playlist(title, keys_list, data['server'], data['user'])
-
+
if opts.action == 'add':
print('Creating playlist(s)...')
for data in playlist_dict['data']:
diff --git a/fun/plex_lifx_color_theme.pyw b/fun/plex_lifx_color_theme.pyw
index 50c3210..b64a5fd 100644
--- a/fun/plex_lifx_color_theme.pyw
+++ b/fun/plex_lifx_color_theme.pyw
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
#
# Author: Bailey Belvis (https://github.com/philosowaffle)
#
@@ -6,18 +9,16 @@
#
# - Enable `Upload Posters to Imgur for Notifications` - required for lights to match the posters color scheme
# - Triggers - PlexLifx supports the following triggers, enable the ones you are interested in.
-# - Notify on Playback Start
-# - Notify on Playback Stop
-# - Notify on Playback Resume
-# - Notify on Playback Pause
+# - Notify on Playback Start
+# - Notify on Playback Stop
+# - Notify on Playback Resume
+# - Notify on Playback Pause
#
# - Copy paste the following line to each of the Triggers you enabled (found on the Arguments tab):
-# -a {action} -mt {media_type} -mi {machine_id} -rk {rating_key} -pu {poster_url}
-#
+# -a {action} -mt {media_type} -mi {machine_id} -rk {rating_key} -pu {poster_url}
+
import os
-import sys
import logging
-import hashlib
import shutil
import numpy
import argparse
@@ -99,10 +100,10 @@ filtered_players = [] if PlayerUUIDs == "none" else PlayerUUIDs.split(',')
logger.debug("Filtered Players: " + filtered_players.__str__())
events = [
- 'play',
- 'pause',
- 'resume',
- 'stop'
+ 'play',
+ 'pause',
+ 'resume',
+ 'stop'
]
##############################
@@ -115,33 +116,33 @@ num_colors = NumColors if NumColors else 4
color_quality = ColorQuality if ColorQuality else 1
if not APIKey:
- logger.error("Missing LIFX API Key")
- exit(1)
+ logger.error("Missing LIFX API Key")
+ exit(1)
else:
- lifx_api_key = APIKey
- logger.debug("LIFX API Key: " + lifx_api_key)
+ lifx_api_key = APIKey
+ logger.debug("LIFX API Key: " + lifx_api_key)
pifx = PIFX(lifx_api_key)
lights = []
if Lights:
- lights_use_name = True
- lights = Lights.split(',')
+ lights_use_name = True
+ lights = Lights.split(',')
- tmp = []
- for light in lights:
- tmp.append(light.strip())
- lights = tmp
+ tmp = []
+ for light in lights:
+ tmp.append(light.strip())
+ lights = tmp
else:
- lights_detail = pifx.list_lights()
- for light in lights_detail:
- lights.append(light['id'])
- shuffle(lights)
+ lights_detail = pifx.list_lights()
+ for light in lights_detail:
+ lights.append(light['id'])
+ shuffle(lights)
scenes_details = pifx.list_scenes()
scenes = dict()
for scene in scenes_details:
- scenes[scene['name']] = scene['uuid']
+ scenes[scene['name']] = scene['uuid']
logger.debug(scenes)
logger.debug(lights)
@@ -154,7 +155,7 @@ default_play_uuid = scenes[default_play_theme]
number_of_lights = len(lights)
if number_of_lights < num_colors:
- num_colors = number_of_lights
+ num_colors = number_of_lights
light_groups = numpy.array_split(numpy.array(lights), num_colors)
@@ -167,15 +168,15 @@ logger.debug("Color Quality: " + color_quality.__str__())
##############################
p = argparse.ArgumentParser()
p.add_argument('-a', '--action', action='store', default='',
- help='The action that triggered the script.')
+ help='The action that triggered the script.')
p.add_argument('-mt', '--media_type', action='store', default='',
- help='The media type of the media being played.')
+ help='The media type of the media being played.')
p.add_argument('-mi', '--machine_id', action='store', default='',
- help='The machine id of where the media is playing.')
+ help='The machine id of where the media is playing.')
p.add_argument('-rk', '--rating_key', action='store', default='',
- help='The unique identifier for the media.')
+ help='The unique identifier for the media.')
p.add_argument('-pu', '--poster_url', action='store', default='',
- help='The poster url for the media playing.')
+ help='The poster url for the media playing.')
parser = p.parse_args()
@@ -196,22 +197,22 @@ logger.debug("Media Guid: " + media_guid)
logger.debug("Poster Url: " + poster_url)
# Only perform action for event play/pause/resume/stop for TV and Movies
-if not event in events:
- logger.debug("Invalid action: " + event)
- exit()
+if event not in events:
+ logger.debug("Invalid action: " + event)
+ exit()
if (media_type != "movie") and (media_type != "episode"):
- logger.debug("Media type was not movie or episode, ignoring.")
- exit()
+ logger.debug("Media type was not movie or episode, ignoring.")
+ exit()
# If we configured only specific players to be able to play with the lights
if filtered_players:
- try:
- if player_uuid not in filtered_players:
- logger.info(player_uuid + " player is not able to play with the lights")
- exit()
- except Exception as e:
- logger.error("Failed to check uuid - " + e.__str__())
+ try:
+ if player_uuid not in filtered_players:
+ logger.info(player_uuid + " player is not able to play with the lights")
+ exit()
+ except Exception as e:
+ logger.error("Failed to check uuid - " + e.__str__())
# Setup Thumbnail directory paths
upload_folder = os.getcwd() + '\\tmp'
@@ -219,63 +220,63 @@ thumb_folder = os.path.join(upload_folder, media_guid)
thumb_path = os.path.join(thumb_folder, "thumb.jpg")
if event == 'stop':
- if os.path.exists(thumb_folder):
- logger.debug("Removing Directory: " + thumb_folder)
- shutil.rmtree(thumb_folder)
+ if os.path.exists(thumb_folder):
+ logger.debug("Removing Directory: " + thumb_folder)
+ shutil.rmtree(thumb_folder)
- pifx.activate_scene(default_pause_uuid)
- exit()
+ pifx.activate_scene(default_pause_uuid)
+ exit()
if event == 'pause':
- pifx.activate_scene(default_pause_uuid)
- exit()
+ pifx.activate_scene(default_pause_uuid)
+ exit()
if event == 'play' or event == "resume":
- # If the file already exists then we don't need to re-upload the image
- if not os.path.exists(thumb_folder):
- try:
- logger.debug("Making Directory: " + thumb_folder)
- os.makedirs(thumb_folder)
- urllib.urlretrieve(poster_url, thumb_path)
- except Exception as e:
- logger.error(e)
- logger.info("No file found in request")
- pifx.activate_scene(default_play_uuid)
- exit()
+ # If the file already exists then we don't need to re-upload the image
+ if not os.path.exists(thumb_folder):
+ try:
+ logger.debug("Making Directory: " + thumb_folder)
+ os.makedirs(thumb_folder)
+ urllib.urlretrieve(poster_url, thumb_path)
+ except Exception as e:
+ logger.error(e)
+ logger.info("No file found in request")
+ pifx.activate_scene(default_play_uuid)
+ exit()
# Determine Color Palette for Lights
- color_thief = ColorThief(thumb_path)
- if num_colors >= 2:
- palette = color_thief.get_palette(color_count=num_colors, quality=color_quality)
- else:
- palette = [color_thief.get_color(quality=color_quality)]
- logger.debug("Color Palette: " + palette.__str__())
+ color_thief = ColorThief(thumb_path)
+ if num_colors >= 2:
+ palette = color_thief.get_palette(color_count=num_colors, quality=color_quality)
+ else:
+ palette = [color_thief.get_color(quality=color_quality)]
+ logger.debug("Color Palette: " + palette.__str__())
# Set Color Palette
- pifx.set_state(selector='all', power="off")
- for index in range(len(light_groups)):
- try:
- color = palette[index]
- light_group = light_groups[index]
+ pifx.set_state(selector='all', power="off")
+ for index in range(len(light_groups)):
+ try:
+ color = palette[index]
+ light_group = light_groups[index]
- logger.debug(light_group)
- logger.debug(color)
+ logger.debug(light_group)
+ logger.debug(color)
- color_rgb = ', '.join(str(c) for c in color)
- color_rgb = "rgb:" + color_rgb
- color_rgb = color_rgb.replace(" ", "")
+ color_rgb = ', '.join(str(c) for c in color)
+ color_rgb = "rgb:" + color_rgb
+ color_rgb = color_rgb.replace(" ", "")
- for light_id in light_group:
- if lights_use_name:
- selector = "label:" + light_id
- else:
- selector = light_id
+ for light_id in light_group:
+ if lights_use_name:
+ selector = "label:" + light_id
+ else:
+ selector = light_id
- logger.debug("Setting light: " + selector + " to color: " + color_rgb)
- pifx.set_state(selector=selector, power="on", color=color_rgb, brightness=brightness, duration=duration)
-
- except Exception as e:
- logger.error(e)
+ logger.debug("Setting light: " + selector + " to color: " + color_rgb)
+ pifx.set_state(selector=selector, power="on", color=color_rgb, brightness=brightness, duration=duration)
+
+ except Exception as e:
+ logger.error(e)
exit()
diff --git a/fun/plexapi_haiku.py b/fun/plexapi_haiku.py
index 6dcedf4..4097b71 100644
--- a/fun/plexapi_haiku.py
+++ b/fun/plexapi_haiku.py
@@ -1,11 +1,14 @@
-'''
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
https://gist.github.com/blacktwin/4ccb79c7d01a95176b8e88bf4890cd2b
-'''
+"""
+
from plexapi.server import PlexServer
import random
import re
-
baseurl = 'http://localhost:32400'
token = 'xxxxx'
plex = PlexServer(baseurl, token)
@@ -44,8 +47,11 @@ def sylco(word):
if word[-2:] == "es" or word[-2:] == "ed":
doubleAndtripple_1 = len(re.findall(r'[eaoui][eaoui]', word))
if doubleAndtripple_1 > 1 or len(re.findall(r'[eaoui][^eaoui]', word)) > 1:
- if word[-3:] == "ted" or word[-3:] == "tes" or word[-3:] == "ses" or word[-3:] == "ied" or word[
- -3:] == "ies":
+ if word[-3:] == "ted" or \
+ word[-3:] == "tes" or \
+ word[-3:] == "ses" or \
+ word[-3:] == "ied" or \
+ word[-3:] == "ies":
pass
else:
disc += 1
@@ -140,8 +146,7 @@ def sylco(word):
if word in exception_add:
syls += 1
-
- # calculate the output
+ # calculate the output
return numVowels - disc + syls
@@ -190,13 +195,13 @@ for x in LIBRARIES_LST:
m_lst = hi_build(sections_lst, 5) + hi_build(sections_lst, 7) + hi_build(sections_lst, 5)
# to see word and syllable count uncomment below print.
-#print(m_lst)
+# print(m_lst)
stanz1 = ' '.join(m_lst[0].keys())
stanz2 = ' '.join(m_lst[1].keys())
stanz3 = ' '.join(m_lst[2].keys())
-lines = stanz1,stanz2,stanz3
+lines = stanz1, stanz2, stanz3
lines = '\n'.join(lines)
print('')
print(lines.lower())
diff --git a/killstream/kill_else_if_buffering.py b/killstream/kill_else_if_buffering.py
index 11f514d..b469c38 100644
--- a/killstream/kill_else_if_buffering.py
+++ b/killstream/kill_else_if_buffering.py
@@ -1,4 +1,7 @@
-'''
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
If server admin stream is experiencing buffering and there are concurrent transcode streams from
another user, kill concurrent transcode stream that has the lowest percent complete. Message in
kill stream will list why it was killed ('Server Admin's stream take priority and this user has X
@@ -11,7 +14,7 @@ Tautulli > Settings > Notification Agents > Scripts > Bell icon:
Tautulli > Settings > Notification Agents > Scripts > Gear icon:
Buffer Warnings: kill_else_if_buffering.py
-'''
+"""
import requests
from operator import itemgetter
@@ -19,7 +22,7 @@ import unicodedata
from plexapi.server import PlexServer
-## EDIT THESE SETTINGS ##
+# ## EDIT THESE SETTINGS ##
PLEX_TOKEN = 'xxxx'
PLEX_URL = 'http://localhost:32400'
@@ -27,8 +30,8 @@ DEFAULT_REASON = 'Server Admin\'s stream takes priority and {user}(you) has {x}
' {user}\'s stream of {video} is {time}% complete. Should be finished in {comp} minutes. ' \
'Try again then.'
-ADMIN_USER = ('Admin') # additional usernames can be added ('Admin', 'user2')
-##
+ADMIN_USER = ('Admin') # Additional usernames can be added ('Admin', 'user2')
+# ##
sess = requests.Session()
sess.verify = False
@@ -71,17 +74,17 @@ def main():
# Remove users with only 1 stream. Targeting users with multiple concurrent streams
filtered_dict = {key: value for key, value in user_dict.items()
- if len(value) is not 1}
+ if len(value) != 1}
# Find who to kill and who will be finishing first.
if filtered_dict:
for users in filtered_dict.values():
to_kill = min(users, key=itemgetter(1))
to_finish = max(users, key=itemgetter(1))
-
+
MESSAGE = DEFAULT_REASON.format(user=to_finish[3], x=len(filtered_dict.values()[0]),
video=to_finish[2], time=to_finish[1], comp=to_finish[4])
-
+
print(MESSAGE)
kill_session(to_kill[0], MESSAGE)
diff --git a/killstream/kill_stream.py b/killstream/kill_stream.py
index 765c1f9..8f99e21 100644
--- a/killstream/kill_stream.py
+++ b/killstream/kill_stream.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
"""
Description: Use conditions to kill a stream
Author: Blacktwin, Arcanemagus, Samwiseg0, JonnyWong16, DirtyCajunRice
@@ -372,8 +375,8 @@ class Stream:
if self.session_exists is False:
sys.stdout.write(
"Session '{}' from user '{}' is no longer active "
- .format(self.session_id, self.username)
- + "on the server, stopping monitoring.\n")
+ .format(self.session_id, self.username) +
+ "on the server, stopping monitoring.\n")
return False
now = datetime.now()
@@ -392,8 +395,8 @@ class Stream:
elif self.state == 'playing' or self.state == 'buffering':
sys.stdout.write(
"Session '{}' from user '{}' has been resumed, "
- .format(self.session_id, self.username)
- + "stopping monitoring.\n")
+ .format(self.session_id, self.username) +
+ "stopping monitoring.\n")
return False
@@ -536,7 +539,7 @@ class Notification:
"value": message,
"short": False
}
- ],
+ ],
"thumb_url": poster_url,
"footer": footer,
"ts": time.time()
@@ -562,8 +565,8 @@ if __name__ == "__main__":
parser.add_argument('--sessionId',
help='The unique identifier for the stream.')
parser.add_argument('--notify', type=int,
- help='Notification Agent ID number to Agent to send '
- + 'notification.')
+ help='Notification Agent ID number to Agent to ' +
+ 'send notification.')
parser.add_argument('--limit', type=int, default=(20 * 60), # 20 minutes
help='The time session is allowed to remain paused.')
parser.add_argument('--interval', type=int, default=30,
diff --git a/killstream/limiterr.py b/killstream/limiterr.py
index d5ec039..9d1e1fa 100644
--- a/killstream/limiterr.py
+++ b/killstream/limiterr.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
"""
Description: Limiting Plex users by plays, watches, or total time from Tautulli.
Author: Blacktwin, Arcanemagus
@@ -48,7 +51,7 @@ import argparse
from datetime import datetime
import sys
import os
-from plexapi.server import PlexServer, CONFIG
+from plexapi.server import PlexServer
from time import time as ttime
from time import sleep
@@ -65,6 +68,7 @@ TAUTULLI_APIKEY = os.getenv('TAUTULLI_APIKEY', TAUTULLI_APIKEY)
TAUTULLI_ENCODING = os.getenv('TAUTULLI_ENCODING', 'UTF-8')
# Using CONFIG file
+# from plexapi.server import CONFIG
# PLEX_URL = CONFIG.data['auth'].get('server_baseurl', PLEX_URL)
# PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN)
# TAUTULLI_URL = CONFIG.data['auth'].get('tautulli_baseurl', TAUTULLI_URL)
@@ -88,7 +92,7 @@ if sess.verify is False:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess)
-lib_dict = {x.title : x.key for x in plex.library.sections()}
+lib_dict = {x.title: x.key for x in plex.library.sections()}
SELECTOR = ['watch', 'plays', 'time', 'limit']
@@ -144,7 +148,7 @@ def get_activity(session_id=None):
try:
req = sess.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
response = req.json()
-
+
if session_id:
res_data = response['response']['data']
else:
@@ -271,7 +275,7 @@ if __name__ == "__main__":
'Default: %(default)s')
parser.add_argument('--duration', type=int, default=0,
help='Duration of item that triggered script agent.')
-
+
opts = parser.parse_args()
total_limit = 0
@@ -327,7 +331,7 @@ if __name__ == "__main__":
else:
print('Session; {} has been dropped. Stopping monitoring of stream.'.format(opts.sessionId))
exit()
-
+
print('Total {} ({} + current item duration {}) is greater than limit ({}).'
.format(opts.jbop, total_jbop, opts.duration, total_limit))
terminate_session(opts.sessionId, message, opts.notify, opts.username)
@@ -346,16 +350,16 @@ if __name__ == "__main__":
if not message:
message = LIMIT_MESSAGE.format(delay=opts.delay)
ep_watched = [data['watched_status'] for data in history['data']
- if data['grandparent_rating_key'] == opts.grandparent_rating_key
- and data['watched_status'] == 1]
+ if data['grandparent_rating_key'] == opts.grandparent_rating_key and
+ data['watched_status'] == 1]
if not ep_watched:
ep_watched = 0
else:
ep_watched = sum(ep_watched)
stopped_time = [data['stopped'] for data in history['data']
- if data['grandparent_rating_key'] == opts.grandparent_rating_key
- and data['watched_status'] == 1]
+ if data['grandparent_rating_key'] == opts.grandparent_rating_key and
+ data['watched_status'] == 1]
if not stopped_time:
stopped_time = unix_time
else:
@@ -366,9 +370,9 @@ if __name__ == "__main__":
sys.exit(1)
if ep_watched >= total_limit:
- print("{}'s limit is {} and has watched {} episodes of this show today."
- .format(opts.username, total_limit, ep_watched))
+ print("{}'s limit is {} and has watched {} episodes of this show today.".format(
+ opts.username, total_limit, ep_watched))
terminate_session(opts.sessionId, message, opts.notify, opts.username)
else:
- print("{}'s limit is {} but has only watched {} episodes of this show today."
- .format(opts.username, total_limit, ep_watched))
\ No newline at end of file
+ print("{}'s limit is {} but has only watched {} episodes of this show today.".format(
+ opts.username, total_limit, ep_watched))
diff --git a/killstream/limiterr_readme.md b/killstream/limiterr_readme.md
index 949b492..9aae83d 100644
--- a/killstream/limiterr_readme.md
+++ b/killstream/limiterr_readme.md
@@ -20,7 +20,7 @@ Triggers: Playback Start
Arguments:
```
---jbop watch --username {username} --sessionId {session_id} --limit plays=3 --notify 1 --killMessage "You have met your limit of 3 watches."
+--jbop watch --username {username} --sessionId {session_id} --limit plays=3 --notify 1 --killMessage "You have met your limit of 3 watches."
```
### Limit user to total Plays/Watches in a specific library (Movies)
@@ -29,7 +29,7 @@ Triggers: Playback Start
Arguments:
```
---jbop watch --username {username} --sessionId {session_id} --limit plays=3 --section Movies --killMessage "You have met your limit of 3 watches."
+--jbop watch --username {username} --sessionId {session_id} --limit plays=3 --section Movies --killMessage "You have met your limit of 3 watches."
```
### Limit user to total time watching
@@ -59,4 +59,3 @@ Arguments:
```
--jbop time --username {username} --sessionId {session_id} --duration {duration} --limit hours=2 --killMessage "You have met your limit of 3 days and 10 hours."
```
-
diff --git a/killstream/readme.md b/killstream/readme.md
index b30c5e5..3a878d5 100644
--- a/killstream/readme.md
+++ b/killstream/readme.md
@@ -248,4 +248,4 @@ Add `--debug` to enable debug logging.
All examples use \[ `Transcode Decision` | `is` | `transcode` \] which will kill any variant of transcoding.
If you want to allow audio or container transcoding and only drop video transcodes, your condition would change to
-\[ `Video Decision` | `is` | `transcode` \]
\ No newline at end of file
+\[ `Video Decision` | `is` | `transcode` \]
diff --git a/maps/ips_to_maps.py b/maps/ips_to_maps.py
index 41a6f60..a78d160 100644
--- a/maps/ips_to_maps.py
+++ b/maps/ips_to_maps.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
"""
Use Tautulli draw a map connecting Server to Clients based on IP addresses.
@@ -30,12 +33,12 @@ import json
import os
from collections import OrderedDict
import argparse
-import numpy as np
+# import numpy as np
import time
import webbrowser
import re
-## EDIT THESE SETTINGS ##
+# ## EDIT THESE SETTINGS ##
TAUTULLI_APIKEY = '' # Your Tautulli API key
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
@@ -259,7 +262,7 @@ def draw_map(map_type, geo_dict, filename, headless, leg_choice):
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
- ## Map stuff ##
+ # ## Map stuff ##
plt.figure(figsize=(16, 9), dpi=100, frameon=False)
lon_r = 0
lon_l = 0
diff --git a/maps/readme.md b/maps/readme.md
index 7dc406f..c0908d0 100644
--- a/maps/readme.md
+++ b/maps/readme.md
@@ -1,4 +1,3 @@
-
# Maps
Maps are created with either Matplotlib/Basemap or as a geojson file on an anonymous gist.
@@ -46,7 +45,7 @@ Choose which map type you'd like by using the `-l` argument:
- [x] Add check for user count in user_table to allow for greater than 25 users - [Pull](https://github.com/blacktwin/JBOPS/pull/3)
- [x] If platform is missing from PLATFORM_COLORS use DEFAULT_COLOR - [Pull](https://github.com/blacktwin/JBOPS/pull/4)
-- [x] Add arg to allow for runs in headless (mpl.use("Agg"))
+- [x] Add arg to allow for runs in headless (mpl.use("Agg"))
- [x] Add pass on N/A values for Lon/Lat - [Pull](https://github.com/blacktwin/JBOPS/pull/2)
### Feature updates:
@@ -57,6 +56,3 @@ Choose which map type you'd like by using the `-l` argument:
- [ ] Find server's external IP, geolocation. Allow custom location to override
- [ ] Add arg for tracert visualization from server to client
- [ ] Animate tracert visualization? gif?
-
-
-
diff --git a/maps/requirements.txt b/maps/requirements.txt
index 5e63c7b..65a1fea 100644
--- a/maps/requirements.txt
+++ b/maps/requirements.txt
@@ -5,4 +5,4 @@
requests
matplotlib
numpy
-basemap
\ No newline at end of file
+basemap
diff --git a/notify/find_unwatched_notify.py b/notify/find_unwatched_notify.py
index a5ce082..d21cb81 100644
--- a/notify/find_unwatched_notify.py
+++ b/notify/find_unwatched_notify.py
@@ -1,5 +1,7 @@
-"""
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
Find what was added TFRAME ago and not watched and notify admin using Tautulli.
TAUTULLI_URL + delete_media_info_cache?section_id={section_id}
@@ -12,7 +14,7 @@ import time
TFRAME = 1.577e+7 # ~ 6 months in seconds
TODAY = time.time()
-## EDIT THESE SETTINGS ##
+# ## EDIT THESE SETTINGS ##
TAUTULLI_APIKEY = '' # Your Tautulli API key
TAUTULLI_URL = 'http://localhost:8183/' # Your Tautulli URL
LIBRARY_NAMES = ['Movies', 'TV Shows'] # Name of libraries you want to check.
@@ -162,10 +164,10 @@ for library in libraries:
# Find movie rating_key.
show_lst += [int(lib.rating_key)]
except Exception as e:
- print "Rating_key failed: {e}".format(e=e)
+ print("Rating_key failed: {e}".format(e=e))
except Exception as e:
- print "Library media info failed: {e}".format(e=e)
+ print("Library media info failed: {e}".format(e=e))
for show in show_lst:
try:
@@ -181,7 +183,7 @@ for show in show_lst:
u" not been watched. Hi!
".format(x=meta, when=added)]
except Exception as e:
- print "Metadata failed. Likely end of range: {e}".format(e=e)
+ print("Metadata failed. Likely end of range: {e}".format(e=e))
if notify_lst:
BODY_TEXT = """\
diff --git a/notify/notify_added_custom.py b/notify/notify_added_custom.py
new file mode 100644
index 0000000..c007177
--- /dev/null
+++ b/notify/notify_added_custom.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Send an email with what was added to Plex in the past week using Tautulli.
+Email includes title (TV: Show Name: Episode Name; Movie: Movie Title), time added, image, and summary.
+
+Uses:
+ notify_added_lastweek.py -t poster -d 1 -u all -i user1 user2 -s 250 100
+ # email all users expect user1 & user2 what was added in the last day using posters that are 250x100
+
+ notify_added_lastweek.py -t poster -d 7 -u all
+ # email all users what was added in the last 7 days(week) using posters that are default sized
+
+ notify_added_lastweek.py -t poster -d 7 -u all -s 1000 500
+ # email all users what was added in the last 7 days(week) using posters that are 1000x500
+
+ notify_added_lastweek.py -t art -d 7 -u user1
+ # email user1 & self what was added in the last 7 days(week) using artwork that is default sized
+
+ notify_added_lastweek.py -t art -d 7
+ # email self what was added in the last 7 days(week) using artwork that is default sized
+
+"""
+
+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
+import argparse
+
+
+# ## EDIT THESE SETTINGS ##
+TAUTULLI_APIKEY = '' # Your Tautulli API key
+TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
+LIBRARY_NAMES = ['Movies', 'TV Shows'] # 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 = 'Tautulli Added Last {} day(s) Notification' # The email subject
+
+# Default sizing for pictures
+# Poster
+poster_h = 205
+poster_w = 100
+# Artwork
+art_h = 100
+art_w = 205
+
+# ## /EDIT THESE SETTINGS ##
+
+
+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.thumb = d['art']
+ self.summary = d['summary']
+
+
+def get_recent(section_id, start, count):
+ # Get the metadata for a media item. Count matters!
+ payload = {'apikey': TAUTULLI_APIKEY,
+ 'start': str(start),
+ 'count': str(count),
+ 'section_id': section_id,
+ 'cmd': 'get_recently_added'}
+
+ try:
+ r = requests.get(TAUTULLI_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("Tautulli API 'get_recently_added' request failed: {0}.".format(e))
+
+
+def get_metadata(rating_key):
+ # Get the metadata for a media item.
+ payload = {'apikey': TAUTULLI_APIKEY,
+ 'rating_key': rating_key,
+ 'cmd': 'get_metadata',
+ 'media_info': True}
+
+ try:
+ r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
+ response = r.json()
+ if response['response']['result'] == 'success':
+ res_data = response['response']['data']
+
+ return METAINFO(data=res_data)
+
+ except Exception as e:
+ sys.stderr.write("Tautulli API 'get_metadata' request failed: {0}.".format(e))
+
+
+def get_libraries_table():
+ # Get the data on the Tautulli libraries table.
+ payload = {'apikey': TAUTULLI_APIKEY,
+ 'cmd': 'get_libraries_table'}
+
+ try:
+ r = requests.get(TAUTULLI_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("Tautulli API 'get_libraries_table' request failed: {0}.".format(e))
+
+
+def update_library_media_info(section_id):
+ # Get the data on the Tautulli media info tables.
+ payload = {'apikey': TAUTULLI_APIKEY,
+ 'cmd': 'get_library_media_info',
+ 'section_id': section_id,
+ 'refresh': True}
+
+ try:
+ r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
+ response = r.status_code
+ if response != 200:
+ print(r.content)
+
+ except Exception as e:
+ sys.stderr.write("Tautulli 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': TAUTULLI_APIKEY,
+ 'cmd': 'pms_image_proxy',
+ 'img': thumb}
+
+ try:
+ r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload, stream=True)
+ return r.url
+
+ except Exception as e:
+ sys.stderr.write("Tautulli API 'get_users_tables' request failed: {0}.".format(e))
+
+
+def get_users():
+ # Get the user list from Tautulli.
+ payload = {'apikey': TAUTULLI_APIKEY,
+ 'cmd': 'get_users'}
+
+ try:
+ r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
+ response = r.json()
+ res_data = response['response']['data']
+ return [d for d in res_data]
+
+ except Exception as e:
+ sys.stderr.write("Tautulli API 'get_user' request failed: {0}.".format(e))
+
+
+def get_rating_keys(TODAY, LASTDATE):
+
+ recent_lst = []
+ # 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_recent(section_id, start, count)
+
+ if all([recent_items]):
+ start += count
+ for item in recent_items:
+ if LASTDATE <= int(item['added_at']) <= TODAY:
+ recent_lst.append(item['rating_key'])
+ continue
+ elif not all([recent_items]):
+ break
+
+ start += count
+ if recent_lst:
+ return recent_lst
+ sys.stderr.write("Recently Added list: {0}.".format(recent_lst))
+ exit()
+
+
+def build_html(rating_key, height, width, pic_type):
+
+ meta = get_metadata(str(rating_key))
+
+ added = time.ctime(float(meta.added_at))
+ # Pull image url
+ thumb_url = "{}.jpeg".format(get_pms_image_proxy(meta.thumb))
+ if pic_type == 'poster':
+ thumb_url = thumb_url.replace('%2Fart%', '%2Fposter%')
+ image_name = "{}.jpg".format(str(rating_key))
+ # Saving image in current path
+ urllib.urlretrieve(thumb_url, image_name)
+ image = dict(title=meta.rating_key, path=image_name, cid=str(uuid.uuid4()))
+ if meta.grandparent_title == '' or meta.media_type == 'movie':
+ # Movies
+ notify = u"
" \
+ ' ' \
+ u" {x.summary}
" \
+ .format(
+ x=meta, when=added, alt=cgi.escape(meta.rating_key),
+ quote=True, width=width, height=height, **image)
+ else:
+ # Shows
+ notify = u"
" \
+ ' ' \
+ u" {x.summary}
" \
+ .format(
+ x=meta, when=added, alt=cgi.escape(meta.rating_key),
+ quote=True, width=width, height=height, **image)
+
+ image_text = MIMEText(u'[image: {title}]'.format(**image), 'plain', 'utf-8')
+
+ return image_text, image, notify
+
+
+def send_email(msg_text_lst, notify_lst, image_lst, to, days):
+ """
+ Using info found here: http://stackoverflow.com/a/20485764/7286812
+ to accomplish emailing inline images
+ """
+ msg_html = MIMEText("""\
+
+
+
+
+
+
+
Below is the list of content added to Plex's {LIBRARY_NAMES} this week.
+
+ {notify_lst}
+
+
Hi!
-
+
{p.user} has watched {p.media_type}:{p.title} from a new IP address: {p.ip_address}
-
On {p.platform}[{p.player}] in
+
On {p.platform}[{p.player}] in
{g.city}, {g.country} {g.postal_code}
at {p.timestamp} on {p.datestamp}
@@ -69,7 +71,7 @@ class UserEmail(object):
def get_user_ip_addresses(user_id='', ip_address=''):
- # Get the user IP list from Tautulli
+ """Get the user IP list from Tautulli."""
payload = {'apikey': TAUTULLI_APIKEY,
'cmd': 'get_user_ips',
'user_id': user_id,
@@ -99,7 +101,7 @@ def get_user_ip_addresses(user_id='', ip_address=''):
def get_geoip_info(ip_address=''):
- # Get the geo IP lookup from Tautulli
+ """Get the geo IP lookup from Tautulli."""
payload = {'apikey': TAUTULLI_APIKEY,
'cmd': 'get_geoip_lookup',
'ip_address': ip_address}
@@ -123,7 +125,7 @@ def get_geoip_info(ip_address=''):
def get_user_email(user_id=''):
- # Get the user email from Tautulli
+ """Get the user email from Tautulli."""
payload = {'apikey': TAUTULLI_APIKEY,
'cmd': 'get_user',
'user_id': user_id}
@@ -147,7 +149,7 @@ def get_user_email(user_id=''):
def send_notification(arguments=None, geodata=None, useremail=None):
- # Format notification text
+ """Format notification text."""
try:
subject = SUBJECT_TEXT.format(p=arguments, g=geodata, u=useremail)
body = BODY_TEXT.format(p=arguments, g=geodata, u=useremail)
diff --git a/notify/notify_on_added.py b/notify/notify_on_added.py
new file mode 100644
index 0000000..f84756a
--- /dev/null
+++ b/notify/notify_on_added.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Tautulli > Settings > Notification Agents > Scripts > Bell icon:
+ [X] Notify on Recently Added
+Tautulli > Settings > Notification Agents > Scripts > Gear icon:
+ Recently Added: notify_on_added.py
+
+Tautulli > Settings > Notifications > Script > Script Arguments:
+-sn {show_name} -ena {episode_name} -ssn {season_num00} -enu {episode_num00} -srv {server_name} -med {media_type} -pos {poster_url} -tt {title} -sum {summary} -lbn {library_name}
+
+You can add more arguments if you want more details in the email body
+"""
+
+from email.mime.text import MIMEText
+import email.utils
+import smtplib
+import argparse
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-sn', '--show_name', action='store', default='',
+ help='The name of the TV show')
+parser.add_argument('-ena', '--episode_name', action='store', default='',
+ help='The name of the episode')
+parser.add_argument('-ssn', '--season_num', action='store', default='',
+ help='The season number of the TV show')
+parser.add_argument('-enu', '--episode_num', action='store', default='',
+ help='The episode number of the TV show')
+parser.add_argument('-srv', '--plex_server', action='store', default='',
+ help='The name of the Plex server')
+parser.add_argument('-med', '--show_type', action='store', default='',
+ help='The type of media')
+parser.add_argument('-pos', '--poster', action='store', default='',
+ help='The poster url')
+parser.add_argument('-tt', '--title', action='store', default='',
+ help='The title of the TV show')
+parser.add_argument('-sum', '--summary', action='store', default='',
+ help='The summary of the TV show')
+parser.add_argument('-lbn', '--library_name', action='store', default='',
+ help='The name of the TV show')
+p = parser.parse_args()
+
+# Edit user@email.com and shows
+users = [{'email': 'user1@gmail.com',
+ 'shows': ('show1', 'show2')
+ },
+ {'email': 'user2@gmail.com',
+ 'shows': ('show1', 'show2', 'show3')
+ },
+ {'email': 'user3@gmail.com',
+ 'shows': ('show1', 'show2', 'show3', 'show4')
+ }]
+
+# Kill script now if show_name is not in lists
+too = list('Match' for u in users if p.show_name in u['shows'])
+if not too:
+ print('Kill script now show_name is not in lists')
+ exit()
+
+# Join email addresses
+to = list([u['email'] for u in users if p.show_name in u['shows']])
+
+# Email settings
+name = 'Tautulli' # Your name
+sender = 'sender' # From email address
+email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com)
+email_port = 587 # Email port (Gmail: 587)
+email_username = 'email' # Your email username
+email_password = 'password' # Your email password
+email_subject = 'New episode for ' + p.show_name + ' is available on ' + p.plex_server # The email subject
+
+# Detailed body for tv shows
+show_html = """\
+
+
Hi!
+ {p.show_name} S{p.season_num} - E{p.episode_num} -- {p.episode_name} -- was recently added to {p.library_name} on PLEX
+
+
{p.summary}
+
+