Repository: Flexget/Flexget

Issue:
npo_watchlist grid-favourite can be missing, spawns an error
If using npo.nl with e.g. `max_episode_age_days` set it can easily occur that no recent favourites are shown in the mijn.npo.nl list.

This spawns an error, because on line 241 (but also 245 and 249) silently assume that the div with `grid-favourite-episodes-today` will exist, even though it does not always do. We need to do more checking if `episodes` is not `None` before staring the parsing (are make the parsing safe for None-types)
  
#### Log:
```
2017-08-13 20:48 CRITICAL task          Series-NPO      BUG: Unhandled error in plugin npo_watchlist: 'NoneType' object has no attribute 'findAll'
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/flexget/task.py", line 486, in __run_plugin
    return method(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/event.py", line 23, in __call__
    return self.func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/input/npo_watchlist.py", line 262, in on_task_input
    entries += self._get_favorites_entries(task, config, page)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/input/npo_watchlist.py", line 225, in _get_favorites_entries
    return self._get_recent_entries(task, config, page)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/input/npo_watchlist.py", line 242, in _get_recent_entries
    entries = self._parse_tiles(task, config, episodes)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/input/npo_watchlist.py", line 168, in _parse_tiles
    for list_item in tiles.findAll('div', class_='npo-asset-tile-container'):
AttributeError: 'NoneType' object has no attribute 'findAll'
```



Discussion:
@jeroenl would you have time to take a look at this?

Resolving patch:
diff --git a/flexget/plugins/input/npo_watchlist.py b/flexget/plugins/input/npo_watchlist.py
--- a/flexget/plugins/input/npo_watchlist.py
+++ b/flexget/plugins/input/npo_watchlist.py
@@ -165,55 +165,56 @@ def _parse_tiles(self, task, config, tiles, series_info=None):
         max_age = config.get('max_episode_age_days')
         entries = list()
 
-        for list_item in tiles.findAll('div', class_='npo-asset-tile-container'):
-            url = list_item.find('a')['href']
-            episode_name = next(list_item.find('h2').stripped_strings)
-            timer = next(list_item.find('div', class_='npo-asset-tile-timer').stripped_strings)
-            remove_url = list_item.find('div', class_='npo-asset-tile-delete')
-
-            episode_id = url.split('/')[-1]
-            title = '{} ({})'.format(episode_name, episode_id)
-
-            not_available = list_item.find('div', class_='npo-asset-tile-availability')['data-to']
-            if not_available:
-                log.debug('Skipping %s, no longer available', title)
-                continue
-
-            entry_date = url.split('/')[4]
-            entry_date = self._parse_date(entry_date)
-
-            if max_age >= 0 and (date.today() - entry_date) > timedelta(days=max_age):
-                log.debug('Skipping %s, aired on %s', title, entry_date)
-                continue
-
-            if not series_info:
-                tile_series_info = self._get_series_info(task, config, episode_name, url)
-            else:
-                tile_series_info = series_info
-
-            series_name = tile_series_info['npo_name']
-
-            e = Entry()
-            e['url'] = url
-            e['title'] = title
-            e['series_name'] = series_name
-            e['series_name_plain'] = self._convert_plain(series_name)
-            e['series_date'] = entry_date
-            e['series_id_type'] = 'date'
-            e['npo_url'] = tile_series_info['npo_url']
-            e['npo_name'] = tile_series_info['npo_name']
-            e['npo_description'] = tile_series_info['npo_description']
-            e['npo_language'] = tile_series_info['npo_language']
-            e['npo_runtime'] = timer.strip('min').strip()
-            e['language'] = tile_series_info['npo_language']
-
-            if remove_url and remove_url['data-link']:
-                e['remove_url'] = remove_url['data-link']
-
-                if config.get('remove_accepted'):
-                    e.on_complete(self.entry_complete, task=task)
-
-            entries.append(e)
+        if tiles is not None:
+            for list_item in tiles.findAll('div', class_='npo-asset-tile-container'):
+                url = list_item.find('a')['href']
+                episode_name = next(list_item.find('h2').stripped_strings)
+                timer = next(list_item.find('div', class_='npo-asset-tile-timer').stripped_strings)
+                remove_url = list_item.find('div', class_='npo-asset-tile-delete')
+
+                episode_id = url.split('/')[-1]
+                title = '{} ({})'.format(episode_name, episode_id)
+
+                not_available = list_item.find('div', class_='npo-asset-tile-availability')['data-to']
+                if not_available:
+                    log.debug('Skipping %s, no longer available', title)
+                    continue
+
+                entry_date = url.split('/')[4]
+                entry_date = self._parse_date(entry_date)
+
+                if max_age >= 0 and (date.today() - entry_date) > timedelta(days=max_age):
+                    log.debug('Skipping %s, aired on %s', title, entry_date)
+                    continue
+
+                if not series_info:
+                    tile_series_info = self._get_series_info(task, config, episode_name, url)
+                else:
+                    tile_series_info = series_info
+
+                series_name = tile_series_info['npo_name']
+
+                e = Entry()
+                e['url'] = url
+                e['title'] = title
+                e['series_name'] = series_name
+                e['series_name_plain'] = self._convert_plain(series_name)
+                e['series_date'] = entry_date
+                e['series_id_type'] = 'date'
+                e['npo_url'] = tile_series_info['npo_url']
+                e['npo_name'] = tile_series_info['npo_name']
+                e['npo_description'] = tile_series_info['npo_description']
+                e['npo_language'] = tile_series_info['npo_language']
+                e['npo_runtime'] = timer.strip('min').strip()
+                e['language'] = tile_series_info['npo_language']
+
+                if remove_url and remove_url['data-link']:
+                    e['remove_url'] = remove_url['data-link']
+
+                    if config.get('remove_accepted'):
+                        e.on_complete(self.entry_complete, task=task)
+
+                entries.append(e)
 
         return entries
 


Test patch: