fix(kids-profiles): Reenables Kids profiles after NEtflix API changes
authorSebastian Golasch <public@asciidisco.com>
Fri, 10 Mar 2017 14:54:45 +0000 (15:54 +0100)
committerSebastian Golasch <public@asciidisco.com>
Fri, 10 Mar 2017 14:54:45 +0000 (15:54 +0100)
addon.xml
resources/language/English/strings.po
resources/language/German/strings.po
resources/lib/Navigation.py
resources/lib/NetflixHttpSubRessourceHandler.py
resources/lib/NetflixSession.py

index 7decf47ac8901daed11e0ed8d05366d0545a51d5..3bce2b8386f27f81c9c312ef3d73cc6f574bf5ea 100644 (file)
--- a/addon.xml
+++ b/addon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="plugin.video.netflix" name="Netflix" version="0.11.1" provider-name="libdev + jojo + asciidisco">
+<addon id="plugin.video.netflix" name="Netflix" version="0.11.2" provider-name="libdev + jojo + asciidisco">
   <requires>
     <import addon="xbmc.python" version="2.24.0"/>
     <import addon="script.module.beautifulsoup4" version="4.3.2"/>
   <requires>
     <import addon="xbmc.python" version="2.24.0"/>
     <import addon="script.module.beautifulsoup4" version="4.3.2"/>
index 29e881d96d469f88f0a083bcfc79f2b716d60f8f..128f5459a22de78e53b45f125e39cfd29d5608ce 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.1
+# Addon version: 0.11.2
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index d980b94722f16152207cba62790fd599c0cdfebe..1d8d3a605db1fb653e390af253759ec7c586e7da 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.1
+# Addon version: 0.11.2
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index 37fa6ec2a94282d6feb3c4d728d6fdf6477a2202..c2ee4cc05396c692c15c2275b62ba6d8deb19e7a 100644 (file)
@@ -160,7 +160,15 @@ class Navigation:
         user_list_id : :obj:`str`
             Type of list to display
         """
         user_list_id : :obj:`str`
             Type of list to display
         """
-        video_list_ids = self.call_netflix_service({'method': 'fetch_video_list_ids', 'type': type})
+        # determine if we´re in kids mode
+        user_data = self.call_netflix_service({'method': 'get_user_data'})
+        profiles = self.call_netflix_service({'method': 'list_profiles'})
+        is_kids = profiles.get(user_data['guid']).get('isKids', False)
+        # fetch video lists
+        if is_kids == True:
+            video_list_ids = self.call_netflix_service({'method': 'fetch_video_list_ids_for_kids'})
+        else:
+            video_list_ids = self.call_netflix_service({'method': 'fetch_video_list_ids', 'type': type})
         # check for any errors
         if self._is_dirty_response(response=video_list_ids):
             return False
         # check for any errors
         if self._is_dirty_response(response=video_list_ids):
             return False
@@ -257,13 +265,21 @@ class Navigation:
         if self.kodi_helper.has_cached_item(cache_id=cache_id):
             video_list_ids = self.kodi_helper.get_cached_item(cache_id=cache_id)
         else:
         if self.kodi_helper.has_cached_item(cache_id=cache_id):
             video_list_ids = self.kodi_helper.get_cached_item(cache_id=cache_id)
         else:
+            # determine if we´re in Kids profile mode
+            user_data = self.call_netflix_service({'method': 'get_user_data'})
+            profiles = self.call_netflix_service({'method': 'list_profiles'})
+            is_kids = profiles.get(user_data['guid']).get('isKids', False)
             # fetch video lists
             # fetch video lists
-            video_list_ids = self.call_netflix_service({'method': 'fetch_video_list_ids'})
+            if is_kids == True:
+                video_list_ids = self.call_netflix_service({'method': 'fetch_video_list_ids_for_kids'})
+            else:
+                video_list_ids = self.call_netflix_service({'method': 'fetch_video_list_ids'})
+
             # check for any errors
             if self._is_dirty_response(response=video_list_ids):
                 return False
             # check for any errors
             if self._is_dirty_response(response=video_list_ids):
                 return False
-            # parse the video list ids
-            self.kodi_helper.add_cached_item(cache_id=cache_id, contents=video_list_ids)
+            # cache the video list ids
+            #self.kodi_helper.add_cached_item(cache_id=cache_id, contents=video_list_ids)
         # defines an order for the user list, as Netflix changes the order at every request
         user_list_order = ['queue', 'continueWatching', 'topTen', 'netflixOriginals', 'trendingNow', 'newRelease', 'popularTitles']
         # define where to route the user
         # defines an order for the user list, as Netflix changes the order at every request
         user_list_order = ['queue', 'continueWatching', 'topTen', 'netflixOriginals', 'trendingNow', 'newRelease', 'popularTitles']
         # define where to route the user
index 1c6d29e3529082f7954d6145f91c3e4190f07807..4e7e1dd0b2659b399edb319b3fac80009d546335 100644 (file)
@@ -23,6 +23,7 @@ class NetflixHttpSubRessourceHandler:
         self.netflix_session = netflix_session
         self.credentials = self.kodi_helper.get_credentials()
         self.video_list_cache = {}
         self.netflix_session = netflix_session
         self.credentials = self.kodi_helper.get_credentials()
         self.video_list_cache = {}
+        self.lolomo = None
 
         # check if we have stored credentials, if so, do the login before the user requests it
         # if that is done, we cache the profiles
 
         # check if we have stored credentials, if so, do the login before the user requests it
         # if that is done, we cache the profiles
@@ -67,6 +68,7 @@ class NetflixHttpSubRessourceHandler:
         """
         self.profiles = []
         self.credentials = {'email': '', 'password': ''}
         """
         self.profiles = []
         self.credentials = {'email': '', 'password': ''}
+        self.lolomo = None
         return self.netflix_session.logout()
 
     def login (self, params):
         return self.netflix_session.logout()
 
     def login (self, params):
@@ -143,6 +145,24 @@ class NetflixHttpSubRessourceHandler:
             return video_list_ids_raw
         return self.netflix_session.parse_video_list_ids(response_data=video_list_ids_raw)
 
             return video_list_ids_raw
         return self.netflix_session.parse_video_list_ids(response_data=video_list_ids_raw)
 
+    def fetch_video_list_ids_for_kids (self, params):
+        """Video list ids proxy function (thanks to Netflix that we need to use a different API for Kids profiles)
+
+        Parameters
+        ----------
+        params : :obj:`dict` of :obj:`str`
+            Request params
+
+        Returns
+        -------
+        :obj:`list`
+            Transformed response of the remote call
+        """
+        if self.lolomo == None:
+            self.lolomo = self.netflix_session.get_lolomo_for_kids()
+        response = self.netflix_session.fetch_lists_for_kids(lolomo=self.lolomo)
+        return response
+
     def fetch_video_list (self, params):
         """Video list proxy function
 
     def fetch_video_list (self, params):
         """Video list proxy function
 
@@ -284,6 +304,7 @@ class NetflixHttpSubRessourceHandler:
             Response of the remote call
         """
         profile_id = params.get('profile_id', [''])[0]
             Response of the remote call
         """
         profile_id = params.get('profile_id', [''])[0]
+        self.lolomo = None
         return self.netflix_session.switch_profile(profile_id=profile_id, account=self.credentials)
 
     def get_user_data (self, params):
         return self.netflix_session.switch_profile(profile_id=profile_id, account=self.credentials)
 
     def get_user_data (self, params):
index 59cfd5e37a6035db35c5796b7cd408c980fa697b..6f28de148264c7a2fbe0107e0ecd0886c6e9b038 100644 (file)
@@ -32,7 +32,8 @@ class NetflixSession:
         'adult_pin': '/pin/service',
         'metadata': '/metadata',
         'set_video_rating': '/setVideoRating',
         'adult_pin': '/pin/service',
         'metadata': '/metadata',
         'set_video_rating': '/setVideoRating',
-        'update_my_list': '/playlistop'
+        'update_my_list': '/playlistop',
+        'kids': '/Kids'
     }
     """:obj:`dict` of :obj:`str` List of all static endpoints for HTML/JSON POST/GET requests"""
 
     }
     """:obj:`dict` of :obj:`str` List of all static endpoints for HTML/JSON POST/GET requests"""
 
@@ -449,13 +450,13 @@ class NetflixSession:
         video_lists = response_data['lists']
         for video_list_id in video_lists.keys():
             video_list = video_lists[video_list_id]
         video_lists = response_data['lists']
         for video_list_id in video_lists.keys():
             video_list = video_lists[video_list_id]
-            if video_list['context'] == 'genre':
-                video_list_ids['genres'].update(self.parse_video_list_ids_entry(id=video_list_id, entry=video_list))
-            elif video_list['context'] == 'similars' or video_list['context'] == 'becauseYouAdded':
-                video_list_ids['recommendations'].update(self.parse_video_list_ids_entry(id=video_list_id, entry=video_list))
-            else:
-                video_list_ids['user'].update(self.parse_video_list_ids_entry(id=video_list_id, entry=video_list))
-
+            if video_list.get('context', False) != False:
+                if video_list['context'] == 'genre':
+                    video_list_ids['genres'].update(self.parse_video_list_ids_entry(id=video_list_id, entry=video_list))
+                elif video_list['context'] == 'similars' or video_list['context'] == 'becauseYouAdded':
+                    video_list_ids['recommendations'].update(self.parse_video_list_ids_entry(id=video_list_id, entry=video_list))
+                else:
+                    video_list_ids['user'].update(self.parse_video_list_ids_entry(id=video_list_id, entry=video_list))
         return video_list_ids
 
     def parse_video_list_ids_entry (self, id, entry):
         return video_list_ids
 
     def parse_video_list_ids_entry (self, id, entry):
@@ -1330,6 +1331,48 @@ class NetflixSession:
         response = self._path_request(paths=paths)
         return self._process_response(response=response, component='Search results')
 
         response = self._path_request(paths=paths)
         return self._process_response(response=response, component='Search results')
 
+    def get_lolomo_for_kids (self):
+        """Fetches the lolomo ID for Kids profiles
+
+        Returns
+        -------
+        :obj:`str`
+            Kids Lolomo ID
+        """
+        response = self._session_get(component='kids')
+        for cookie in response.cookies:
+            if cookie.name.find('lhpuuidh-browse-' + self.user_data['guid']) != -1 and cookie.name.rfind('-T') == -1:
+                start = unquote(cookie.value).rfind(':')
+                return unquote(cookie.value)[start+1:]
+        return None
+
+    def fetch_lists_for_kids (self, lolomo, list_from=0, list_to=50):
+        """Fetches the JSON which contains the contents of a the video list for kids users
+
+        Parameters
+        ----------
+        lolomo : :obj:`str`
+            Lolomo ID for the Kids profile
+
+        list_from : :obj:`int`
+            Start entry for pagination
+
+        list_to : :obj:`int`
+            Last entry for pagination
+
+        Returns
+        -------
+        :obj:`dict` of :obj:`dict` of :obj:`str`
+            Raw Netflix API call response or api call error
+        """
+        paths = [
+            ['lists', lolomo, {'from': list_from, 'to': list_to}, ['displayName', 'context', 'genreId', 'id', 'index', 'length']]
+        ]
+
+        response = self._path_request(paths=paths)
+        res = self._process_response(response=response, component='Kids lists')
+        return self.parse_video_list_ids(response_data=res['value'])
+
     def fetch_video_list (self, list_id, list_from=0, list_to=20):
         """Fetches the JSON which contains the contents of a given video list
 
     def fetch_video_list (self, list_id, list_from=0, list_to=20):
         """Fetches the JSON which contains the contents of a given video list
 
@@ -2178,9 +2221,9 @@ class NetflixSession:
             'profileName',
             'isActive',
             'isFirstUse',
             'profileName',
             'isActive',
             'isFirstUse',
-            'isAccountOwner'
+            'isAccountOwner',
+            'isKids'
         ]
         ]
-
         # values are accessible via dict (sloppy parsing successfull)
         if type(netflix_page_data) == dict:
             for profile_id in netflix_page_data.get('profiles'):
         # values are accessible via dict (sloppy parsing successfull)
         if type(netflix_page_data) == dict:
             for profile_id in netflix_page_data.get('profiles'):