From 2be0e376f8f4d80d20259fa366f9e0b4fa91e6eb Mon Sep 17 00:00:00 2001 From: Landon Abney Date: Thu, 20 Jun 2019 23:55:11 -0700 Subject: [PATCH] Massive style cleanup Adds a basic `setup.cfg` file with configurations for flake8 and pylama, fixes some basic issues with essentially every file including: * Many, many, whitespace line cleanups * Several unused variables and imports * Missing coding and shabang lines * Minor style fixes to more closely align with PEP8 * Turn `print` into function calls for Python 2/3 compat * A few minor bugs * Things like using an undefined `i` in `stream_limiter_ban_email.py` --- fun/playlist_manager.py | 116 ++++++------ fun/plex_lifx_color_theme.pyw | 177 +++++++++--------- fun/plexapi_haiku.py | 23 ++- killstream/kill_else_if_buffering.py | 19 +- killstream/kill_stream.py | 17 +- killstream/limiterr.py | 30 +-- killstream/limiterr_readme.md | 5 +- killstream/readme.md | 2 +- maps/ips_to_maps.py | 9 +- maps/readme.md | 6 +- maps/requirements.txt | 2 +- notify/find_unwatched_notify.py | 12 +- notify/notify_added_custom.py | 63 ++++--- notify/notify_delay.py | 9 +- notify/notify_fav_tv_all_movie.py | 20 +- notify/notify_newip.py | 16 +- notify/notify_on_added.py | 33 ++-- notify/notify_recently_aired.py | 9 +- notify/notify_user_favorites.py | 22 ++- notify/notify_user_newip.py | 49 +++-- notify/twitter_notify.py | 17 +- reporting/added_to_plex.py | 32 ++-- reporting/check_play.py | 11 +- reporting/check_plex_log.py | 12 +- reporting/drive_check.py | 22 ++- reporting/library_play_days.py | 8 +- reporting/plays_by_library.py | 16 +- reporting/plex_netflix_check.py | 19 +- reporting/server_compare.py | 28 +-- reporting/userplays_weekly_reporting.py | 21 ++- reporting/watched_percentages.py | 74 ++++---- reporting/weekly_stats_reporting.py | 13 +- setup.cfg | 11 ++ utility/bypass_auth_name.py | 10 +- utility/delete_watched_TV.py | 13 +- utility/enable_disable_all_guest_access.py | 6 +- utility/find_plex_meta.py | 15 +- utility/find_unwatched.py | 18 +- utility/get_serial_transcoders.py | 6 +- utility/grab_gdrive_media.py | 22 +-- utility/off_deck.py | 33 ++-- utility/plex_api_invite.py | 14 +- utility/plex_api_parental_control.py | 6 +- utility/plex_api_poster_pull.py | 13 +- utility/plex_api_share.py | 49 ++--- utility/plex_api_show_settings.py | 10 +- utility/plex_imgur_dl.py | 17 +- utility/plex_popular_playlist.py | 15 +- utility/plex_theme_songs.py | 17 +- utility/plexapi_delete_playlists.py | 10 +- utility/purge_removed_plex_friends.py | 6 +- utility/refresh_next_episode.py | 10 +- utility/remove_inactive_users.py | 8 +- utility/remove_watched_movies.py | 17 +- utility/stream_limiter_ban_email.py | 39 ++-- utility/sync_watch_status.py | 126 +++++++------ ...utulli_friendly_name_to_ombi_alias_sync.py | 6 +- 57 files changed, 803 insertions(+), 606 deletions(-) create mode 100644 setup.cfg 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.
File location: {x.file}

".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 index 4129f75..c007177 100644 --- a/notify/notify_added_custom.py +++ b/notify/notify_added_custom.py @@ -1,3 +1,6 @@ +#!/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. @@ -35,22 +38,21 @@ import uuid import argparse - -## EDIT THESE SETTINGS ## +# ## 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. +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'] +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_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 +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 @@ -60,7 +62,8 @@ poster_w = 100 art_h = 100 art_w = 205 -## /EDIT THESE SETTINGS ## +# ## /EDIT THESE SETTINGS ## + class METAINFO(object): def __init__(self, data=None): @@ -220,17 +223,21 @@ def build_html(rating_key, height, width, pic_type): if meta.grandparent_title == '' or meta.media_type == 'movie': # Movies notify = u"
{x.title} ({x.rating_key}) was added {when}.
" \ - u"
' \ - u"
" \ - '{alt} {x.summary}

" \ - .format(x=meta, when=added, alt=cgi.escape(meta.rating_key), quote=True, width=width, height=height,**image) + u"
' \ + u"
" \ + '{alt} {x.summary}

" \ + .format( + x=meta, when=added, alt=cgi.escape(meta.rating_key), + quote=True, width=width, height=height, **image) else: # Shows notify = u"
{x.grandparent_title}: {x.title} ({x.rating_key}) was added {when}." \ - u"
' \ - u"
" \ - '{alt} {x.summary}

" \ - .format(x=meta, when=added, alt=cgi.escape(meta.rating_key), quote=True, width=width, height=height, **image) + u"
' \ + u"
" \ + '{alt} {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') @@ -258,8 +265,10 @@ def send_email(msg_text_lst, notify_lst, image_lst, to, days):

- """.format(notify_lst="\n".join(notify_lst).encode("utf-8"), LIBRARY_NAMES=" & ".join(LIBRARY_NAMES) - , quote=True, ), 'html', 'utf-8') + """.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.format(days) @@ -291,19 +300,17 @@ def send_email(msg_text_lst, notify_lst, image_lst, to, days): if __name__ == '__main__': - parser = argparse.ArgumentParser(description="Send an email with what was added to Plex in the past week using Tautulli.") parser.add_argument('-t', '--type', help='Metadata picture type from Plex.', - required= True, choices=['art', 'poster']) + required=True, choices=['art', 'poster']) parser.add_argument('-s', '--size', help='Metadata picture size from Plex {Height Width}.', nargs='*') parser.add_argument('-d', '--days', help='Time frame for which to check recently added to Plex.', - required= True, type=int) + required=True, type=int) parser.add_argument('-u', '--users', help='Which users from Plex will be emailed.', nargs='+', default='self', type=str) parser.add_argument('-i', '--ignore', help='Which users from Plex to ignore.', nargs='+', default='None', type=str) - opts = parser.parse_args() TODAY = int(time.time()) @@ -328,8 +335,10 @@ if __name__ == '__main__': # Gather all users email addresses if opts.users == ['all']: - [to.append(x['email']) for x in get_users() if x['email'] is not None and x['email'] not in to - and x['username'] not in opts.ignore] + [to.append(x['email']) for x in get_users() + if x['email'] is not None and + x['email'] not in to and + x['username'] not in opts.ignore] elif opts.users != ['all'] and opts.users != 'self': for get_users in get_users(): for arg_users in opts.users: diff --git a/notify/notify_delay.py b/notify/notify_delay.py index 642dbed..b2bbecb 100644 --- a/notify/notify_delay.py +++ b/notify/notify_delay.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Delay Notification Agent message for concurrent streams @@ -19,7 +22,7 @@ import sys import argparse from time import sleep -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = '' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL CONCURRENT_TOTAL = 2 @@ -44,7 +47,7 @@ BODY_TEXT = """\ def get_activity(): - # Get the current activity on the PMS. + """Get the current activity on the PMS.""" payload = {'apikey': TAUTULLI_APIKEY, 'cmd': 'get_activity'} @@ -60,7 +63,7 @@ def get_activity(): def send_notification(subject_text, body_text): - # Format notification text + """Format notification text.""" try: subject = subject_text.format(p=p, total=cc_total) body = body_text.format(p=p, total=cc_total, time=TIMEOUT / 60) diff --git a/notify/notify_fav_tv_all_movie.py b/notify/notify_fav_tv_all_movie.py index f5854e0..5e96732 100644 --- a/notify/notify_fav_tv_all_movie.py +++ b/notify/notify_fav_tv_all_movie.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Notify users of recently added episode to show that they have watched at least LIMIT times via email. Also notify users of new movies. @@ -22,11 +25,11 @@ import smtplib import sys import argparse -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'XXXXXXX' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL -IGNORE_LST = ['123456', '123456'] # User_ids +IGNORE_LST = ['123456', '123456'] # User_ids LIMIT = 3 # Email settings @@ -74,6 +77,7 @@ TV_BODY = """\ user_dict = {} + class Users(object): def __init__(self, data=None): d = data or {} @@ -108,6 +112,7 @@ def get_user(user_id): except Exception as e: sys.stderr.write("Tautulli API 'get_user' request failed: {0}.".format(e)) + def get_users(): # Get the user list from Tautulli. payload = {'apikey': TAUTULLI_APIKEY, @@ -134,8 +139,9 @@ def get_history(showkey): r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload) response = r.json() res_data = response['response']['data']['data'] - return [UserHIS(data=d) for d in res_data if d['watched_status'] == 1 - and d['media_type'].lower() in ('episode', 'show')] + return [UserHIS(data=d) for d in res_data + if d['watched_status'] == 1 and + d['media_type'].lower() in ('episode', 'show')] except Exception as e: sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e)) @@ -177,7 +183,7 @@ def get_email(show): def send_email(to, email_subject, body_html): - ### Do not edit below ### + # ## Do not edit below ### message = MIMEText(body_html, 'html') message['Subject'] = email_subject message['From'] = email.utils.formataddr((name, sender)) @@ -188,7 +194,7 @@ def send_email(to, email_subject, body_html): mailserver.login(email_username, email_password) mailserver.sendmail(sender, to, message.as_string()) mailserver.quit() - print 'Email sent' + print('Email sent') if __name__ == '__main__': @@ -241,7 +247,7 @@ if __name__ == '__main__': body_html = MOVIE_BODY.format(p=p) send_email(to, email_subject, body_html) - elif p.media_type in ['show', 'season', 'episode']: + elif p.media_type in ['show', 'season', 'episode']: email_subject = TV_SUBJECT.format(p=p) to = get_email(int(p.grandparent_rating_key)) body_html = TV_BODY.format(p=p) diff --git a/notify/notify_newip.py b/notify/notify_newip.py index 0458917..bea3121 100644 --- a/notify/notify_newip.py +++ b/notify/notify_newip.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Pulling together User IP information and Email. @@ -19,7 +21,7 @@ import argparse import requests import sys -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = '' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL NOTIFIER_ID = 12 # The notification notifier ID @@ -39,9 +41,9 @@ BODY_TEXT = """\

Hi!
-
Poster unavailable +
Poster unavailable {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 index 56fce65..f84756a 100644 --- a/notify/notify_on_added.py +++ b/notify/notify_on_added.py @@ -1,10 +1,13 @@ +#!/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: + +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 @@ -13,7 +16,6 @@ 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 sys import argparse @@ -50,24 +52,24 @@ users = [{'email': 'user1@gmail.com', {'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() + 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) +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 +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 = """\ @@ -84,14 +86,13 @@ show_html = """\ """.format(p=p) -### Do not edit below ### +# ### Do not edit below ### # Check to see whether it is a tv show if p.show_type.lower() == 'show' or p.show_type.lower() == 'episode': message = MIMEText(show_html, 'html') message['Subject'] = email_subject message['From'] = email.utils.formataddr((name, sender)) - - + mailserver = smtplib.SMTP(email_server, email_port) mailserver.starttls() mailserver.ehlo() @@ -99,4 +100,4 @@ if p.show_type.lower() == 'show' or p.show_type.lower() == 'episode': mailserver.sendmail(sender, to, message.as_string()) mailserver.quit() else: - exit() + exit() diff --git a/notify/notify_recently_aired.py b/notify/notify_recently_aired.py index 1efcd38..821eff6 100644 --- a/notify/notify_recently_aired.py +++ b/notify/notify_recently_aired.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Description: Notify only if recently aired/released Author: Blacktwin @@ -64,6 +67,7 @@ aired_date = datetime.strptime(air_date, date_format) today = date.today() delta = today - aired_date.date() + def notify_recently_added(rating_key, notifier_id): # Get the metadata for a media item. payload = {'apikey': TAUTULLI_APIKEY, @@ -79,8 +83,9 @@ def notify_recently_added(rating_key, notifier_id): except Exception as e: sys.stderr.write("Tautulli API 'notify_recently_added' request failed: {0}.".format(e)) pass - + + if delta.days < RECENT_DAYS: notify_recently_added(rating_key, NOTIFIER_ID) else: - print("Not recent enough, no notification to be sent.") \ No newline at end of file + print("Not recent enough, no notification to be sent.") diff --git a/notify/notify_user_favorites.py b/notify/notify_user_favorites.py index c7a558c..165fcc6 100644 --- a/notify/notify_user_favorites.py +++ b/notify/notify_user_favorites.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Notify users of recently added episode to show that they have watched at least LIMIT times via email. Block users with IGNORE_LST. @@ -21,11 +24,11 @@ import smtplib import sys import argparse -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'XXXXXXX' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL -IGNORE_LST = [123456, 123456] # User_ids +IGNORE_LST = [123456, 123456] # User_ids LIMIT = 3 # Email settings @@ -38,6 +41,7 @@ email_password = '' # Your email password user_dict = {} + class Users(object): def __init__(self, data=None): d = data or {} @@ -74,7 +78,10 @@ def get_user(user_id): def get_history(showkey): - # Get the user history from Tautulli. Length matters! + """Get the user history from Tautulli. + + Length matters! + """ payload = {'apikey': TAUTULLI_APIKEY, 'cmd': 'get_history', 'grandparent_rating_key': showkey, @@ -84,8 +91,9 @@ def get_history(showkey): r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload) response = r.json() res_data = response['response']['data']['data'] - return [UserHIS(data=d) for d in res_data if d['watched_status'] == 1 - and d['media_type'].lower() in ('episode', 'show')] + return [UserHIS(data=d) for d in res_data + if d['watched_status'] == 1 and + d['media_type'].lower() in ('episode', 'show')] except Exception as e: sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e)) @@ -188,7 +196,7 @@ if __name__ == '__main__': """.format(p=p) - ### Do not edit below ### + # ## Do not edit below ### message = MIMEText(show_html, 'html') message['Subject'] = email_subject message['From'] = email.utils.formataddr((name, sender)) @@ -199,4 +207,4 @@ if __name__ == '__main__': mailserver.login(email_username, email_password) mailserver.sendmail(sender, to, message.as_string()) mailserver.quit() - print 'Email sent' + print('Email sent') diff --git a/notify/notify_user_newip.py b/notify/notify_user_newip.py index d11677b..c88a90d 100644 --- a/notify/notify_user_newip.py +++ b/notify/notify_user_newip.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Pulling together User IP information and Email. Enable the API under Settings > Access Control and remember your API key. @@ -13,9 +16,9 @@ from email.mime.text import MIMEText import email.utils import smtplib -## -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} -ip {ip_address} -us {user} -uid {user_id} -pf {platform} -pl {player} -da {datestamp} -ti {timestamp} +# ## -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} -ip {ip_address} -us {user} -uid {user_id} -pf {platform} -pl {player} -da {datestamp} -ti {timestamp} -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'xxxxxxxxxxx' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL @@ -44,17 +47,20 @@ BODY_TEXT = """\ """ # Email settings -name = '' # Your name -sender = '' # From email address -email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com) +name = '' # Your name +sender = '' # From email address +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_username = '' # Your email username +email_password = '' # Your email password email_subject = "New IP has been detected using Plex." -IGNORE_LST = ['123456', '123456'] # User_id +IGNORE_LST = ['123456', '123456'] # User_id + + +# ##Geo Space## + -##Geo Space## class GeoData(object): def __init__(self, data=None): data = data or {} @@ -62,22 +68,28 @@ class GeoData(object): self.city = data.get('city', 'N/A') self.postal_code = data.get('postal_code', 'N/A') -##USER Space## + +# ##USER Space## + + class UserEmail(object): def __init__(self, data=None): data = data or {} self.email = data.get('email', 'N/A') self.user_id = data.get('user_id', 'N/A') self.user_thumb = data.get('user_thumb', 'N/A') - -##API Space## + + +# ##API Space## + + def get_user_ip_addresses(user_id='', ip_address=''): # Get the user IP list from Tautulli payload = {'apikey': TAUTULLI_APIKEY, 'cmd': 'get_user_ips', 'user_id': user_id, 'search': ip_address} - + try: r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload) response = r.json() @@ -99,6 +111,7 @@ def get_user_ip_addresses(user_id='', ip_address=''): except Exception as e: sys.stderr.write("Tautulli API 'get_user_ip_addresses' request failed: {0}.".format(e)) + def get_geoip_info(ip_address=''): # Get the geo IP lookup from Tautulli payload = {'apikey': TAUTULLI_APIKEY, @@ -146,6 +159,7 @@ def get_user_email(user_id=''): sys.stderr.write("Tautulli API 'get_user' request failed: {0}.".format(e)) return UserEmail() + def send_notification(arguments=None, geodata=None, useremail=None): # Format notification text try: @@ -163,12 +177,12 @@ def send_notification(arguments=None, geodata=None, useremail=None): mailserver.login(email_username, email_password) mailserver.sendmail(sender, u.email, message.as_string()) mailserver.quit() - print 'Email sent' + print('Email sent') except Exception as e: sys.stderr.write("Email Failure: {0}.".format(e)) -def clr_sql(ip): +def clr_sql(ip): try: payload = {'apikey': TAUTULLI_APIKEY, 'cmd': 'sql', @@ -179,6 +193,7 @@ def clr_sql(ip): except Exception as e: sys.stderr.write("Tautulli API 'get_sql' request failed: {0}.".format(e)) + if __name__ == '__main__': # Parse arguments from Tautulli parser = argparse.ArgumentParser() @@ -188,7 +203,7 @@ if __name__ == '__main__': parser.add_argument('-us', '--user', action='store', default='', help='Username of the person watching the stream') parser.add_argument('-uid', '--user_id', action='store', default='', - help='User_ID of the person watching the stream') + help='User_ID of the person watching the stream') parser.add_argument('-med', '--media_type', action='store', default='', help='The media type of the stream') parser.add_argument('-tt', '--title', action='store', default='', @@ -217,7 +232,7 @@ if __name__ == '__main__': 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() if p.user_id not in IGNORE_LST: diff --git a/notify/twitter_notify.py b/notify/twitter_notify.py index ec7ba3a..d0004df 100644 --- a/notify/twitter_notify.py +++ b/notify/twitter_notify.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ 1. Install the requests module for python. pip install requests @@ -14,18 +17,18 @@ Tautulli > Settings > Notifications > Script > Script Arguments: https://gist.github.com/blacktwin/261c416dbed08291e6d12f6987d9bafa """ -from twitter import * +from twitter import Twitter, OAuth import argparse import requests import os -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TOKEN = '' TOKEN_SECRET = '' CONSUMER_KEY = '' CONSUMER_SECRET = '' -TITLE_FIND = ['Friends'] # Title to ignore ['Snow White'] +TITLE_FIND = ['Friends'] # Title to ignore ['Snow White'] TWITTER_USER = ' @username' BODY_TEXT = '' @@ -80,12 +83,13 @@ if __name__ == '__main__': p = parser.parse_args() - if p.media_type == 'movie': BODY_TEXT = MOVIE_TEXT.format(media_type=p.media_type, title=p.title, duration=p.duration) elif p.media_type == 'episode': - BODY_TEXT = TV_TEXT.format(media_type=p.media_type, show_name=p.show_name, title=p.title, - season_num00=p.season_num, episode_num00=p.episode_num, duration=p.duration) + BODY_TEXT = TV_TEXT.format( + media_type=p.media_type, show_name=p.show_name, title=p.title, + season_num00=p.season_num, episode_num00=p.episode_num, + duration=p.duration) else: exit() @@ -101,7 +105,6 @@ if __name__ == '__main__': for chunk in request: image.write(chunk) - t_upload = Twitter(domain='upload.twitter.com', auth=OAuth(TOKEN, TOKEN_SECRET, CONSUMER_KEY, CONSUMER_SECRET)) diff --git a/reporting/added_to_plex.py b/reporting/added_to_plex.py index 339023b..822067a 100644 --- a/reporting/added_to_plex.py +++ b/reporting/added_to_plex.py @@ -1,7 +1,10 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Find when media was added between STARTFRAME and ENDFRAME to Plex through Tautulli. -Some Exceptions have been commented out to supress what is printed. +Some Exceptions have been commented out to supress what is printed. Uncomment Exceptions if you run into problem and need to investigate. """ @@ -9,22 +12,20 @@ import requests import sys import time - -STARTFRAME = 1480550400 # 2016, Dec 1 in seconds -ENDFRAME = 1488326400 # 2017, March 1 in seconds +STARTFRAME = 1480550400 # 2016, Dec 1 in seconds +ENDFRAME = 1488326400 # 2017, March 1 in seconds TODAY = int(time.time()) -LASTMONTH = int(TODAY - 2629743) # 2629743 = 1 month in seconds +LASTMONTH = int(TODAY - 2629743) # 2629743 = 1 month in seconds # Uncomment to change range to 1 month ago - Today # STARTFRAME = LASTMONTH # ENDFRAME = TODAY - -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'XXXXX' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL -LIBRARY_NAMES = ['TV Shows', 'Movies'] # Names of your libraries you want to check. +LIBRARY_NAMES = ['TV Shows', 'Movies'] # Names of your libraries you want to check. class LIBINFO(object): @@ -70,6 +71,7 @@ def get_new_rating_keys(rating_key, media_type): except Exception as e: sys.stderr.write("Tautulli API 'get_new_rating_keys' request failed: {0}.".format(e)) + def get_library_media_info(section_id): # Get the data on the Tautulli media info tables. Length matters! payload = {'apikey': TAUTULLI_APIKEY, @@ -88,6 +90,7 @@ def get_library_media_info(section_id): except Exception as e: sys.stderr.write("Tautulli API 'get_library_media_info' request failed: {0}.".format(e)) + def get_metadata(rating_key): # Get the metadata for a media item. payload = {'apikey': TAUTULLI_APIKEY, @@ -106,6 +109,7 @@ def get_metadata(rating_key): except Exception as e: sys.stderr.write("Tautulli API 'get_metadata' 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, @@ -122,6 +126,7 @@ def update_library_media_info(section_id): except Exception as e: sys.stderr.write("Tautulli API 'update_library_media_info' request failed: {0}.".format(e)) + def get_libraries_table(): # Get the data on the Tautulli libraries table. payload = {'apikey': TAUTULLI_APIKEY, @@ -181,15 +186,16 @@ for i in sorted(show_lst, reverse=True): # Shows print(u"{x.grandparent_title}: {x.title} ({x.rating_key}) was added {when}.".format(x=x, when=added)) - except Exception as e: + except Exception: # Remove commented print below to investigate problems. # print("Metadata failed. Likely end of range: {e}").format(e=e) # Remove break if not finding files in range break -print("There were {amount} files added between {start}:{end}".format(amount=len(count_lst), - start=time.ctime(float(STARTFRAME)), - end=time.ctime(float(ENDFRAME)))) +print("There were {amount} files added between {start}:{end}".format( + amount=len(count_lst), + start=time.ctime(float(STARTFRAME)), + end=time.ctime(float(ENDFRAME)))) print("Total movies: {}".format(count_lst.count('movie'))) print("Total shows: {}".format(count_lst.count('show') + count_lst.count('episode'))) -print("Total size of files added: {}MB".format(sum(size_lst)>>20)) +print("Total size of files added: {}MB".format(sum(size_lst) >> 20)) diff --git a/reporting/check_play.py b/reporting/check_play.py index ed841b2..459e7d7 100644 --- a/reporting/check_play.py +++ b/reporting/check_play.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # 1. Install the requests module for python. # pip install requests # 2. Add script arguments in Tautulli. @@ -10,7 +13,7 @@ import sys user = sys.argv[1] title = sys.argv[2] -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'XXXXXXXXXX' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL NOTIFIER_ID = 10 # The notification notifier ID for Tautulli @@ -25,7 +28,7 @@ BODY_TEXT = """\

-""" %(user, title) +""" % (user, title) class UserHIS(object): @@ -33,9 +36,9 @@ class UserHIS(object): data = data or {} self.watched = [d['watched_status'] for d in data] - + def get_history(): - # Get the user IP list from Tautulli + """Get the history from Tautulli.""" payload = {'apikey': TAUTULLI_APIKEY, 'cmd': 'get_history', 'user': user, diff --git a/reporting/check_plex_log.py b/reporting/check_plex_log.py index b40594f..de8f168 100644 --- a/reporting/check_plex_log.py +++ b/reporting/check_plex_log.py @@ -1,19 +1,23 @@ -''' +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" Run script by itself. Will look for WARN code followed by /library/metadata/ str in Plex logs. This is find files that are corrupt or having playback issues. I corrupted a file to test. -''' +""" import requests import sys -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'XXXXXXXX' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL lib_met = [] err_title = [] + class PlexLOG(object): def __init__(self, data=None): self.error_msg = [] @@ -43,6 +47,7 @@ def get_plex_log(): except Exception as e: sys.stderr.write("Tautulli API 'get_plex_log' request failed: {0}.".format(e)) + def get_history(key): # Get the user IP list from Tautulli payload = {'apikey': TAUTULLI_APIKEY, @@ -59,6 +64,7 @@ def get_history(key): except Exception as e: sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e)) + if __name__ == '__main__': p_log = get_plex_log() for co, msg in p_log.error_msg: diff --git a/reporting/drive_check.py b/reporting/drive_check.py index b788c60..f4ff713 100644 --- a/reporting/drive_check.py +++ b/reporting/drive_check.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + import psutil import requests @@ -6,20 +9,21 @@ drive = 'F:' disk = psutil.disk_partitions() -TAUTULLI_URL = 'http://localhost:8182/' # Your Tautulli URL -TAUTULLI_APIKEY = 'xxxxxx' # Enter your Tautulli API Key -NOTIFIER_LST = [10, 11] # The Tautulli notifier notifier id found here: https://github.com/drzoidberg33/plexpy/blob/master/plexpy/notifiers.py#L43 -NOTIFY_SUBJECT = 'Tautulli' # The notification subject -NOTIFY_BODY = 'The Plex disk {0} was not found'.format(drive) # The notification body +TAUTULLI_URL = 'http://localhost:8182/' # Your Tautulli URL +TAUTULLI_APIKEY = 'xxxxxx' # Enter your Tautulli API Key +NOTIFIER_LST = [10, 11] # The Tautulli notifier notifier id found here: https://github.com/drzoidberg33/plexpy/blob/master/plexpy/notifiers.py#L43 +NOTIFY_SUBJECT = 'Tautulli' # The notification subject +NOTIFY_BODY = 'The Plex disk {0} was not found'.format(drive) # The notification body disk_check = [True for i in disk if drive in i.mountpoint] if not disk_check: # Send the notification through Tautulli - payload = {'apikey': TAUTULLI_APIKEY, - 'cmd': 'notify', - 'subject': NOTIFY_SUBJECT, - 'body': NOTIFY_BODY} + payload = { + 'apikey': TAUTULLI_APIKEY, + 'cmd': 'notify', + 'subject': NOTIFY_SUBJECT, + 'body': NOTIFY_BODY} for notifier in NOTIFIER_LST: payload['notifier_id'] = notifier diff --git a/reporting/library_play_days.py b/reporting/library_play_days.py index 263b63b..c3fc4f3 100644 --- a/reporting/library_play_days.py +++ b/reporting/library_play_days.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Use Tautulli to print plays by library from 0, 1, 7, or 30 days ago. 0 = total @@ -26,14 +29,15 @@ import requests import sys import argparse -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'xxxxx' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL OUTPUT = 'Library: {section}\nDays: {days}\nPlays: {plays}' -## CODE BELOW ## +# ## CODE BELOW ## + def get_library_names(): # Get a list of new rating keys for the PMS of all of the item's parent/children. diff --git a/reporting/plays_by_library.py b/reporting/plays_by_library.py index 332882b..fba72d9 100644 --- a/reporting/plays_by_library.py +++ b/reporting/plays_by_library.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Use Tautulli to pull plays by library @@ -6,8 +9,8 @@ optional arguments: -l [ ...], --libraries [ ...] Space separated list of case sensitive names to process. Allowed names are: (choices: All Library Names) - - + + Usage: plays_by_library.py -l "TV Shows" Movies TV Shows - Plays: 2859 @@ -18,18 +21,17 @@ Usage: import requests import sys import argparse -import json +# import json - - -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'xxxxxx' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL OUTPUT = '{section} - Plays: {plays}' -## CODE BELOW ## +# ## CODE BELOW ## + def get_libraries_table(sections=None): # Get a list of new rating keys for the PMS of all of the item's parent/children. diff --git a/reporting/plex_netflix_check.py b/reporting/plex_netflix_check.py index eacb613..d5bd0df 100644 --- a/reporting/plex_netflix_check.py +++ b/reporting/plex_netflix_check.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ usage: plex_netflix_check.py [-h] [-l [...]] [-s ] [-t ] @@ -29,15 +32,14 @@ import argparse from xmljson import badgerfish as bf from lxml.html import fromstring from time import sleep -import json from plexapi.server import PlexServer # pip install plexapi -## Edit ## +# ## Edit ## PLEX_URL = 'http://localhost:32400' PLEX_TOKEN = 'xxxx' -## /Edit ## +# ## /Edit ## sess = requests.Session() sess.verify = False @@ -57,7 +59,7 @@ def instantwatch_search(name, media_type, site, search_limit): elif media_type == 'episode': content_type = '4' else: - content_type ='' + content_type = '' payload = {'content_type': content_type, 'q': name.lower()} @@ -108,7 +110,7 @@ def instantwatch_search(name, media_type, site, search_limit): pass for data in results['span']: - if data['@class'] == 'title' and search_limit is not 0: + if data['@class'] == 'title' and search_limit != 0: if str(data['a']['$']).lower().startswith(name.lower()): if amazon_id: if data['a']['@data-title-id'] == amazon_id: @@ -119,10 +121,10 @@ def instantwatch_search(name, media_type, site, search_limit): print('Page: {}{}'.format(NETFLIX_URL, data['a']['@data-title-id'])) results_count += 1 search_limit -= 1 - if search_limit is 0: + if search_limit == 0: limit = True - elif data['@class'] == 'title' and search_limit is 0 and limit is False: + elif data['@class'] == 'title' and search_limit == 0 and limit is False: if data['a']['$'].lower().startswith(name.lower()): if amazon_id: if data['a']['@data-title-id'] == amazon_id: @@ -206,7 +208,7 @@ def main(): '(choices: %(choices)s)\n(default: %(default)s)') parser.add_argument('-site', '--site', metavar='', choices=['Netflix', 'Amazon', 'Both'], nargs='?', default='Both', help='Refine search for name by using type.\n' - '(choices: %(choices)s)\n(default: %(default)s)') + '(choices: %(choices)s)\n(default: %(default)s)') parser.add_argument('-sl', '--search_limit', metavar='', nargs='?', type=int, default=5, help='Define number of search returns from page. Zero returns all.' '\n(default: %(default)s)') @@ -223,5 +225,6 @@ def main(): else: plex_library_search(opts.library[0], opts.site, opts.episodes, opts.search_limit) + if __name__ == '__main__': main() diff --git a/reporting/server_compare.py b/reporting/server_compare.py index 6236054..d31d3fd 100644 --- a/reporting/server_compare.py +++ b/reporting/server_compare.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Description: Comparing content between two or more Plex servers. Creates .json file in script directory of server compared. @@ -103,14 +105,14 @@ def get_meta(meta): """ thumb_url = '{}{}?X-Plex-Token={}'.format( meta._server._baseurl, meta.thumb, meta._server._token) - + meta_dict = {'title': meta.title, 'rating': meta.rating if - meta.rating != None else 0.0, + meta.rating is not None else 0.0, 'genres': [x.tag for x in meta.genres], 'server': [meta._server.friendlyName], 'thumb': [thumb_url] - } + } if meta.guid: # guid will return (com.plexapp.agents.imdb://tt4302938?lang=en) # Agents will differ between servers. @@ -193,8 +195,9 @@ def org_diff(lst_dicts, media_type, main_server): # Sort item list by Plex rating # Duplicates will use originals rating meta_lst = sorted(meta_lst, key=lambda d: d['rating'], reverse=True) - diff_dict[mtype] = {'combined': {'count': len(meta_lst), - 'list': meta_lst}} + diff_dict[mtype] = {'combined': { + 'count': len(meta_lst), + 'list': meta_lst}} print('...finding {}s missing from {}'.format( mtype, main_server)) @@ -205,13 +208,15 @@ def org_diff(lst_dicts, media_type, main_server): # Main Server name is absent in items server list elif main_server in item['server'] and len(item['server']) == 1: unique.append(item) - diff_dict[mtype].update({'missing': {'count': len(missing), - 'list': missing}}) + diff_dict[mtype].update({'missing': { + 'count': len(missing), + 'list': missing}}) print('...finding {}s unique to {}'.format( mtype, main_server)) - diff_dict[mtype].update({'unique': {'count': len(unique), - 'list': unique}}) + diff_dict[mtype].update({'unique': { + 'count': len(unique), + 'list': unique}}) return diff_dict @@ -220,7 +225,7 @@ if __name__ == "__main__": parser = argparse.ArgumentParser( description="Comparing content between two or more Plex servers.", - formatter_class = argparse.RawTextHelpFormatter) + formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('--server', required=True, choices=SERVER_DICT.keys(), action='append', nargs='?', metavar='', help='Choose servers to connect to and compare.\n' @@ -272,7 +277,8 @@ if __name__ == "__main__": main_dict = org_diff(combined_lst, opts.media_type, main_server.friendlyName) - filename = 'diff_{}_{}_servers.json'.format(opts.server[0],'_'.join(servers)) + filename = 'diff_{}_{}_servers.json'.format( + opts.server[0], '_'.join(servers)) with open(filename, 'w') as fp: json.dump(main_dict, fp, indent=4, sort_keys=True) diff --git a/reporting/userplays_weekly_reporting.py b/reporting/userplays_weekly_reporting.py index f3717f7..9ae7ac0 100644 --- a/reporting/userplays_weekly_reporting.py +++ b/reporting/userplays_weekly_reporting.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Use Tautulli to count how many plays per user occurred this week. Notify via Tautulli Notification @@ -10,7 +13,7 @@ import time TODAY = int(time.time()) LASTWEEK = int(TODAY - 7 * 24 * 60 * 60) -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'XXXXXX' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL SUBJECT_TEXT = "Tautulli Weekly Plays Per User" @@ -29,12 +32,13 @@ class UserHIS(object): self.full_title = d['full_title'] self.date = d['date'] + def get_history(): # Get the Tautulli history. Count matters!!! payload = {'apikey': TAUTULLI_APIKEY, 'cmd': 'get_history', 'length': 100000} - + try: r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload) response = r.json() @@ -42,10 +46,11 @@ def get_history(): res_data = response['response']['data']['data'] return [UserHIS(data=d) for d in res_data if d['watched_status'] == 1 and LASTWEEK < d['date'] < TODAY] - + except Exception as e: sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e)) + def send_notification(BODY_TEXT): # Format notification text try: @@ -73,13 +78,15 @@ def send_notification(BODY_TEXT): sys.stderr.write("Tautulli API 'notify' request failed: {0}.".format(e)) return None + def add_to_dictlist(d, key, val): if key not in d: d[key] = [val] else: d[key].append(val) -user_dict ={} + +user_dict = {} notify_lst = [] [add_to_dictlist(user_dict, h.user, h.media) for h in get_history()] @@ -106,7 +113,9 @@ BODY_TEXT = """\

-""".format(notify_lst="\n".join(notify_lst).encode("utf-8"),end=time.ctime(float(TODAY)), - start=time.ctime(float(LASTWEEK))) +""".format( + notify_lst="\n".join(notify_lst).encode("utf-8"), + end=time.ctime(float(TODAY)), + start=time.ctime(float(LASTWEEK))) send_notification(BODY_TEXT) diff --git a/reporting/watched_percentages.py b/reporting/watched_percentages.py index 9e4824f..f9507eb 100644 --- a/reporting/watched_percentages.py +++ b/reporting/watched_percentages.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + import time import argparse from plexapi.myplex import MyPlexAccount @@ -36,6 +38,7 @@ VERIFY_SSL = False timestr = time.strftime("%Y%m%d-%H%M%S") + class Connection: def __init__(self, url=None, apikey=None, verify_ssl=False): self.url = url @@ -47,15 +50,15 @@ class Connection: pool_block=True) self.session.mount('http://', self.adapters) self.session.mount('https://', self.adapters) - + # Ignore verifying the SSL certificate if verify_ssl is False: self.session.verify = False # Disable the warning that the request is insecure, we know that... import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - - + + class Library(object): def __init__(self, data=None): d = data or {} @@ -66,38 +69,38 @@ class Library(object): try: self.parent_count = d['parent_count'] self.child_count = d['child_count'] - except Exception as e: + except Exception: # print(e) pass - + class Tautulli: def __init__(self, connection): self.connection = connection - + def _call_api(self, cmd, payload, method='GET'): payload['cmd'] = cmd payload['apikey'] = self.connection.apikey - + try: response = self.connection.session.request(method, self.connection.url + '/api/v2', params=payload) except RequestException as e: print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL? Error: {}".format(cmd, e)) return - + try: response_json = response.json() except ValueError: print("Failed to parse json response for Tautulli API cmd '{}'".format(cmd)) return - + if response_json['response']['result'] == 'success': return response_json['response']['data'] else: error_msg = response_json['response']['message'] print("Tautulli API cmd '{}' failed: {}".format(cmd, error_msg)) return - + def get_watched_history(self, user=None, section_id=None, rating_key=None, start=None, length=None): """Call Tautulli's get_history api endpoint""" payload = {"order_column": "full_title", @@ -112,14 +115,14 @@ class Tautulli: payload["start"] = start if length: payload["lengh"] = length - + history = self._call_api('get_history', payload) - + return [d for d in history['data'] if d['watched_status'] == 1] def get_libraries(self): """Call Tautulli's get_libraries api endpoint""" - + payload = {} return self._call_api('get_libraries', payload) @@ -131,7 +134,7 @@ class Plex: if token and url: session = Connection().session self.server = PlexServer(baseurl=url, token=token, session=session) - + def all_users(self): """All users Returns @@ -141,9 +144,9 @@ class Plex: users = {self.account.title: self.account} for user in self.account.users(): users[user.title] = user - + return users - + def all_sections(self): """All sections from server Returns @@ -152,7 +155,7 @@ class Plex: {section title: section object} """ sections = {section.title: section for section in self.server.library.sections()} - + return sections def all_sections_totals(self, library=None): @@ -174,17 +177,17 @@ class Plex: section_total = len(section.search(libtype='episode')) else: continue - + if library: return section_total - + section_totals[section.title] = section_total return section_totals def make_pie(user_dict, sections_dict, title, filename=None, headless=None): - + import matplotlib as mpl mpl.rcParams['text.color'] = FONT_COLOR mpl.rcParams['axes.labelcolor'] = FONT_COLOR @@ -209,7 +212,7 @@ def make_pie(user_dict, sections_dict, title, filename=None, headless=None): ax.pie(fracs, explode=EXPLODE, colors=COLORS, pctdistance=1.3, autopct='%1.1f%%', shadow=True, startangle=300, radius=0.8, wedgeprops=dict(width=0.5, edgecolor=BACKGROUND_COLOR)) - + if user_position == 0: ax.set_title("{}: {}".format(library, library_total), bbox=BBOX_PROPS, ha='center', va='bottom', size=12) @@ -223,7 +226,7 @@ def make_pie(user_dict, sections_dict, title, filename=None, headless=None): plt.suptitle(title, bbox=BBOX_PROPS, size=15) plt.tight_layout() fig.subplots_adjust(top=0.88) - + if filename: plt.savefig('{}_{}.png'.format(filename, timestr), facecolor=BACKGROUND_COLOR) print('Image saved as: {}_{}.png'.format(filename, timestr)) @@ -235,13 +238,13 @@ if __name__ == '__main__': parser = argparse.ArgumentParser(description="Show watched percentage of users by libraries.", formatter_class=argparse.RawTextHelpFormatter) - + servers = parser.add_mutually_exclusive_group() servers.add_argument('--plex', default=False, action='store_true', - help='Pull data from Plex') + help='Pull data from Plex') servers.add_argument('--tautulli', default=False, action='store_true', - help='Pull data from Tautulli') - + help='Pull data from Tautulli') + parser.add_argument('--libraries', nargs='*', metavar='library', help='Libraries to scan for watched content.') parser.add_argument('--users', nargs='*', metavar='users', @@ -251,20 +254,19 @@ if __name__ == '__main__': parser.add_argument('--filename', type=str, default='Users_Watched_{}'.format(timestr), metavar='', help='Filename of pie chart. None will not save. \n(default: %(default)s)') parser.add_argument('--headless', action='store_true', help='Run headless.') - + opts = parser.parse_args() - sections_totals_dict = {} sections_dict = {} user_dict = {} title = "User's Watch Percentage by Library\nFrom: {}" - + if opts.plex: admin_account = Plex(PLEX_TOKEN) plex_server = Plex(PLEX_TOKEN, PLEX_URL) title = title.format(plex_server.server.friendlyName) - + for library in opts.libraries: section_total = plex_server.all_sections_totals(library) sections_totals_dict[library] = section_total @@ -284,7 +286,7 @@ if __name__ == '__main__': section_watched_total = len(section_watched_lst) percent_watched = 100 * (float(section_watched_total) / float(section_total)) print(" {} has watched {} items ({}%).".format(user, section_watched_total, int(percent_watched))) - + if user_dict.get(user): user_dict[user].update({library: section_watched_total}) else: @@ -295,7 +297,7 @@ if __name__ == '__main__': user_dict[user].update({library: 0}) else: user_dict[user] = {library: 0} - + elif opts.tautulli: # Create a Tautulli instance tautulli_server = Tautulli(Connection(url=TAUTULLI_URL.rstrip('/'), @@ -307,7 +309,7 @@ if __name__ == '__main__': for section in tautulli_sections: library = Library(section) sections_dict[library.title] = library - + for library in opts.libraries: section = sections_dict[library] if section.type == "movie": @@ -317,7 +319,7 @@ if __name__ == '__main__': else: print("Not doing that...") section_total = 0 - + print("Section: {}, has {} items.".format(library, section_total)) sections_totals_dict[library] = section_total for user in opts.users: @@ -337,7 +339,7 @@ if __name__ == '__main__': elif not all([tt_watched]): break start += count - + except Exception as e: print(user, e) @@ -351,4 +353,4 @@ if __name__ == '__main__': user_dict[user] = {library: section_watched_total} if opts.pie: - make_pie(user_dict, sections_totals_dict, title, opts.filename, opts.headless) \ No newline at end of file + make_pie(user_dict, sections_totals_dict, title, opts.filename, opts.headless) diff --git a/reporting/weekly_stats_reporting.py b/reporting/weekly_stats_reporting.py index b8cec44..4c47be5 100644 --- a/reporting/weekly_stats_reporting.py +++ b/reporting/weekly_stats_reporting.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Pull library and user statistics of last week. @@ -13,7 +16,7 @@ import requests import sys import time import datetime -import json +# import json from operator import itemgetter import argparse @@ -72,6 +75,7 @@ BODY_TEXT = """\ # /EDIT THESE SETTINGS # + def get_history(section_id, check_date): # Get the Tautulli history. payload = {'apikey': TAUTULLI_APIKEY, @@ -156,7 +160,7 @@ def send_notification(body_text): def sizeof_fmt(num, suffix='B'): # Function found https://stackoverflow.com/a/1094933 - for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: + for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 @@ -171,7 +175,7 @@ def date_split(to_split): def add_to_dictval(d, key, val): - #print(d, key, val) + # print(d, key, val) if key not in d: d[key] = val else: @@ -191,7 +195,7 @@ def get_server_stats(date_ranges): user_stats_lst = [] user_stats_dict = {} user_names_lst = [] - user_durations_lst =[] + user_durations_lst = [] print('Checking library stats.') for sections in get_libraries(): @@ -290,5 +294,6 @@ def main(): print('Sending message.') send_notification(BODY_TEXT) + if __name__ == '__main__': main() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d27c5b4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,11 @@ +; Contains configuration for various linters + +; E501: Disable line length limits (for now) +; W504: Require newlines after binary operators, use W503 for requiring the +; operators on the next line + +[flake8] +ignore = E501,W504 + +[pylama] +ignore = E501,W504 diff --git a/utility/bypass_auth_name.py b/utility/bypass_auth_name.py index 71aea16..5c0c4af 100644 --- a/utility/bypass_auth_name.py +++ b/utility/bypass_auth_name.py @@ -1,5 +1,7 @@ #!/usr/bin/env python -''' +# -*- coding: utf-8 -*- + +""" Use Tautulli to pull last IP address from user and add to List of IP addresses and networks that are allowed without auth in Plex. optional arguments: @@ -13,14 +15,14 @@ optional arguments: (default: None) List of IP addresses is cleared before adding new IPs -''' +""" import requests import argparse import sys -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## PLEX_TOKEN = 'xxxx' PLEX_URL = 'http://localhost:32400' TAUTULLI_APIKEY = 'xxxx' # Your Tautulli API key @@ -78,7 +80,7 @@ if __name__ == '__main__': parser.add_argument('-u', '--users', nargs='+', type=str, choices=user_lst, metavar='', help='Space separated list of case sensitive names to process. Allowed names are: \n' '(choices: %(choices)s) \n(default: %(default)s)') - parser.add_argument('-c', '--clear', nargs='?',default=None, metavar='', + parser.add_argument('-c', '--clear', nargs='?', default=None, metavar='', help='Clear List of IP addresses and networks that are allowed without auth in Plex: \n' '(default: %(default)s)') diff --git a/utility/delete_watched_TV.py b/utility/delete_watched_TV.py index 58f094c..b5f5c09 100644 --- a/utility/delete_watched_TV.py +++ b/utility/delete_watched_TV.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ From a list of TV shows, check if users in a list has watched shows episodes. If all users in list have watched an episode of listed show, then delete episode. @@ -9,8 +12,7 @@ import requests import sys import os - -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'xxxxx' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL SHOW_LST = [123456, 123456, 123456, 123456] # Show rating keys. @@ -109,8 +111,9 @@ for user in USER_LST: for meta_dict in meta_lst: if set(USER_LST) == set(meta_dict['watched_by']): - print("{} {} has been watched by {}".format(meta_dict['grandparent_title'].encode('UTF-8'), - meta_dict['title'].encode('UTF-8'), - " & ".join(USER_LST))) + print("{} {} has been watched by {}".format( + meta_dict['grandparent_title'].encode('UTF-8'), + meta_dict['title'].encode('UTF-8'), + " & ".join(USER_LST))) print("Removing {}".format(meta_dict['file'])) os.remove(meta_dict['file']) diff --git a/utility/enable_disable_all_guest_access.py b/utility/enable_disable_all_guest_access.py index 42ef664..42c4511 100644 --- a/utility/enable_disable_all_guest_access.py +++ b/utility/enable_disable_all_guest_access.py @@ -1,6 +1,8 @@ #!/usr/bin/env python -""" -Description: Enable or disable all users remote access to Tautulli +# -*- coding: utf-8 -*- + +"""Enable or disable all users remote access to Tautulli. + Author: DirtyCajunRice Requires: requests, python3.6+ """ diff --git a/utility/find_plex_meta.py b/utility/find_plex_meta.py index 81164bb..de7dba1 100644 --- a/utility/find_plex_meta.py +++ b/utility/find_plex_meta.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + ''' Find location of Plex metadata. @@ -18,7 +20,7 @@ import hashlib import argparse import requests -## Edit ## +# ## Edit ## PLEX_URL = '' PLEX_TOKEN = '' PLEX_URL = CONFIG.data['auth'].get('server_baseurl', PLEX_URL) @@ -28,7 +30,7 @@ PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN) PLEX_LOCAL_TV_PATH = os.path.join(os.getenv('LOCALAPPDATA'), 'Plex Media Server\Metadata\TV Shows') PLEX_LOCAL_MOVIE_PATH = os.path.join(os.getenv('LOCALAPPDATA'), 'Plex Media Server\Metadata\Movies') PLEX_LOCAL_ALBUM_PATH = os.path.join(os.getenv('LOCALAPPDATA'), 'Plex Media Server\Metadata\Albums') -## /Edit ## +# ## /Edit ## sess = requests.Session() # Ignore verifying the SSL certificate @@ -44,6 +46,7 @@ if sess.verify is False: plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess) + def hash_to_path(hash_str, path, title, media_type, artist=None): full_hash = hashlib.sha1(hash_str).hexdigest() hash_path = '{}\{}{}'.format(full_hash[0], full_hash[1::1], '.bundle') @@ -54,12 +57,11 @@ def hash_to_path(hash_str, path, title, media_type, artist=None): output = "{} titled: {}\nPath: {}".format(media_type.title(), title, full_path) print(output) + def get_plex_hash(search, mediatype=None): for searched in plex.search(search, mediatype=mediatype): - # Remove special characters from name - clean_title = re.sub('\W+',' ', searched.title) - if searched.type == 'show': # Need to find guid. + if searched.type == 'show': # Get tvdb_if from first episode db_id = searched.episodes()[0].guid # Find str to pop @@ -79,7 +81,7 @@ def get_plex_hash(search, mediatype=None): hash_str = 'local://{}'.format(local_id) else: hash_str = searched.tracks()[0].guid.replace('/1?lang=en', '?lang=en') - #print(searched.__dict__.items()) + # print(searched.__dict__.items()) hash_to_path(hash_str, PLEX_LOCAL_ALBUM_PATH, searched.title, searched.type, searched.parentTitle) elif searched.type == 'artist': @@ -89,6 +91,7 @@ def get_plex_hash(search, mediatype=None): else: pass + if __name__ == '__main__': parser = argparse.ArgumentParser(description="Helping navigate Plex's locally stored data.") parser.add_argument('-s', '--search', required=True, help='Search Plex for title.') diff --git a/utility/find_unwatched.py b/utility/find_unwatched.py index 76d3134..9ee77bb 100644 --- a/utility/find_unwatched.py +++ b/utility/find_unwatched.py @@ -1,7 +1,8 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + """ - Find what was added TFRAME ago and not watched using Tautulli. - """ import requests @@ -9,14 +10,14 @@ import sys import time import os -TFRAME = 1.577e+7 # ~ 6 months in seconds +TFRAME = 1.577e+7 # ~ 6 months in seconds TODAY = time.time() -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'XXXXXX' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL -LIBRARY_NAMES = ['My TV Shows', 'My Movies'] # Name of libraries you want to check. +LIBRARY_NAMES = ['My TV Shows', 'My Movies'] # Name of libraries you want to check. class LIBINFO(object): @@ -81,7 +82,7 @@ def get_metadata(rating_key): res_data = response['response']['data'] return METAINFO(data=res_data) - except Exception as e: + except Exception: # sys.stderr.write("Tautulli API 'get_metadata' request failed: {0}.".format(e)) pass @@ -103,6 +104,7 @@ def get_library_media_info(section_id): except Exception as e: sys.stderr.write("Tautulli API 'get_library_media_info' request failed: {0}.".format(e)) + def get_libraries_table(): # Get the data on the Tautulli libraries table. payload = {'apikey': TAUTULLI_APIKEY, @@ -117,7 +119,8 @@ def get_libraries_table(): except Exception as e: sys.stderr.write("Tautulli API 'get_libraries_table' request failed: {0}.".format(e)) - + + def delete_files(tmp_lst): del_file = raw_input('Delete all unwatched files? (yes/no)').lower() if del_file.startswith('y'): @@ -127,6 +130,7 @@ def delete_files(tmp_lst): else: print('Ok. doing nothing.') + show_lst = [] path_lst = [] diff --git a/utility/get_serial_transcoders.py b/utility/get_serial_transcoders.py index 4a7c508..da4baba 100644 --- a/utility/get_serial_transcoders.py +++ b/utility/get_serial_transcoders.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 -""" -Description: Get a list of "Serial Transcoders" +# -*- coding: utf-8 -*- + +"""Get a list of "Serial Transcoders". + Author: DirtyCajunRice Requires: requests, plexapi, python3.6+ """ diff --git a/utility/grab_gdrive_media.py b/utility/grab_gdrive_media.py index 03d33e2..612c32c 100644 --- a/utility/grab_gdrive_media.py +++ b/utility/grab_gdrive_media.py @@ -1,8 +1,9 @@ -# -*- encoding: UTF-8 -*- +#!/usr/bin/env python +# -*- coding: utf-8 -*- -''' +""" https://gist.github.com/blacktwin/f435aa0ccd498b0840d2407d599bf31d -''' +""" import os import httplib2 @@ -12,15 +13,11 @@ from oauth2client.file import Storage from googleapiclient.discovery import build from oauth2client.client import OAuth2WebServerFlow -import time, shutil, sys - # Copy your credentials from the console # https://console.developers.google.com CLIENT_ID = '' CLIENT_SECRET = '' -OUT_PATH = '' # Output Path - - +OUT_PATH = '' # Output Path OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive' REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob' @@ -48,6 +45,7 @@ http = credentials.authorize(http) drive_service = build('drive', 'v2', http=http) + def list_files(service): page_token = None while True: @@ -90,11 +88,11 @@ for item in list_files(drive_service): if 'mimeType' in item and 'image/jpeg' in item['mimeType'] or 'video/mp4' in item['mimeType']: download_url = item['downloadUrl'] else: - print 'ERROR getting %s' % item.get('title') - print item - print dir(item) + print('ERROR getting %s' % item.get('title')) + print(item) + print(dir(item)) if download_url: - print "downloading %s" % item.get('title') + print("downloading %s" % item.get('title')) resp, content = drive_service._http.request(download_url) if resp.status == 200: if os.path.isfile(outfile): diff --git a/utility/off_deck.py b/utility/off_deck.py index 090634a..fa08715 100644 --- a/utility/off_deck.py +++ b/utility/off_deck.py @@ -1,13 +1,15 @@ #!/usr/bin/env python -""" -Description: Removes Shows from On Deck +# -*- coding: utf-8 -*- + +"""Removes Shows from On Deck. + Author: Blacktwin Requires: requests, plexapi Example: python off_deck.py --action deck --user Steve - Display what shows are on Steve's On Deck - + python off_deck.py --action deck --shows "The Simpsons" Seinfeld - The Simpsons and Seinfeld will be removed from On Deck @@ -17,10 +19,10 @@ Requires: requests, plexapi python off_deck.py --action deck --playlist "Favorite Shows!" - Any Show found in Favorite Shows playlist will be remove from On Deck - + python off_deck.py --action watch --user Steve - Display what shows are on Steve's Continue Watching - + python off_deck.py --action watch --shows "The Simpsons" Seinfeld - The Simpsons and Seinfeld will be removed from Continue Watching @@ -99,9 +101,9 @@ def get_con_watch(server, off_deck=None): if item.grandparentTitle in off_deck: print('Removing {}: S{:02}E{:02} {} from Continue Watching ' 'by marking watched.'.format( - item.grandparentTitle.encode('UTF-8'), - int(item.parentIndex), int(item.index), - item.title.encode('UTF-8'))) + item.grandparentTitle.encode('UTF-8'), + int(item.parentIndex), int(item.index), + item.title.encode('UTF-8'))) item.markWatched() else: if item.type == 'episode' and item.viewOffset > 0: @@ -115,7 +117,7 @@ def get_con_watch(server, off_deck=None): item.grandparentTitle.encode('UTF-8'), int(item.parentIndex), int(item.index), item.title.encode('UTF-8'), offset)) - + def get_on_deck(server, off_deck=None): """ @@ -142,12 +144,13 @@ def get_on_deck(server, off_deck=None): watched_statuses['grandparent'] = grandparent watched_statuses['episodes'] = [] for episode in grandparent.episodes(): - watched_statuses['episodes'].append({'object': episode, - 'viewCount': episode.viewCount}) + watched_statuses['episodes'].append({ + 'object': episode, + 'viewCount': episode.viewCount}) else: if item.type == 'episode': on_deck.append(item) - + if on_deck: watched_statuses['on_deck'] = on_deck return watched_statuses @@ -204,7 +207,7 @@ if __name__ == '__main__': off_deck['grandparent'].markUnwatched() else: break - + print('Resetting watch counts...') for item in ep_list: print('Resetting view count for {}: S{:02}E{:02} {}'.format( @@ -214,7 +217,7 @@ if __name__ == '__main__': # if viewCount was 0 then make 1 so as not to return to On Deck. for _ in range(item['viewCount'] if item['viewCount'] != 0 else 1): item['object'].markWatched() - + elif opts.action == 'watch': print('Finding shows marked Continue Watching...') - get_con_watch(plex_server, to_remove) \ No newline at end of file + get_con_watch(plex_server, to_remove) diff --git a/utility/plex_api_invite.py b/utility/plex_api_invite.py index c9d8864..c30d8db 100644 --- a/utility/plex_api_invite.py +++ b/utility/plex_api_invite.py @@ -1,5 +1,7 @@ #!/usr/bin/env python -''' +# -*- coding: utf-8 -*- + +""" Invite new users to share Plex libraries. optional arguments: @@ -33,7 +35,7 @@ Usage: plex_api_invite.py --libraries Movies --user USER --movieRatings G, PG-13 - Share Movie library with USER but restrict them to only G and PG-13 titles. -''' +""" from plexapi.server import PlexServer, CONFIG import argparse @@ -71,11 +73,11 @@ def invite(user, sections, allowSync, camera, channels, filterMovies, filterTele allowCameraUpload=camera, allowChannels=channels, filterMovies=filterMovies, filterTelevision=filterTelevision, filterMusic=filterMusic) print('Invited {user} to share libraries: \n{sections}'.format(sections=sections, user=user)) - if allowSync == True: + if allowSync is True: print('Sync: Enabled') - if camera == True: + if camera is True: print('Camera Upload: Enabled') - if channels == True: + if channels is True: print('Plugins: Enabled') if filterMovies: print('Movie Filters: {}'.format(filterMovies)) @@ -161,4 +163,4 @@ if __name__ == "__main__": for user in opts.user: invite(user, libraries, sync, camera, channels, - filterMovies, filterTelevision, filterMusic) \ No newline at end of file + filterMovies, filterTelevision, filterMusic) diff --git a/utility/plex_api_parental_control.py b/utility/plex_api_parental_control.py index 0edaf7d..b460510 100644 --- a/utility/plex_api_parental_control.py +++ b/utility/plex_api_parental_control.py @@ -1,5 +1,7 @@ #!/usr/bin/env python -''' +# -*- coding: utf-8 -*- + +""" Set as cron or task for times of allowing and not allowing user access to server. Unsharing will kill any current stream from user before unsharing. @@ -33,7 +35,7 @@ Usage: - Unshared all libraries with USER. - USER is still exists as a Friend or Home User -''' +""" import argparse diff --git a/utility/plex_api_poster_pull.py b/utility/plex_api_poster_pull.py index ad33004..82bef61 100644 --- a/utility/plex_api_poster_pull.py +++ b/utility/plex_api_poster_pull.py @@ -1,6 +1,11 @@ #!/usr/bin/env python -""" -Description: Pull Movie and TV Show poster images from Plex. Save to Movie and TV Show directories in scripts working directory. +# -*- coding: utf-8 -*- + +"""Pull Movie and TV Show poster images from Plex. + +Saves the poster images to Movie and TV Show directories in scripts working +directory. + Author: Blacktwin Requires: plexapi @@ -15,7 +20,7 @@ import re import os import urllib -library_name = ['Movies','TV Shows'] # You library names +library_name = ['Movies', 'TV Shows'] # Your library names PLEX_URL = '' PLEX_TOKEN = '' @@ -50,7 +55,7 @@ if not os.path.isdir(show_path): for library in library_name: for child in plex.library.section(library).all(): # Clean names of special characters - name = re.sub('\W+',' ', child.title) + name = re.sub('\W+', ' ', child.title) # Add (year) to name name = '{} ({})'.format(name, child.year) # Pull URL for poster diff --git a/utility/plex_api_share.py b/utility/plex_api_share.py index ff3a0b2..933c54e 100644 --- a/utility/plex_api_share.py +++ b/utility/plex_api_share.py @@ -1,6 +1,7 @@ #!/usr/bin/env python -''' -Share or unshare libraries. +# -*- coding: utf-8 -*- + +"""Share or unshare libraries. optional arguments: -h, --help Show this help message and exit @@ -65,7 +66,7 @@ Usage: plex_api_share.py --backup - Backup all user shares to a json file - + plex_api_share.py --backup --user USER - Backup USER shares to a json file @@ -96,7 +97,7 @@ Usage: plex_api_share.py --share -u USER --allLibraries --libraries Movies - Shared [all libraries but Movies] with USER. -''' +""" from plexapi.server import PlexServer, CONFIG import time @@ -137,13 +138,14 @@ movies_keys = [x.key for x in plex.library.sections() if x.type == 'movie'] show_keys = [x.key for x in plex.library.sections() if x.type == 'show'] json_check = sorted([f for f in os.listdir('.') if os.path.isfile(f) and - f.endswith(".json") and f.startswith(plex.friendlyName)], + f.endswith(".json") and + f.startswith(plex.friendlyName)], key=os.path.getmtime) my_server_names = [] # Find all owners server names. For owners with multiple servers. for res in plex.myPlexAccount().resources(): - if res.provides == 'server' and res.owned == True: + if res.provides == 'server' and res.owned is True: my_server_names.append(res.name) @@ -151,7 +153,7 @@ def get_ratings_lst(section_id): headers = {'Accept': 'application/json'} params = {'X-Plex-Token': PLEX_TOKEN} content = sess.get("{}/library/sections/{}/contentRating".format(PLEX_URL, section_id), - headers=headers, params=params) + headers=headers, params=params) ratings_keys = content.json()['MediaContainer']['Directory'] ratings_lst = [x['title'] for x in ratings_keys] @@ -166,7 +168,7 @@ def filter_clean(filter_type): labels = v.replace('%20', ' ') labels = labels.split('%2C') clean[k] = labels - except Exception as e: + except Exception: pass return clean @@ -193,7 +195,7 @@ def find_shares(user): if server.name == plex.friendlyName: sections = [] for section in server.sections(): - if section.shared == True: + if section.shared is True: sections.append(section.title) user_backup['sections'] = sections @@ -219,17 +221,17 @@ def share(user, sections, allowSync, camera, channels, filterMovies, filterTelev filterTelevision=filterTelevision, filterMusic=filterMusic) if sections: print('{user}\'s updated shared libraries: \n{sections}'.format(sections=sections, user=user)) - if allowSync == True: + if allowSync is True: print('Sync: Enabled') - if allowSync == False: + if allowSync is False: print('Sync: Disabled') - if camera == True: + if camera is True: print('Camera Upload: Enabled') - if camera == False: + if camera is False: print('Camera Upload: Disabled') - if channels == True: + if channels is True: print('Plugins: Enabled') - if channels == False: + if channels is False: print('Plugins: Disabled') if filterMovies: print('Movie Filters: {}'.format(filterMovies)) @@ -241,7 +243,7 @@ def share(user, sections, allowSync, camera, channels, filterMovies, filterTelev print('Show Filters:') if filterMusic: print('Music Filters: {}'.format(filterMusic)) - if filterMusic == {} and filterMusic != None: + if filterMusic == {} and filterMusic is not None: print('Music Filters:') @@ -286,7 +288,7 @@ if __name__ == "__main__": help='Show all shares by library.') # For Plex Pass members - if plex.myPlexSubscription == True: + if plex.myPlexSubscription is True: movie_ratings = [] show_ratings = [] for movie in movies_keys: @@ -371,7 +373,7 @@ if __name__ == "__main__": for k, v in user_lst.items(): if v == user: del user_lst[k] - + users = user_lst.keys() # Defining libraries @@ -384,7 +386,7 @@ if __name__ == "__main__": for library in opts.libraries: sections_lst.remove(library) libraries = sections_lst - + if opts.libraryShares: users = user_lst.keys() user_sections = {} @@ -396,11 +398,10 @@ if __name__ == "__main__": for user, sections in user_sections.items(): for section in sections: section_users.setdefault(section, []).append(user) - + for section, users in section_users.items(): - print("{} is shared to the following users:\n {}\n".format(section, ", ".join(users))) - - + print("{} is shared to the following users:\n {}\n".format(section, ", ".join(users))) + # Share, Unshare, Kill, Add, or Remove for user in users: user_shares = find_shares(user) @@ -485,4 +486,4 @@ if __name__ == "__main__": print('Restoring user {}\'s shares and settings...'.format(user['title'])) share(user['title'], user['sections'], user['allowSync'], user['camera'], user['channels'], user['filterMovies'], user['filterTelevision'], - user['filterMusic']) \ No newline at end of file + user['filterMusic']) diff --git a/utility/plex_api_show_settings.py b/utility/plex_api_show_settings.py index 1170a73..513398e 100644 --- a/utility/plex_api_show_settings.py +++ b/utility/plex_api_show_settings.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + """ Change show deletion settings by library. @@ -33,7 +35,7 @@ PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN) # Allowed days/episodes to keep or delete WATCHED_LST = [0, 1, 7] -UNWATCHED_LST = [0, 5, 3, 1, -3, -7,-30] +UNWATCHED_LST = [0, 5, 3, 1, -3, -7, -30] sess = requests.Session() # Ignore verifying the SSL certificate @@ -57,10 +59,10 @@ def set_show(rating_key, action, number): path = '{}/prefs'.format(rating_key) try: params = {'X-Plex-Token': PLEX_TOKEN, - action: number - } + action: number + } - r = requests.put(PLEX_URL + path, params=params, verify=False) + r = requests.put(PLEX_URL + path, params=params, verify=False) print(r.url) except Exception as e: print('Error: {}'.format(e)) diff --git a/utility/plex_imgur_dl.py b/utility/plex_imgur_dl.py index 8dc02fb..47f4a94 100644 --- a/utility/plex_imgur_dl.py +++ b/utility/plex_imgur_dl.py @@ -1,27 +1,30 @@ #!/usr/bin/env python -''' +# -*- coding: utf-8 -*- + +""" Pull poster images from Imgur and places them inside Shows root folder. /path/to/show/Show.jpg - + Skips download if showname.jpg exists or if show does not exist. -''' +""" import requests import urllib import os -## Edit ## +# ## Edit ## # Imgur info -CLIENT_ID = 'xxxxx' # Tautulli Settings > Notifications > Imgur Client ID -ALBUM_ID = '7JeSw' # http://imgur.com/a/7JeSw <--- 7JeSw is the ablum_id +CLIENT_ID = 'xxxxx' # Tautulli Settings > Notifications > Imgur Client ID +ALBUM_ID = '7JeSw' # http://imgur.com/a/7JeSw <--- 7JeSw is the ablum_id # Local info SHOW_PATH = 'D:\\Shows\\' -## /Edit ## +# ## /Edit ## + class IMGURINFO(object): def __init__(self, data=None): diff --git a/utility/plex_popular_playlist.py b/utility/plex_popular_playlist.py index 03a4b11..a2132b5 100644 --- a/utility/plex_popular_playlist.py +++ b/utility/plex_popular_playlist.py @@ -1,6 +1,7 @@ #!/usr/bin/env python -''' -Build playlist from popular tracks. +# -*- coding: utf-8 -*- + +"""Build playlist from popular tracks. optional arguments: -h, --help show this help message and exit @@ -14,7 +15,7 @@ optional arguments: * LIBRARY_EXCLUDE are excluded from libraries choice. -''' +""" import requests @@ -59,8 +60,8 @@ def fetch(path): header = {'Accept': 'application/json'} params = {'X-Plex-Token': PLEX_TOKEN, - 'includePopularLeaves': '1' - } + 'includePopularLeaves': '1' + } r = requests.get(url + path, headers=header, params=params, verify=False) return r.json()['MediaContainer']['Metadata'][0]['PopularLeaves']['Metadata'] @@ -97,7 +98,7 @@ if __name__ == "__main__": parser.add_argument('--tracks', nargs='?', default=False, type=int, metavar='', help='Specify the track length you would like the playlist.') - parser.add_argument('--random',nargs='?', default=False, type=int, metavar='', + parser.add_argument('--random', nargs='?', default=False, type=int, metavar='', help='Randomly select N artists.') opts = parser.parse_args() @@ -128,7 +129,7 @@ if __name__ == "__main__": if opts.tracks and opts.random: playlist = random.sample((playlist), opts.tracks) - + elif opts.tracks and not opts.random: playlist = playlist[:opts.tracks] diff --git a/utility/plex_theme_songs.py b/utility/plex_theme_songs.py index e45d0f4..97f17ff 100644 --- a/utility/plex_theme_songs.py +++ b/utility/plex_theme_songs.py @@ -1,9 +1,12 @@ #!/usr/bin/env python -''' -Download theme songs from Plex TV Shows. Theme songs are mp3 and named by shows as displayed by Plex. +# -*- coding: utf-8 -*- + +"""Download theme songs from Plex TV Shows. + +Theme songs are mp3 and named by shows as displayed by Plex. Songs are saved in a 'Theme Songs' directory located in script's path. -''' +""" from plexapi.server import PlexServer, CONFIG @@ -13,14 +16,14 @@ import re import urllib import requests -## Edit ## +# ## Edit ## PLEX_URL = '' PLEX_TOKEN = '' PLEX_URL = CONFIG.data['auth'].get('server_baseurl', PLEX_URL) PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN) -TV_LIBRARY = 'TV Shows' # Name of your TV Show library -## /Edit ## +TV_LIBRARY = 'TV Shows' # Name of your TV Show library +# ## /Edit ## sess = requests.Session() # Ignore verifying the SSL certificate @@ -47,7 +50,7 @@ if not os.path.isdir(out_path): # Get episodes from TV Shows for show in plex.library.section(TV_LIBRARY).all(): # Remove special characters from name - filename = '{}.mp3'.format(re.sub('\W+',' ', show.title)) + filename = '{}.mp3'.format(re.sub('\W+', ' ', show.title)) # Set output path theme_path = os.path.join(out_path, filename) # Get tvdb_if from first episode, no need to go through all episodes diff --git a/utility/plexapi_delete_playlists.py b/utility/plexapi_delete_playlists.py index 8cc5da8..3385fa9 100644 --- a/utility/plexapi_delete_playlists.py +++ b/utility/plexapi_delete_playlists.py @@ -1,5 +1,7 @@ -""" -Delete all playlists from Plex using PlexAPI +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Delete all playlists from Plex. https://github.com/mjs7231/python-plexapi """ @@ -21,8 +23,8 @@ for playlist in plex.playlists(): for i in tmp_lst: try: - r = requests.delete('{}/playlists/{}?X-Plex-Token={}'.format(baseurl,i,token)) + r = requests.delete('{}/playlists/{}?X-Plex-Token={}'.format(baseurl, i, token)) print(r) except Exception as e: - print e + print(e) diff --git a/utility/purge_removed_plex_friends.py b/utility/purge_removed_plex_friends.py index 1bfdc78..23ab4e5 100644 --- a/utility/purge_removed_plex_friends.py +++ b/utility/purge_removed_plex_friends.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 -""" -Description: Purge Tautulli users that no longer exist as a friend in Plex +# -*- coding: utf-8 -*- + +"""Purge Tautulli users that no longer exist as a friend in Plex. + Author: DirtyCajunRice Requires: requests, plexapi, python3.6+ """ diff --git a/utility/refresh_next_episode.py b/utility/refresh_next_episode.py index 582f5c9..db169e8 100644 --- a/utility/refresh_next_episode.py +++ b/utility/refresh_next_episode.py @@ -1,6 +1,8 @@ #!/usr/bin/env python -''' -Refresh the next episode of show once current episode is watched. +# -*- coding: utf-8 -*- + +"""Refresh the next episode of show once current episode is watched. + Check Tautulli's Watched Percent in Tautulli > Settings > General 1. Tautulli > Settings > Notification Agents > Scripts > Bell icon: @@ -12,7 +14,7 @@ Check Tautulli's Watched Percent in Tautulli > Settings > General 3. Tautulli > Settings > Notifications > Script > Script Arguments: {show_name} {episode_num00} {season_num00} -''' +""" import requests import sys @@ -42,7 +44,7 @@ plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess) show_name = sys.argv[1] next_ep_num = int(sys.argv[2]) season_num = int(sys.argv[3]) -TV_LIBRARY = 'My TV Shows' # Name of your TV Shows library +TV_LIBRARY = 'My TV Shows' # Name of your TV Shows library current_season = season_num - 1 diff --git a/utility/remove_inactive_users.py b/utility/remove_inactive_users.py index b1397a4..1b2672a 100644 --- a/utility/remove_inactive_users.py +++ b/utility/remove_inactive_users.py @@ -1,6 +1,7 @@ #!/usr/bin/env python -""" -Unshare or Remove users who have been inactive for X days. Prints out last seen for all users. +# -*- coding: utf-8 -*- + +"""Unshare or Remove users who have been inactive for X days. Prints out last seen for all users. Just run. @@ -114,6 +115,3 @@ for user in TAUTULLI_USERS: else: print('{}, and has reached their inactivity limit. Unsharing.'.format(OUTPUT)) ACCOUNT.updateFriend(PLEX_USERS[UID], SERVER, removeSections=True) - - - diff --git a/utility/remove_watched_movies.py b/utility/remove_watched_movies.py index 1fc43cb..fd99190 100644 --- a/utility/remove_watched_movies.py +++ b/utility/remove_watched_movies.py @@ -1,6 +1,7 @@ -""" -Find Movies that have been watched by a list of users. -If all users have watched movie than delete. +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Find and delete Movies that have been watched by a list of users. Deletion is prompted """ @@ -11,11 +12,12 @@ import os import shutil -## EDIT THESE SETTINGS ## +# ## EDIT THESE SETTINGS ## TAUTULLI_APIKEY = 'xxxxxxxx' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL -LIBRARY_NAMES = ['My Movies'] # Whatever your movie libraries are called. -USER_LST = ['Joe', 'Alex'] # Name of users +LIBRARY_NAMES = ['My Movies'] # Whatever your movie libraries are called. +USER_LST = ['Joe', 'Alex'] # Name of users + class UserHIS(object): def __init__(self, data=None): @@ -81,6 +83,7 @@ def delete_files(tmp_lst): else: print('Ok. doing nothing.') + movie_dict = {} movie_lst = [] delete_lst = [] @@ -119,7 +122,7 @@ for user in USER_LST: for movie_dict in movie_lst: if set(USER_LST) == set(movie_dict['watched_by']): - print(u"{} has been watched by {}".format(movie_dict['title']," & ".join(USER_LST))) + print(u"{} has been watched by {}".format(movie_dict['title'], " & ".join(USER_LST))) delete_lst.append(movie_dict['file']) delete_files(delete_lst) diff --git a/utility/stream_limiter_ban_email.py b/utility/stream_limiter_ban_email.py index e02cc6c..1eccc2d 100644 --- a/utility/stream_limiter_ban_email.py +++ b/utility/stream_limiter_ban_email.py @@ -1,6 +1,7 @@ #!/usr/bin/env python -""" -Share functions from https://gist.github.com/JonnyWong16/f8139216e2748cb367558070c1448636 +# -*- coding: utf-8 -*- + +"""Share functions from https://gist.github.com/JonnyWong16/f8139216e2748cb367558070c1448636 Once user stream count hits LIMIT they are unshared from libraries expect for banned library. Once user stream count is below LIMIT and banned library video is watched their shares are restored. @@ -30,7 +31,6 @@ Tautulli will continue displaying that user is watching after unshare is execute Tautulli will update after ~5 minutes and no longer display user's stream in ACTIVITY. Tautulli will think that user has stopped. - Create new library with one video. Name library and video whatever you want. @@ -59,7 +59,7 @@ import email.utils import smtplib -## EDIT THESE SETTINGS ### +# ## EDIT THESE SETTINGS ### TAUTULLI_APIKEY = 'XXXXXX' # Your Tautulli API key TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL @@ -74,8 +74,8 @@ SERVER_ID = "XXXXX" # Example: https://i.imgur.com/EjaMTUk.png # UserID2: [LibraryID1, LibraryID2]} USER_LIBRARIES = {123456: [123456, 123456, 123456, 123456, 123456, 123456]} -BAN_LIBRARY = {123456: [123456]} # {UserID1: [LibraryID1], UserID2: [LibraryID1]} -BAN_RATING = 123456 # Banned rating_key to check if it's been watched. +BAN_LIBRARY = {123456: [123456]} # {UserID1: [LibraryID1], UserID2: [LibraryID1]} +BAN_RATING = 123456 # Banned rating_key to check if it's been watched. LIMIT = 3 VIOLATION_LIMIT = 3 @@ -99,15 +99,15 @@ BODY_TEXT = """\ """ # Email settings -name = '' # Your name -sender = '' # From email address -email_server = 'smtp.gmail.com' # Email server (Gmail: smtp.gmail.com) +name = '' # Your name +sender = '' # From email address +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_username = '' # Your email username +email_password = '' # Your email password -## DO NOT EDIT BELOW ## +# ## DO NOT EDIT BELOW ## class Activity(object): def __init__(self, data=None): @@ -216,7 +216,7 @@ def unshare(user_id): return elif r.status_code == 400: - print r.content + print(r.content) return elif r.status_code == 200: @@ -256,6 +256,7 @@ def get_activity(): except Exception as e: sys.stderr.write("Tautulli API 'get_activity' request failed: {0}.".format(e)) + def send_notification(to=None, friendly=None, val_cnt=None, val_tot=None, mess=None): # Format notification text try: @@ -295,9 +296,9 @@ if __name__ == "__main__": try: if act_lst.count(user) >= LIMIT: # Trigger for first and next violation - unshare(user) # Remove libraries - share(user, BAN) # Share banned library - sys.stdout.write("Shared BAN_LIBRARY with user {0}".format(i)) + unshare(user) # Remove libraries + share(user, BAN) # Share banned library + sys.stdout.write("Shared BAN_LIBRARY with user {0}".format(user)) if type(history) is int: # Next violation, history of banned video. send_notification(mail_add, friendly, history, VIOLATION_LIMIT, FIRST_WARN) @@ -308,10 +309,10 @@ if __name__ == "__main__": elif type(history) is int: # Trigger to share if share(user, UNBAN) == 400: - exit() # User has history of watching banned video but libraries are already restored. + exit() # User has history of watching banned video but libraries are already restored. else: - unshare(user) # Remove banned library - share(user, UNBAN) # Restore libraries + unshare(user) # Remove banned library + share(user, UNBAN) # Restore libraries elif history == 'ban': # Trigger for ban unshare(user) diff --git a/utility/sync_watch_status.py b/utility/sync_watch_status.py index ce4a5b2..01293af 100644 --- a/utility/sync_watch_status.py +++ b/utility/sync_watch_status.py @@ -1,6 +1,8 @@ #!/usr/bin/env python -""" -Description: Sync the watch status from one Plex or Tautulli user to other users across any owned server. +# -*- coding: utf-8 -*- + +"""Sync the watch status from one Plex or Tautulli user to other users across any owned server. + Author: Blacktwin Requires: requests, plexapi, argparse @@ -87,7 +89,7 @@ class Connection: pool_block=True) self.session.mount('http://', self.adapters) self.session.mount('https://', self.adapters) - + # Ignore verifying the SSL certificate if verify_ssl is False: self.session.verify = False @@ -115,7 +117,7 @@ class Metadata(object): self.title = ep_name.lstrip() else: self.title = d['full_title'] - + # For History try: if d['watched_status']: @@ -133,32 +135,32 @@ class Metadata(object): class Tautulli: def __init__(self, connection): self.connection = connection - + def _call_api(self, cmd, payload, method='GET'): payload['cmd'] = cmd payload['apikey'] = self.connection.apikey - + try: response = self.connection.session.request(method, self.connection.url + '/api/v2', params=payload) except RequestException as e: print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL? Error: {}".format(cmd, e)) return - + try: response_json = response.json() except ValueError: print("Failed to parse json response for Tautulli API cmd '{}'".format(cmd)) return - + if response_json['response']['result'] == 'success': return response_json['response']['data'] else: error_msg = response_json['response']['message'] print("Tautulli API cmd '{}' failed: {}".format(cmd, error_msg)) return - + def get_watched_history(self, user=None, section_id=None, rating_key=None, start=None, length=None): - """Call Tautulli's get_history api endpoint""" + """Call Tautulli's get_history api endpoint.""" payload = {"order_column": "full_title", "order_dir": "asc"} if user: @@ -171,20 +173,18 @@ class Tautulli: payload["start"] = start if length: payload["lengh"] = length - + history = self._call_api('get_history', payload) - + return [d for d in history['data'] if d['watched_status'] == 1] - + def get_metadata(self, rating_key): - """Call Tautulli's get_metadata api endpoint""" - + """Call Tautulli's get_metadata api endpoint.""" payload = {"rating_key": rating_key} return self._call_api('get_metadata', payload) - + def get_libraries(self): - """Call Tautulli's get_libraries api endpoint""" - + """Call Tautulli's get_libraries api endpoint.""" payload = {} return self._call_api('get_libraries', payload) @@ -196,37 +196,43 @@ class Plex: if token and url: session = Connection().session self.server = PlexServer(baseurl=url, token=token, session=session) - + def admin_servers(self): - """All owned servers + """Get all owned servers. + Returns ------- data: dict + """ resources = {} for resource in self.account.resources(): - if 'server' in [resource.provides] and resource.owned == True: + if 'server' in [resource.provides] and resource.owned is True: resources[resource.name] = resource - + return resources - + def all_users(self): - """All users + """Get all users. + Returns ------- data: dict + """ users = {self.account.title: self.account} for user in self.account.users(): users[user.title] = user - + return users - + def all_sections(self): - """All sections from all owned servers + """Get all sections from all owned servers. + Returns ------- data: dict + """ data = {} servers = self.admin_servers() @@ -235,21 +241,23 @@ class Plex: connect = server.connect() sections = {section.title: section for section in connect.library.sections()} data[name] = sections - + return data - + def users_access(self): - """Users access across all owned servers + """Get users access across all owned servers. + Returns ------- data: dict + """ all_users = self.all_users().values() admin_servers = self.admin_servers() all_sections = self.all_sections() - + data = {self.account.title: {"account": self.account}} - + for user in all_users: if not data.get(user.title): servers = [] @@ -257,7 +265,7 @@ class Plex: if admin_servers.get(server.name): access = {} sections = {section.title: section for section in server.sections() - if section.shared == True} + if section.shared is True} access['server'] = {server.name: admin_servers.get(server.name)} access['sections'] = sections servers += [access] @@ -278,7 +286,8 @@ class Plex: def connect_to_server(server_obj, user_account): - """Find server url and connect using user token + """Find server url and connect using user token. + Parameters ---------- server_obj: class @@ -287,10 +296,11 @@ def connect_to_server(server_obj, user_account): Returns ------- user_connection.server: class + """ server_name = server_obj.name user = user_account.title - + print('Connecting {} to {}...'.format(user, server_name)) server_connection = server_obj.connect() baseurl = server_connection._baseurl.split('.') @@ -300,14 +310,15 @@ def connect_to_server(server_obj, user_account): token = PLEX_TOKEN else: token = user_account.get_token(server_connection.machineIdentifier) - + user_connection = Plex(url=url, token=token) - + return user_connection.server def check_users_access(access, user, server_name, libraries=None): """Check user's access to server. If allowed connect. + Parameters ---------- access: dict @@ -318,6 +329,7 @@ def check_users_access(access, user, server_name, libraries=None): Returns ------- server_connection: class + """ try: _user = access.get(user) @@ -333,7 +345,7 @@ def check_users_access(access, user, server_name, libraries=None): if library_check: server_connection = connect_to_server(server_obj, _user['account']) return server_connection - + elif not library_check: print("User does not have access to this library.") # Not syncing by libraries @@ -349,7 +361,8 @@ def check_users_access(access, user, server_name, libraries=None): def sync_watch_status(watched, section, accountTo, userTo, same_server=False): - """ + """Sync watched status between two users. + Parameters ---------- watched: list @@ -362,6 +375,7 @@ def sync_watch_status(watched, section, accountTo, userTo, same_server=False): User's server class of sync to user same_server: bool Are serverFrom and serverTo the same + """ print('Marking watched...') sectionTo = accountTo.library.section(section) @@ -387,7 +401,7 @@ def sync_watch_status(watched, section, accountTo, userTo, same_server=False): fetch_check.markWatched() title = fetch_check._prettyfilename() print("Synced watched status of {} to account {}...".format(title, userTo)) - + except Exception as e: print(e) pass @@ -407,11 +421,11 @@ if __name__ == '__main__': requiredNamed.add_argument('--userTo', nargs='*', metavar='user=server', required=True, type=lambda kv: kv.split("="), help='Select user and server to sync to.') - + opts = parser.parse_args() # print(opts) tautulli_server = '' - + libraries = [] all_sections = {} watchedFrom = '' @@ -420,15 +434,15 @@ if __name__ == '__main__': start = 0 plex_admin = Plex(PLEX_TOKEN) plex_access = plex_admin.users_access() - + userFrom, serverFrom = opts.userFrom - + if serverFrom == "Tautulli": # Create a Tautulli instance tautulli_server = Tautulli(Connection(url=TAUTULLI_URL.rstrip('/'), apikey=TAUTULLI_APIKEY, verify_ssl=VERIFY_SSL)) - + if serverFrom == "Tautulli" and opts.libraries: # Pull all libraries from Tautulli _sections = {} @@ -452,19 +466,19 @@ if __name__ == '__main__': else: print("No matching library name '{}'".format(library)) exit() - + # If server is Plex and synciing libraries, check access if serverFrom != "Tautulli" and libraries: print("Checking {}'s access to {}".format(userFrom, serverFrom)) watchedFrom = check_users_access(plex_access, userFrom, serverFrom, libraries) - + if libraries: print("Finding watched items in libraries...") plexTo = [] - + for user, server_name in opts.userTo: plexTo.append([user, check_users_access(plex_access, user, server_name, libraries)]) - + for _library in libraries: watched_lst = [] print("Checking {}'s library: '{}' watch statuses...".format(userFrom, _library.title)) @@ -492,7 +506,7 @@ if __name__ == '__main__': else: for item in sectionFrom.search(unwatched=False): watched_lst.append(item) - + for user in plexTo: username, server = user if server == serverFrom: @@ -502,7 +516,7 @@ if __name__ == '__main__': elif opts.ratingKey and serverFrom == "Tautulli": plexTo = [] watched_item = [] - + if userFrom != "Tautulli": print("Request manually triggered to update watch status") tt_watched = tautulli_server.get_watched_history(user=userFrom, rating_key=opts.ratingKey) @@ -511,18 +525,18 @@ if __name__ == '__main__': else: print("Rating Key {} was not reported as watched in Tautulli for user {}".format(opts.ratingKey, userFrom)) exit() - + elif userFrom == "Tautulli": print("Request from Tautulli notification agent to update watch status") watched_item = Metadata(tautulli_server.get_metadata(opts.ratingKey)) - + for user, server_name in opts.userTo: # Check access and connect plexTo.append([user, check_users_access(plex_access, user, server_name, libraries)]) - + for user in plexTo: username, server = user sync_watch_status([watched_item], watched_item.libraryName, server, username) - + else: - print("You aren't using this script correctly... bye!") \ No newline at end of file + print("You aren't using this script correctly... bye!") diff --git a/utility/tautulli_friendly_name_to_ombi_alias_sync.py b/utility/tautulli_friendly_name_to_ombi_alias_sync.py index 7af5cd5..b5c3de7 100644 --- a/utility/tautulli_friendly_name_to_ombi_alias_sync.py +++ b/utility/tautulli_friendly_name_to_ombi_alias_sync.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 -""" -Description: Sync Tautulli friendly names with Ombi aliases (Tautulli as master) +# -*- coding: utf-8 -*- + +"""Sync Tautulli friendly names with Ombi aliases (Tautulli as master). + Author: DirtyCajunRice Requires: requests, python3.6+ """