class Plex: def __init__(self, token, url=None): if token and not url: self.account = MyPlexAccount(token) if token and url: session = Connection().session self.server = PlexServer(baseurl=url, token=token, session=session) def all_users(self): """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 server Returns ------- sections: dict {section title: section object} """ sections = { section.title: section for section in self.server.library.sections() } return sections def all_sections_totals(self, library=None): """All sections total items Returns ------- section_totals: dict or int {section title: section object} or int """ section_totals = {} if library: sections = [self.all_sections()[library]] else: sections = self.all_sections() for section in sections: if section.type == 'movie': section_total = len(section.all()) elif section.type == 'show': section_total = len(section.search(libtype='episode')) else: continue if library: return section_total section_totals[section.title] = section_total return section_totals
def choose_managed_user(account: MyPlexAccount): users = [u.title for u in account.users()] if not users: return None click.echo(success("Managed user(s) found:")) users = sorted(users) users.insert(0, account.username) user = inquirer.select( message="Select the user you would like to use:", choices=users, default=None, style=style, qmark="", pointer=">", ).execute() if user == account.username: return None # Sanity check, even the user can't input invalid user user_account = account.user(user) if user_account: return user return None
def choose_managed_user(account: MyPlexAccount): users = [u.title for u in account.users() if u.friend] if not users: return None click.echo(success("Managed user(s) found:")) users = sorted(users) for user in users: click.echo(f"- {user}") if not click.confirm(PROMPT_MANAGED_USER): return None # choice = prompt_choice(users) user = click.prompt( title("Please select:"), type=Choice(users), show_default=True, ) # Sanity check, even the user can't input invalid user user_account = account.user(user) if user_account: return user return None
def get_friends(config): token = config['plex']['token'] account = MyPlexAccount(token) columns = ['email', 'cleaned_email'] friends = [(user.email, clean_email(user.email)) for user in account.users() if user.friend] frame = pd.DataFrame(friends, columns=columns) return frame
class Plex: def __init__(self, token, url=None): if token and not url: self.account = MyPlexAccount(token) if token and url: session = Connection().session self.server = PlexServer(baseurl=url, token=token, session=session) def admin_servers(self): """All owned servers Returns ------- data: dict """ resources = {} for resource in self.account.resources(): if 'server' in [resource.provides] and resource.owned == True: resources[resource.name] = resource return resources def all_users(self): """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 Returns ------- data: dict """ data = {} servers = self.admin_servers() print("Connecting to admin server(s) for access info...") for name, server in servers.items(): 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 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 = [] for server in user.servers: if admin_servers.get(server.name): access = {} sections = { section.title: section for section in server.sections() if section.shared == True } access['server'] = { server.name: admin_servers.get(server.name) } access['sections'] = sections servers += [access] data[user.title] = {'account': user, 'access': servers} else: # Admin account servers = [] for name, server in admin_servers.items(): access = {} sections = all_sections.get(name) access['server'] = {name: server} access['sections'] = sections servers += [access] data[user.title] = {'account': user, 'access': servers} return data
def get_env_data(): trakt.core.CONFIG_PATH = pytrakt_file plex_needed = util.input_yesno( "-- Plex --\nAre you logged into this server with a Plex account?") if plex_needed: username = input("Please enter your Plex username: "******"Please enter your Plex password: "******"Now enter the server name: ") account = MyPlexAccount(username, password) plex = account.resource( servername).connect() # returns a PlexServer instance token = plex._token users = account.users() if users: print("Managed user(s) found:") for user in users: if user.friend is True: print(user.title) print("If you want to use a managed user enter its username,") name = input( "if you want to use your main account just press enter: ") while name: try: useraccount = account.user(name) except: if name != "_wrong": print("Unknown username!") name = input( "Please enter a managed username (or just press enter to use your main account): " ) if not name: print("Ok, continuing with your account " + username) break continue try: token = account.user(name).get_token( plex.machineIdentifier) username = name break except: print("Impossible to find the managed user \'" + name + "\' on this server!") name = "_wrong" with open(env_file, 'w') as txt: txt.write("PLEX_USERNAME="******"\n") txt.write("PLEX_TOKEN=" + token + "\n") txt.write("PLEX_BASEURL=" + plex._baseurl + "\n") txt.write("PLEX_FALLBACKURL=http://localhost:32400\n") print("Plex token and baseurl for {} have been added in .env file:". format(username)) print("PLEX_TOKEN={}".format(token)) print("PLEX_BASEURL={}".format(plex._baseurl)) else: with open(env_file, "w") as txt: txt.write("PLEX_USERNAME=-\n") txt.write("PLEX_TOKEN=-\n") txt.write("PLEX_BASEURL=http://localhost:32400\n") trakt.core.AUTH_METHOD = trakt.core.DEVICE_AUTH print("-- Trakt --") client_id, client_secret = trakt.core._get_client_info() trakt.init(client_id=client_id, client_secret=client_secret, store=True) trakt_user = trakt.users.User('me') with open(env_file, "a") as txt: txt.write("TRAKT_USERNAME="******"\n") print( "You are now logged into Trakt. Your Trakt credentials have been added in .env and .pytrakt.json files." ) print("You can enjoy sync! \nCheck config.json to adjust settings.") print( "If you want to change Plex or Trakt account, just edit or remove .env and .pytrakt.json files." )
def get_env_data(): trakt.core.CONFIG_PATH = pytrakt_file print("") #plex_needed = util.input_yesno("Are you logged into hawke.one with your Plex account? (almost certainly yes)") plex_needed = 1 if plex_needed: username = input(" Please enter your Plex username: "******" Please enter your Plex password: "******"\nNext we will need your server name:\n\n The server name is displayed top left (under the library title) when viewing a library on that server:\n https://app.plex.tv/desktop \n\n For Hawke.one your server name will most likely be one of the following:" ) print(" 1) plex1.hawke.one (usually for Golden Company)") print(" 2) plex2.hawke.one (usually for Nightswatch)") print(" 3) plex3.hawke.one (usually for Kings Guard") print(" 4) plex4.hawke.one (usually for Nightswatch)") servername = input( "\n Please select you server (1-4), or enter your custom server name: " ) if servername == "1": servername = "plex1.hawke.one" if servername == "2": servername = "plex2.hawke.one" if servername == "3": servername = "plex3.hawke.one" if servername == "4": servername = "plex4.hawke.one" print(" Verifying server...") account = MyPlexAccount(username, password) plex = account.resource( servername).connect() # returns a PlexServer instance token = plex._token users = account.users() if users: print("\n The following managed Plex user(s) were found:") for user in users: if user.friend is True: print(" * " + user.title) print(" ") name = input( " To use your " + username + " account, press enter (else enter the name of a managed user): " ) while name: try: useraccount = account.user(name) except: if name != "_wrong": print("Unknown username!") name = input( "Please enter a managed username (or just press enter to use your main account): " ) if not name: print("Ok, continuing with your account " + username) break continue try: token = account.user(name).get_token( plex.machineIdentifier) username = name break except: print(" Impossible to find the managed user \'" + name + "\' on this server!") name = "_wrong" CONFIG["PLEX_USERNAME"] = username CONFIG["PLEX_TOKEN"] = token CONFIG["PLEX_BASEURL"] = plex._baseurl CONFIG["PLEX_FALLBACKURL"] = "http://localhost:32400" print(" User {} configured successfully.".format(username)) # print("PLEX_TOKEN={}".format(token)) # print("PLEX_BASEURL={}".format(plex._baseurl)) else: CONFIG["PLEX_USERNAME"] = "******" CONFIG["PLEX_TOKEN"] = "-" CONFIG["PLEX_BASEURL"] = "http://localhost:32400" trakt.core.AUTH_METHOD = trakt.core.DEVICE_AUTH print("\n\n") print("Now we'll setup the Trakt part of things:") print(" ") print( " Create the required Trakt client ID and secret by completing the following steps:" ) if platform.system() == "Windows": print( " 1 - Press enter below to open http://trakt.tv/oauth/applications" ) print(" 2 - Login to your Trakt account") print(" 3 - Press the NEW APPLICATION button") print( " 4 - Set the NAME field = hawke.one (or something else meaningful)" ) import pyperclip pyperclip.copy("urn:ietf:wg:oauth:2.0:oob") print( " 5 - Set the REDIRECT URL field = urn:ietf:wg:oauth:2.0:oob (This has been copied to your clipboard for you)" ) input( "\n Press Enter to open http://trakt.tv/oauth/applications and complete steps 1-6: " ) webbrowser.open('http://trakt.tv/oauth/applications') else: print( " 1 - Open http://trakt.tv/oauth/applications on any computer" ) print(" 2 - Login to your Trakt account") print(" 3 - Press the NEW APPLICATION button") print(" 4 - Set the NAME field = hawke.one") print( " 5 - Set the REDIRECT URL field = urn:ietf:wg:oauth:2.0:oob") print(" 6 - Press the SAVE APP button") print( "\n Once steps 1-6 are completed, please proceed to steps 7-8 below:\n" ) #client_id, client_secret = trakt.core._get_client_info() client_id = input(" 7 - Copy and paste the displayed Client ID: ") client_secret = input( " 8 - Copy and paste the displayed Client secret: ") if platform.system() == "Windows": input( "\n We will now generate a user code and open https://trakt.tv/activate for you to authenticate the app.\n Press Enter to continue..." ) webbrowser.open('https://trakt.tv/activate') print("\n") trakt.init(client_id=client_id, client_secret=client_secret, store=True) trakt_user = trakt.users.User('me') CONFIG["TRAKT_USERNAME"] = trakt_user.username print("\n\n") print("You're all done!\n") print( "Plex / Trakt accounts may be altered by re-running setup, or deleting the .env and .pytrakt.json files." ) print("Expert settings may also be altered within the config.json file.") print(" ") print(" ") CONFIG.save()
import trakt import trakt.core import trakt.users trakt.core.CONFIG_PATH = path.join(path.dirname(path.abspath(__file__)), ".pytrakt.json") env_file = path.join(path.dirname(path.abspath(__file__)), ".env") plex_needed = utils.input_yesno("-- Plex --\nAre you logged into this server with a Plex account?") if plex_needed: username = input("Please enter your Plex username: "******"Please enter your Plex password: "******"Now enter the server name: ") account = MyPlexAccount(username, password) plex = account.resource(servername).connect() # returns a PlexServer instance token = plex._token users = account.users() if users: print("Managed user(s) found:") for user in users: if user.friend is True: print(user.title) print("If you want to use a managed user enter its username,") name = input("if you want to use your main account just press enter: ") while name: try: useraccount = account.user(name) except: if name != "_wrong": print("Unknown username!") name = input("Please enter a managed username (or just press enter to use your main account): ") if not name:
# Do not edit past this line # account = MyPlexAccount(PLEX_USERNAME, PLEX_PASSWORD) session = Session() session.params = {'apikey': TAUTULLI_API_KEY} formatted_url = f'{TAUTULLI_URL}/api/v2' request = session.get(formatted_url, params={'cmd': 'get_user_names'}) tautulli_users = None try: tautulli_users = request.json()['response']['data'] except JSONDecodeError: exit("Error talking to Tautulli API, please check your TAUTULLI_URL") plex_friend_ids = [friend.id for friend in account.users()] removed_users = [ user for user in tautulli_users if user['user_id'] not in plex_friend_ids ] if BACKUP_DB: backup = session.get(formatted_url, params={'cmd': 'backup_db'}) if removed_users: for user in removed_users: removed_user = session.get(formatted_url, params={ 'cmd': 'delete_user', 'user_id': user['user_id'] }) print(f"Removed {user['friendly_name']} from Tautulli")
class PlexAttributes(): def __init__(self, opts): self.opts = opts # command line options self.clsnames = [c for c in opts.clsnames.split(',') if c] # list of clsnames to report (blank=all) self.account = MyPlexAccount() # MyPlexAccount instance self.plex = PlexServer() # PlexServer instance self.total = 0 # Total objects parsed self.attrs = defaultdict(dict) # Attrs result set def run(self): starttime = time.time() self._parse_myplex() self._parse_server() self._parse_search() self._parse_library() self._parse_audio() self._parse_photo() self._parse_movie() self._parse_show() self._parse_client() self._parse_playlist() self._parse_sync() self.runtime = round((time.time() - starttime) / 60.0, 1) return self def _parse_myplex(self): self._load_attrs(self.account, 'myplex') self._load_attrs(self.account.devices(), 'myplex') for resource in self.account.resources(): self._load_attrs(resource, 'myplex') self._load_attrs(resource.connections, 'myplex') self._load_attrs(self.account.users(), 'myplex') def _parse_server(self): self._load_attrs(self.plex, 'serv') self._load_attrs(self.plex.account(), 'serv') self._load_attrs(self.plex.history()[:50], 'hist') self._load_attrs(self.plex.history()[50:], 'hist') self._load_attrs(self.plex.sessions(), 'sess') def _parse_search(self): for search in ('cre', 'ani', 'mik', 'she', 'bea'): self._load_attrs(self.plex.search(search), 'hub') def _parse_library(self): cat = 'lib' self._load_attrs(self.plex.library, cat) # self._load_attrs(self.plex.library.all()[:50], 'all') self._load_attrs(self.plex.library.onDeck()[:50], 'deck') self._load_attrs(self.plex.library.recentlyAdded()[:50], 'add') for search in ('cat', 'dog', 'rat', 'gir', 'mou'): self._load_attrs(self.plex.library.search(search)[:50], 'srch') # TODO: Implement section search (remove library search?) # TODO: Implement section search filters def _parse_audio(self): cat = 'lib' for musicsection in self.plex.library.sections(): if musicsection.TYPE == library.MusicSection.TYPE: self._load_attrs(musicsection, cat) for artist in musicsection.all(): self._load_attrs(artist, cat) for album in artist.albums(): self._load_attrs(album, cat) for track in album.tracks(): self._load_attrs(track, cat) def _parse_photo(self): cat = 'lib' for photosection in self.plex.library.sections(): if photosection.TYPE == library.PhotoSection.TYPE: self._load_attrs(photosection, cat) for photoalbum in photosection.all(): self._load_attrs(photoalbum, cat) for photo in photoalbum.photos(): self._load_attrs(photo, cat) def _parse_movie(self): cat = 'lib' for moviesection in self.plex.library.sections(): if moviesection.TYPE == library.MovieSection.TYPE: self._load_attrs(moviesection, cat) for movie in moviesection.all(): self._load_attrs(movie, cat) def _parse_show(self): cat = 'lib' for showsection in self.plex.library.sections(): if showsection.TYPE == library.ShowSection.TYPE: self._load_attrs(showsection, cat) for show in showsection.all(): self._load_attrs(show, cat) for season in show.seasons(): self._load_attrs(season, cat) for episode in season.episodes(): self._load_attrs(episode, cat) def _parse_client(self): for device in self.account.devices(): client = self._safe_connect(device) if client is not None: self._load_attrs(client, 'myplex') for client in self.plex.clients(): self._safe_connect(client) self._load_attrs(client, 'client') def _parse_playlist(self): for playlist in self.plex.playlists(): self._load_attrs(playlist, 'pl') for item in playlist.items(): self._load_attrs(item, 'pl') playqueue = PlayQueue.create(self.plex, playlist) self._load_attrs(playqueue, 'pq') def _parse_sync(self): # TODO: Get plexattrs._parse_sync() working. pass def _load_attrs(self, obj, cat=None): if isinstance(obj, (list, tuple)): return [self._parse_objects(item, cat) for item in obj] self._parse_objects(obj, cat) def _parse_objects(self, obj, cat=None): clsname = '%s.%s' % (obj.__module__, obj.__class__.__name__) clsname = clsname.replace('plexapi.', '') if self.clsnames and clsname not in self.clsnames: return None self._print_the_little_dot() if clsname not in self.attrs: self.attrs[clsname] = copy.deepcopy(NAMESPACE) self.attrs[clsname]['total'] += 1 self._load_xml_attrs(clsname, obj._data, self.attrs[clsname]['xml'], self.attrs[clsname]['examples'], self.attrs[clsname]['categories'], cat) self._load_obj_attrs(clsname, obj, self.attrs[clsname]['obj'], self.attrs[clsname]['docs']) def _print_the_little_dot(self): self.total += 1 if not self.total % 100: sys.stdout.write('.') if not self.total % 8000: sys.stdout.write('\n') sys.stdout.flush() def _load_xml_attrs(self, clsname, elem, attrs, examples, categories, cat): if elem is None: return None for attr in sorted(elem.attrib.keys()): attrs[attr] += 1 if cat: categories[attr].add(cat) if elem.attrib[attr] and len(examples[attr]) <= self.opts.examples: examples[attr].add(elem.attrib[attr]) for subelem in elem: attrname = TAGATTRS.get(subelem.tag, '%ss' % subelem.tag.lower()) attrs['%s[]' % attrname] += 1 def _load_obj_attrs(self, clsname, obj, attrs, docs): if clsname in STOP_RECURSING_AT: return None if isinstance(obj, PlexObject) and clsname not in DONT_RELOAD: self._safe_reload(obj) alldocs = '\n\n'.join(self._all_docs(obj.__class__)) for attr, value in obj.__dict__.items(): if value is None or isinstance(value, (str, bool, float, int, datetime)): if not attr.startswith('_') and attr not in IGNORES.get( clsname, []): attrs[attr] += 1 if re.search('\s{8}%s\s\(.+?\)\:' % attr, alldocs) is not None: docs[attr] += 1 if isinstance(value, list): if not attr.startswith('_') and attr not in IGNORES.get( clsname, []): if value and isinstance(value[0], PlexObject): attrs['%s[]' % attr] += 1 [self._parse_objects(obj) for obj in value] def _all_docs(self, cls, docs=None): import inspect docs = docs or [] if cls.__doc__ is not None: docs.append(cls.__doc__) for parent in inspect.getmro(cls): if parent != cls: docs += self._all_docs(parent) return docs def print_report(self): total_attrs = 0 for clsname in sorted(self.attrs.keys()): if self._clsname_match(clsname): meta = self.attrs[clsname] count = meta['total'] print(_('\n%s (%s)\n%s' % (clsname, count, '-' * 30), 'yellow')) attrs = sorted( set(list(meta['xml'].keys()) + list(meta['obj'].keys()))) for attr in attrs: state = self._attr_state(clsname, attr, meta) count = meta['xml'].get(attr, 0) categories = ','.join(meta['categories'].get(attr, ['--'])) examples = '; '.join( list(meta['examples'].get(attr, ['--']))[:3])[:80] print('%7s %3s %-30s %-20s %s' % (count, state, attr, categories, examples)) total_attrs += count print(_('\nSUMMARY\n%s' % ('-' * 30), 'yellow')) print('%7s %3s %3s %3s %-20s %s' % ('total', 'new', 'old', 'doc', 'categories', 'clsname')) for clsname in sorted(self.attrs.keys()): if self._clsname_match(clsname): print('%7s %12s %12s %12s %s' % (self.attrs[clsname]['total'], _(self.attrs[clsname]['new'] or '', 'cyan'), _(self.attrs[clsname]['old'] or '', 'red'), _(self.attrs[clsname]['doc'] or '', 'purple'), clsname)) print('\nPlex Version %s' % self.plex.version) print('PlexAPI Version %s' % plexapi.VERSION) print('Total Objects %s' % sum([x['total'] for x in self.attrs.values()])) print('Runtime %s min\n' % self.runtime) def _clsname_match(self, clsname): if not self.clsnames: return True for cname in self.clsnames: if cname.lower() in clsname.lower(): return True return False def _attr_state(self, clsname, attr, meta): if attr in meta['xml'].keys() and attr not in meta['obj'].keys(): self.attrs[clsname]['new'] += 1 return _('new', 'blue') if attr not in meta['xml'].keys() and attr in meta['obj'].keys(): self.attrs[clsname]['old'] += 1 return _('old', 'red') if attr not in meta['docs'].keys() and attr in meta['obj'].keys(): self.attrs[clsname]['doc'] += 1 return _('doc', 'purple') return _(' ', 'green') def _safe_connect(self, elem): try: return elem.connect() except: return None def _safe_reload(self, elem): try: elem.reload() except: pass
class PlexAttributes(): def __init__(self, opts): self.opts = opts # command line options self.clsnames = [c for c in opts.clsnames.split(',') if c] # list of clsnames to report (blank=all) self.account = MyPlexAccount() # MyPlexAccount instance self.plex = PlexServer() # PlexServer instance self.total = 0 # Total objects parsed self.attrs = defaultdict(dict) # Attrs result set def run(self): starttime = time.time() self._parse_myplex() self._parse_server() self._parse_search() self._parse_library() self._parse_audio() self._parse_photo() self._parse_movie() self._parse_show() self._parse_client() self._parse_playlist() self._parse_sync() self.runtime = round((time.time() - starttime) / 60.0, 1) return self def _parse_myplex(self): self._load_attrs(self.account, 'myplex') self._load_attrs(self.account.devices(), 'myplex') for resource in self.account.resources(): self._load_attrs(resource, 'myplex') self._load_attrs(resource.connections, 'myplex') self._load_attrs(self.account.users(), 'myplex') def _parse_server(self): self._load_attrs(self.plex, 'serv') self._load_attrs(self.plex.account(), 'serv') self._load_attrs(self.plex.history()[:50], 'hist') self._load_attrs(self.plex.history()[50:], 'hist') self._load_attrs(self.plex.sessions(), 'sess') def _parse_search(self): for search in ('cre', 'ani', 'mik', 'she', 'bea'): self._load_attrs(self.plex.search(search), 'hub') def _parse_library(self): cat = 'lib' self._load_attrs(self.plex.library, cat) # self._load_attrs(self.plex.library.all()[:50], 'all') self._load_attrs(self.plex.library.onDeck()[:50], 'deck') self._load_attrs(self.plex.library.recentlyAdded()[:50], 'add') for search in ('cat', 'dog', 'rat', 'gir', 'mou'): self._load_attrs(self.plex.library.search(search)[:50], 'srch') # TODO: Implement section search (remove library search?) # TODO: Implement section search filters def _parse_audio(self): cat = 'lib' for musicsection in self.plex.library.sections(): if musicsection.TYPE == library.MusicSection.TYPE: self._load_attrs(musicsection, cat) for artist in musicsection.all(): self._load_attrs(artist, cat) for album in artist.albums(): self._load_attrs(album, cat) for track in album.tracks(): self._load_attrs(track, cat) def _parse_photo(self): cat = 'lib' for photosection in self.plex.library.sections(): if photosection.TYPE == library.PhotoSection.TYPE: self._load_attrs(photosection, cat) for photoalbum in photosection.all(): self._load_attrs(photoalbum, cat) for photo in photoalbum.photos(): self._load_attrs(photo, cat) def _parse_movie(self): cat = 'lib' for moviesection in self.plex.library.sections(): if moviesection.TYPE == library.MovieSection.TYPE: self._load_attrs(moviesection, cat) for movie in moviesection.all(): self._load_attrs(movie, cat) def _parse_show(self): cat = 'lib' for showsection in self.plex.library.sections(): if showsection.TYPE == library.ShowSection.TYPE: self._load_attrs(showsection, cat) for show in showsection.all(): self._load_attrs(show, cat) for season in show.seasons(): self._load_attrs(season, cat) for episode in season.episodes(): self._load_attrs(episode, cat) def _parse_client(self): for device in self.account.devices(): client = self._safe_connect(device) if client is not None: self._load_attrs(client, 'myplex') for client in self.plex.clients(): self._safe_connect(client) self._load_attrs(client, 'client') def _parse_playlist(self): for playlist in self.plex.playlists(): self._load_attrs(playlist, 'pl') for item in playlist.items(): self._load_attrs(item, 'pl') playqueue = PlayQueue.create(self.plex, playlist) self._load_attrs(playqueue, 'pq') def _parse_sync(self): # TODO: Get plexattrs._parse_sync() working. pass def _load_attrs(self, obj, cat=None): if isinstance(obj, (list, tuple)): return [self._parse_objects(item, cat) for item in obj] self._parse_objects(obj, cat) def _parse_objects(self, obj, cat=None): clsname = '%s.%s' % (obj.__module__, obj.__class__.__name__) clsname = clsname.replace('plexapi.', '') if self.clsnames and clsname not in self.clsnames: return None self._print_the_little_dot() if clsname not in self.attrs: self.attrs[clsname] = copy.deepcopy(NAMESPACE) self.attrs[clsname]['total'] += 1 self._load_xml_attrs(clsname, obj._data, self.attrs[clsname]['xml'], self.attrs[clsname]['examples'], self.attrs[clsname]['categories'], cat) self._load_obj_attrs(clsname, obj, self.attrs[clsname]['obj'], self.attrs[clsname]['docs']) def _print_the_little_dot(self): self.total += 1 if not self.total % 100: sys.stdout.write('.') if not self.total % 8000: sys.stdout.write('\n') sys.stdout.flush() def _load_xml_attrs(self, clsname, elem, attrs, examples, categories, cat): if elem is None: return None for attr in sorted(elem.attrib.keys()): attrs[attr] += 1 if cat: categories[attr].add(cat) if elem.attrib[attr] and len(examples[attr]) <= self.opts.examples: examples[attr].add(elem.attrib[attr]) for subelem in elem: attrname = TAGATTRS.get(subelem.tag, '%ss' % subelem.tag.lower()) attrs['%s[]' % attrname] += 1 def _load_obj_attrs(self, clsname, obj, attrs, docs): if clsname in STOP_RECURSING_AT: return None if isinstance(obj, PlexObject) and clsname not in DONT_RELOAD: self._safe_reload(obj) alldocs = '\n\n'.join(self._all_docs(obj.__class__)) for attr, value in obj.__dict__.items(): if value is None or isinstance(value, (str, bool, float, int, datetime)): if not attr.startswith('_') and attr not in IGNORES.get(clsname, []): attrs[attr] += 1 if re.search('\s{8}%s\s\(.+?\)\:' % attr, alldocs) is not None: docs[attr] += 1 if isinstance(value, list): if not attr.startswith('_') and attr not in IGNORES.get(clsname, []): if value and isinstance(value[0], PlexObject): attrs['%s[]' % attr] += 1 [self._parse_objects(obj) for obj in value] def _all_docs(self, cls, docs=None): import inspect docs = docs or [] if cls.__doc__ is not None: docs.append(cls.__doc__) for parent in inspect.getmro(cls): if parent != cls: docs += self._all_docs(parent) return docs def print_report(self): total_attrs = 0 for clsname in sorted(self.attrs.keys()): if self._clsname_match(clsname): meta = self.attrs[clsname] count = meta['total'] print(_('\n%s (%s)\n%s' % (clsname, count, '-' * 30), 'yellow')) attrs = sorted(set(list(meta['xml'].keys()) + list(meta['obj'].keys()))) for attr in attrs: state = self._attr_state(clsname, attr, meta) count = meta['xml'].get(attr, 0) categories = ','.join(meta['categories'].get(attr, ['--'])) examples = '; '.join(list(meta['examples'].get(attr, ['--']))[:3])[:80] print('%7s %3s %-30s %-20s %s' % (count, state, attr, categories, examples)) total_attrs += count print(_('\nSUMMARY\n%s' % ('-' * 30), 'yellow')) print('%7s %3s %3s %3s %-20s %s' % ('total', 'new', 'old', 'doc', 'categories', 'clsname')) for clsname in sorted(self.attrs.keys()): if self._clsname_match(clsname): print('%7s %12s %12s %12s %s' % (self.attrs[clsname]['total'], _(self.attrs[clsname]['new'] or '', 'cyan'), _(self.attrs[clsname]['old'] or '', 'red'), _(self.attrs[clsname]['doc'] or '', 'purple'), clsname)) print('\nPlex Version %s' % self.plex.version) print('PlexAPI Version %s' % plexapi.VERSION) print('Total Objects %s' % sum([x['total'] for x in self.attrs.values()])) print('Runtime %s min\n' % self.runtime) def _clsname_match(self, clsname): if not self.clsnames: return True for cname in self.clsnames: if cname.lower() in clsname.lower(): return True return False def _attr_state(self, clsname, attr, meta): if attr in meta['xml'].keys() and attr not in meta['obj'].keys(): self.attrs[clsname]['new'] += 1 return _('new', 'blue') if attr not in meta['xml'].keys() and attr in meta['obj'].keys(): self.attrs[clsname]['old'] += 1 return _('old', 'red') if attr not in meta['docs'].keys() and attr in meta['obj'].keys(): self.attrs[clsname]['doc'] += 1 return _('doc', 'purple') return _(' ', 'green') def _safe_connect(self, elem): try: return elem.connect() except: return None def _safe_reload(self, elem): try: elem.reload() except: pass
"½": "½", "⅓": "⅓" } today = datetime.date.today() notification_date = today - datetime.timedelta( days=defaults['notification_period']) notification_epoch = date_to_epoch("%s 00:00:00" % notification_date) print("Logging into Plex as %s" % defaults['plex_user']) plexAccount = MyPlexAccount(defaults['plex_user'], defaults['plex_pass']) plexServer = PlexServer(plex_local_url, plexAccount._token) recent = plexServer.library.recentlyAdded() user_emails = [plexAccount.email] users = plexAccount.users() for user in users: user_emails.append(user.email) print("Email recipients: %s" % ', '.join(user_emails)) recent_items = [] for video in recent: if video.type == "movie" and date_to_epoch( video.addedAt) > notification_epoch: recent_item = {} recent_item['title'] = "".join( html_escape_table.get(c, c) for c in video.title) # This allows movie posters to be loaded recent_item['poster_url'] = video.thumbUrl.replace( plex_local_url, defaults['base_url'])