def set_identifier(cls, data, guid, strict=True): if not guid: return None if type(guid) is str: # Parse raw guid guid = Guid.parse(guid) if 'ids' not in data: data['ids'] = {} ids = data['ids'] if guid.agent == 'imdb': ids['imdb'] = guid.sid elif guid.agent == 'tmdb': ids['tmdb'] = try_convert(guid.sid, int) elif guid.agent == 'tvdb': ids['tvdb'] = try_convert(guid.sid, int) elif not strict: log.info('Unknown Guid agent: "%s"', guid.agent) else: log.info('Unknown Guid agent: "%s" [strict]', guid.agent) return None return data
def from_guid(cls, guid): if not guid: return None uri = urlparse(guid) agent = uri.scheme result = PlexParsedGuid(agent, uri.netloc, uri.query) # Nothing more to parse, return now if not uri.path: return result # Parse path component for agent-specific data path_fragments = uri.path.strip('/').split('/') if agent in SHOW_AGENTS: if len(path_fragments) >= 1: result.season = try_convert(path_fragments[0], int) if len(path_fragments) >= 2: result.episode = try_convert(path_fragments[1], int) else: log.warn('Unable to completely parse guid "%s"', guid) return result
def ListMessages(days=14, version='latest', viewed=False, *args, **kwargs): # Cast `viewed` to boolean if type(viewed) is str: if viewed == 'None': viewed = None else: viewed = viewed == 'True' # Retrieve messages messages = list( List(days=try_convert(days, int), version=version, viewed=viewed).order_by(Message.last_logged_at.desc()).limit(50)) total_messages = List( days=try_convert(days, int), version=version, ).count() # Construct container oc = ObjectContainer(title2=_("Messages")) if viewed is False and len(messages) > 1: oc.add( DirectoryObject(key=Callback(DismissMessages), title=pad_title(_("Dismiss all")))) for m in messages: if m.type is None or\ m.summary is None: continue thumb = None if m.type == Message.Type.Exception: thumb = R("icon-exception-viewed.png") if m.viewed else R( "icon-exception.png") elif m.type == Message.Type.Info: thumb = R("icon-notification-viewed.png") if m.viewed else R( "icon-notification.png") elif m.type in ERROR_TYPES: thumb = R("icon-error-viewed.png") if m.viewed else R( "icon-error.png") oc.add( DirectoryObject( key=Callback(ViewMessage, error_id=m.id), title=pad_title('[%s] %s' % (Message.Type.title(m.type), m.summary)), thumb=thumb)) # Append "View All" button if len(messages) != 50 and len(messages) < total_messages: oc.add( DirectoryObject(key=Callback(ListMessages, days=None, viewed=None), title=pad_title(_("View All")))) return oc
def pull_show(watched, rated, directory, tvdb_id): # Sync watched if Prefs['sync_watched'] is True: for show in [x for x in watched if x['tvdb_id'] == tvdb_id]: Log.Debug('We have a match for %s' % show['title']) episodes = PMS.get_metadata_leaves(directory.get('ratingKey')) if not episodes: Log.Warn('Unable to fetch episodes for show with id %s' % directory.get('ratingKey')) continue for episode in episodes.xpath('//Video'): season_num = try_convert(episode.get('parentIndex'), int) episode_num = try_convert(episode.get('index'), int) # Skip episodes with missing season or episode numbers if season_num is None or episode_num is None: continue for season in matches(season_num, show['seasons'], lambda x: int(x['season'])): if episode_num in season['episodes']: Log.Debug('Marking %s episode %s with key: %s as seen.' % ( episode.get('grandparentTitle'), episode.get('title'), episode.get('ratingKey') )) if not PMS.scrobble(episode): Log.Debug('The episode %s is already marked as seen in the library.' % episode.get('title')) # Sync ratings if Prefs['sync_ratings'] is True: for show in [x for x in rated if x['show']['tvdb_id'] == tvdb_id]: show_season = try_convert(show['episode']['season'], int) show_episode = try_convert(show['episode']['number'], int) # Skip episodes with missing season or episode numbers if show_season is None or show_episode is None: continue episodes = PMS.get_metadata_leaves(directory.get('ratingKey')) if not episodes: Log.Warn('Unable to fetch episodes for show with id %s' % directory.get('ratingKey')) continue for episode in episodes.xpath('//Video'): if show_season == int(episode.get('parentIndex')) and show_episode == int(episode.get('index')): PMS.rate(episode, show['rating_advanced'])
def fill(obj, container, video, parsed_guid=None): obj.type = video.get('type') obj.title = video.get('title') obj.year = try_convert(video.get('year'), int) obj.user_rating = try_convert(video.get('userRating'), float) if obj.user_rating: obj.user_rating = int(round(obj.user_rating, 0)) obj.section_key = try_convert(container.get('librarySectionID'), int) obj.section_title = container.get('librarySectionTitle') if parsed_guid is not None: obj.agent = parsed_guid.agent obj.sid = parsed_guid.sid
def set(cls, key, value, value_type=None): result = cls.request(':/prefs?%s=%s' % (key, try_convert(value, value_type)), 'text', method='PUT') if result is None: return False return True
def scrobble_percentage(self, value): value = try_convert(value, int) if value is None: return None if 0 <= value <= 100: return value return None
def get_identifier(cls, video): # Get plex identifier p_season = try_convert(video.get('parentIndex'), int) p_episode = try_convert(video.get('index'), int) # Ensure plex data is valid if p_season is None or p_episode is None: log.debug('Ignoring item with key "%s", invalid season or episode attribute', video.get('ratingKey')) return None, [] # Find new episodes from identifiers c_episodes = [p_episode] # Add extended episodes c_episodes.extend(cls.get_extended(video, p_season, p_episode)) # Remove any episode identifiers that are more than 1 away c_episodes = cls.remove_distant(c_episodes, p_episode) return p_season, c_episodes
def update(self, info): # Ignore if scrobbling is disabled if not get_pref('scrobble'): return session_key = try_convert(info.get('sessionKey'), int) state = info.get('state') view_offset = info.get('viewOffset') ws = self.get_session(session_key, state, view_offset) if not ws: log.trace('Invalid or ignored session, nothing to do') return # Ignore sessions flagged as 'skip' if ws.skip: return # Validate session (check filters) if not self.valid(ws): return # Check if we are scrobbling a known media type if not ws.type: log.info('Playing unknown item, will not be scrobbled: "%s"' % ws.title) ws.skip = True return # Check if the view_offset has jumped (#131) if self.offset_jumped(ws, view_offset): log.info('View offset jump detected, ignoring the state update') ws.save() return ws.last_view_offset = view_offset # Calculate progress if not self.update_progress(ws, view_offset): log.warn('Error while updating session progress, queued session to be updated') ws.update_required = True ws.save() return action = self.get_action(ws, state) if action: self.handle_action(ws, action) else: log.debug(self.status_message(ws, state)('Nothing to do this time for %s')) ws.save() self.handle_state(ws, state)
def match_identifier(cls, p_season, p_episode, c_identifier): # Season number retrieval/validation (only except exact matches to p_season) if 'season' not in c_identifier: return c_season = try_convert(c_identifier['season'], int) if c_season != p_season: return # Episode number retrieval/validation c_episodes = None # Single or repeat-style episodes if 'episode' in c_identifier: episodes = c_identifier['episode'] if not isinstance(episodes, (list, set)): episodes = [episodes] c_episodes = [try_convert(x, int) for x in episodes] # Extend-style episodes if 'episode_from' in c_identifier and 'episode_to' in c_identifier: c_from = try_convert(c_identifier['episode_from'], int) c_to = try_convert(c_identifier['episode_to'], int) if c_from is None or c_to is None: return episodes = range(c_from, c_to + 1) # Ensure plex episode is inside identifier episode range if p_episode in episodes: c_episodes = episodes else: return return c_episodes
def process_playing(item): session_key = item.get('sessionKey') state = item.get('state') view_offset = try_convert(item.get('viewOffset'), int) valid = all([x is not None for x in [session_key, state, view_offset]]) if valid: EventManager.fire('notifications.playing', str(session_key), str(state), view_offset) return True log.warn("'playing' notification doesn't look valid, ignoring: %s" % item) return False
def process_playing(item): session_key = item.get('sessionKey') state = item.get('state') view_offset = try_convert(item.get('viewOffset'), int) valid = all([ x is not None for x in [session_key, state, view_offset] ]) if valid: EventManager.fire('notifications.playing', str(session_key), str(state), view_offset) return True log.warn("'playing' notification doesn't look valid, ignoring: %s" % item) return False
def from_event(cls, info): account_key = try_convert(info.get('account_key'), int) rating_key = info.get('rating_key') if account_key is None or rating_key is None: log.warn('Invalid action format: %s', info) return None if account_key != 1: log.debug('Ignoring action from shared account') return None if WatchSession.is_active(rating_key, lambda ws: not ws.update_required): log.debug('Ignoring action, item is currently being watched') return False metadata = Metadata.get(rating_key) if not metadata: log.debug('Ignoring action, unable to retrieve metadata') return False section = metadata.section.title.lower() f_allow, _ = get_filter('filter_sections') if f_allow is not None and section not in f_allow: log.debug('Ignoring action, section has been filtered') return False guid = Guid.parse(metadata.guid) request = {} if type(metadata) is Movie: request = cls.from_movie(metadata, guid) elif type(metadata) is Season: request = cls.from_season(metadata, guid) elif type(metadata) is Episode: request = cls.from_episode(metadata, guid) else: log.warn('Unsupported metadata type: %r', metadata) return None log.debug('request: %r', request) return request
def process(self, opcode, data): if opcode not in self.opcode_data: return info = JSON.ObjectFromString(data) item = info['_children'][0] if info['type'] == "playing" and Dict["scrobble"]: session_key = str(item['sessionKey']) state = str(item['state']) view_offset = try_convert(item['viewOffset'], int) self.scrobbler.update(session_key, state, view_offset) if info['type'] == "timeline" and Dict['new_sync_collection']: if item['type'] not in [1, 4]: return if item['state'] == 0: Log.Info("New File added to Libray: " + item['title'] + ' - ' + str(item['itemID'])) self.update_collection(item['itemID'], 'add')
def fill(obj, container, video, parsed_guid=None): PlexMedia.fill(obj, container, video, parsed_guid) obj.view_count = try_convert(video.get('viewCount'), int) obj.duration = try_convert(video.get('duration'), int, 0) / float(1000 * 60) # Convert to minutes
def ListMessages(days=14, version='latest', viewed=False, *args, **kwargs): # Cast `viewed` to boolean if type(viewed) is str: if viewed == 'None': viewed = None else: viewed = viewed == 'True' # Retrieve messages messages = list(List( days=try_convert(days, int), version=version, viewed=viewed ).order_by( Message.last_logged_at.desc() ).limit(50)) total_messages = List( days=try_convert(days, int), version=version, ).count() # Construct container oc = ObjectContainer( title2=_("Messages") ) # Add "Dismiss All" button if viewed is False and len(messages) > 1: oc.add(DirectoryObject( key=Callback(DismissMessages), title=pad_title(_("Dismiss all")) )) # Add interface messages for record in InterfaceMessages.records: # Pick object thumb if record.level >= logging.WARNING: thumb = R("icon-error.png") else: thumb = R("icon-notification.png") # Add object oc.add(DirectoryObject( key=PLUGIN_PREFIX + '/messages/list', title=pad_title('[%s] %s' % (logging.getLevelName(record.level).capitalize(), record.message)), thumb=thumb )) # Add stored messages for m in messages: if m.type is None or\ m.summary is None: continue # Pick thumb if m.type == Message.Type.Exception: thumb = R("icon-exception-viewed.png") if m.viewed else R("icon-exception.png") elif m.type == Message.Type.Info: thumb = R("icon-notification-viewed.png") if m.viewed else R("icon-notification.png") elif m.type in CONNECTION_TYPES: thumb = R("icon-connection-viewed.png") if m.viewed else R("icon-connection.png") else: thumb = R("icon-error-viewed.png") if m.viewed else R("icon-error.png") # Add object oc.add(DirectoryObject( key=Callback(ViewMessage, error_id=m.id), title=pad_title('[%s] %s' % (Message.Type.title(m.type), m.summary)), thumb=thumb )) # Append "View All" button if len(messages) != 50 and len(messages) < total_messages: oc.add(DirectoryObject( key=Callback(ListMessages, days=None, viewed=None), title=pad_title(_("View All")) )) return oc
def ListMessages(days=14, version='latest', viewed=False, *args, **kwargs): # Cast `viewed` to boolean if type(viewed) is str: if viewed == 'None': viewed = None else: viewed = viewed == 'True' # Retrieve messages messages = list( List(days=try_convert(days, int), version=version, viewed=viewed).order_by(Message.last_logged_at.desc()).limit(50)) total_messages = List( days=try_convert(days, int), version=version, ).count() # Construct container oc = ObjectContainer(title2=_("Messages")) # Add "Dismiss All" button if viewed is False and len(messages) > 1: oc.add( DirectoryObject(key=Callback(DismissMessages), title=pad_title(_("Dismiss all")))) # Add interface messages for record in InterfaceMessages.records: # Pick object thumb if record.level >= logging.WARNING: thumb = R("icon-error.png") else: thumb = R("icon-notification.png") # Add object oc.add( DirectoryObject(key=PLUGIN_PREFIX + '/messages/list', title=pad_title('[%s] %s' % (logging.getLevelName( record.level).capitalize(), record.message)), thumb=thumb)) # Add stored messages for m in messages: if m.type is None or\ m.summary is None: continue # Pick thumb if m.type == Message.Type.Exception: thumb = R("icon-exception-viewed.png") if m.viewed else R( "icon-exception.png") elif m.type == Message.Type.Info: thumb = R("icon-notification-viewed.png") if m.viewed else R( "icon-notification.png") elif m.type in CONNECTION_TYPES: thumb = R("icon-connection-viewed.png") if m.viewed else R( "icon-connection.png") else: thumb = R("icon-error-viewed.png") if m.viewed else R( "icon-error.png") # Add object oc.add( DirectoryObject( key=Callback(ViewMessage, error_id=m.id), title=pad_title('[%s] %s' % (Message.Type.title(m.type), m.summary)), thumb=thumb)) # Append "View All" button if len(messages) != 50 and len(messages) < total_messages: oc.add( DirectoryObject(key=Callback(ListMessages, days=None, viewed=None), title=pad_title(_("View All")))) return oc