Merge pull request #68 from Sopor-/patch-1
authorSebastian Golasch <public@asciidisco.com>
Wed, 19 Jul 2017 17:30:58 +0000 (19:30 +0200)
committerGitHub <noreply@github.com>
Wed, 19 Jul 2017 17:30:58 +0000 (19:30 +0200)
Updated Swedish translation

14 files changed:
README.md
addon.xml
resources/language/Dutch/strings.po
resources/language/English/strings.po
resources/language/German/strings.po
resources/language/Polish/strings.po
resources/language/Portugese/strings.po
resources/language/Slovak/strings.po
resources/language/Spanish/strings.po
resources/language/Swedish/strings.po
resources/lib/KodiHelper.py
resources/lib/MSL.py
resources/lib/NetflixHttpSubRessourceHandler.py
resources/lib/NetflixSession.py

index ce3656c3d1e9c126abb15aa8481f95872d58cc06..1c24ba279b7944e683748071bc04680821868ff7 100644 (file)
--- a/README.md
+++ b/README.md
@@ -10,9 +10,9 @@ The trademark "Netflix" is registered by "Netflix, Inc."
 Prerequisites
 -------------
 
-- Kodi 18 [agile build](https://github.com/FernetMenta/kodi-agile)
+- Kodi 18 [nightlybuild](http://mirrors.kodi.tv/nightlies/)
 - Libwidevine 1.4.8.962 (A german description how to get/install it, can be found [here](https://www.kodinerds.net/index.php/Thread/51486-Kodi-17-Inputstream-HowTo-AddOns-f%C3%BCr-Kodi-17-ab-Beta-6-aktuelle-Git-builds-Updat/))
-- Inputstream.adaptive [agile branch build](https://github.com/liberty-developer/inputstream.adaptive/tree/agile)
+- Inputstream.adaptive [v2.0.4](https://github.com/peak3d/inputstream.adaptive)
 
 FAQ
 ---
@@ -54,8 +54,8 @@ If something doesn't work for you, please:
 - Enable verbose logging in the plugin settings
 - Enable the Debug log in you Kodi settings
 - Open an issue with a titles that summarises your problems and include:
-       - Kodi version (git sha as long as we´re on agile only)
-       - Inputstream.adaptive version (git sha as long as we´re on the agile branch)
+       - Kodi version (git sha if possible)
+       - Inputstream.adaptive version (git sha if possible)
        - Your OS and OS version
        - Libwedevine version
        - A Kodi debug log that represents your issue
index d04de3ea155895c5a0317a6c247ebe2985f01f1e..d26c343867cbb768b3970adebc70da1e096e66a9 100644 (file)
--- a/addon.xml
+++ b/addon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="plugin.video.netflix" name="Netflix" version="0.11.11" provider-name="libdev + jojo + asciidisco">
+<addon id="plugin.video.netflix" name="Netflix" version="0.11.15" provider-name="libdev + jojo + asciidisco">
   <requires>
     <import addon="xbmc.python" version="2.24.0"/>
     <import addon="script.module.beautifulsoup4" version="4.3.2"/>
     <license>MIT</license>
     <forum>http://www.kodinerds.net/index.php/Thread/55607-Inputstream-Agile-Betatest-Netflix/</forum>
     <source>https://github.com/asciidisco/plugin.video.netflix</source>
-    <news>v0.11.11 (2017-4-10)
+    <news>v0.11.14 (2017-07-02)
+    - Fix issue with Unicode escaping
+
+    v0.11.13 (2017-07-02)
+    - Update number of requested list items (see https://github.com/asciidisco/plugin.video.netflix/issues/42 for details)
+
+    v0.11.12 (2017-07-02)
+    - Fix missing isFirstUse inline setting
+    - Fix dynamic ESN loading for widevine
+    - Fix unicode decoding problems
+
+    v0.11.11 (2017-4-10)
     - Portugese translations
     - Swedish translations
 
 
     v0.11.9 (2017-4-5)
     - Fix issues with persisted msl manifests
-
-    v0.11.8 (2017-3-17)
-    - Fix 1 sec delay per request on windows (see https://github.com/asciidisco/plugin.video.netflix/issues/21 for details)
-
-    v0.11.7 (2017-3-17)
-    - Remove initial connection check as it´s causing trouble</news>
+    </news>
   </extension>
 </addon>
index 366bd37c007b97d56edfdbf14827e67a111ce9b6..962d90739622fa5a279100cb6a149e8ba29ec4b2 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.11
+# Addon version: 0.11.14
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index 965a0b3b822798cf55eeb3e23ea97c0fe4ad74d6..6779545e64310e16c74b790d887e09db6b275e53 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.11
+# Addon version: 0.11.14
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index a20a10759b0a50672bc421716b5d2a3bd8402010..76decf5c9ffec1e281565ec6196a9821a39e19cc 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.11
+# Addon version: 0.11.14
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index 0580e1792f2dd7ebb63e0b34003bb3c0d9db90db..4ac70c8f12065a18791b643532cb3c855280700f 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.11
+# Addon version: 0.11.14
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index 63e8ac3083b3681be7a861b9f2fbcfbe46443704..0bca0c4fd93b3fbffcf99d891b7f601c2da6c54b 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.11
+# Addon version: 0.11.14
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index df75c2cead4a81fd9e280ef01195dc5f2e30a08c..5023400623ef469cc52852bd62ce6794b02071c1 100644 (file)
@@ -1,14 +1,14 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.11
+# Addon version: 0.11.14
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
 "Project-Id-Version: XBMC-Addons\n"
 "Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
 "POT-Creation-Date: 2017-01-01 12:00+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2017-05-14 19:55+0000\n"
 "Last-Translator: Matej Moško <matej.mosko@gmail.com>\n"
 "Language-Team: Slovak\n"
 "MIME-Version: 1.0\n"
@@ -19,11 +19,11 @@ msgstr ""
 
 msgctxt "Addon Summary"
 msgid "Netflix"
-msgstr "Netlflix"
+msgstr "Netflix"
 
 msgctxt "Addon Description"
 msgid "Netflix VOD Services Addon"
-msgstr "Doplnok služby Netflix"
+msgstr "Doplnok na prehrávanie obsahu služby Netflix"
 
 msgctxt "Addon Disclaimer"
 msgid "Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing."
@@ -135,7 +135,7 @@ msgstr "Chyba prehrávania"
 
 msgctxt "#30029"
 msgid "Missing Inputstream addon"
-msgstr "Nenašiel sa doplnok Inputstream"
+msgstr "Nepodarilo sa nájsť doplnok Inputstream"
 
 msgctxt "#30030"
 msgid "Remove from library"
@@ -155,7 +155,7 @@ msgstr "Použiť Dolby Sound"
 
 msgctxt "#30034"
 msgid "ESN (set automatically, can be changed manually)"
-msgstr "ESN (nastavené automaticky, môžete to ručne zmeniť"
+msgstr "ESN (nastavené automaticky, môžete to ručne zmeniť)"
 
 msgctxt "#30035"
 msgid "Inputstream Addon Settings..."
index 16071d8053c44e5448c14b39fbb7104d69704291..d019e8a9dda1f403cfcb1feccb04c6b0036a748d 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.11
+# Addon version: 0.11.14
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index 8d5d079d214ecf7a6c491cb4cfaa9785fdce7ab3..08baa4e48a3d52c66566e36e6ff4dec7b94a339f 100644 (file)
@@ -1,7 +1,7 @@
 # Kodi Media Center language file
 # Addon Name: Netflix
 # Addon id: plugin.video.netflix
-# Addon version: 0.11.11
+# Addon version: 0.11.14
 # Addon Provider: libdev + jojo + asciidisco
 msgid ""
 msgstr ""
index 100f94e79c653bc293b5bede79533ac0b7ad2bf8..a75f2e71f6ee3fb234ce7b90d4fa9743a13823c8 100644 (file)
@@ -7,7 +7,9 @@ import xbmcplugin
 import xbmcgui
 import xbmc
 import json
-from os.path import join
+from MSL import MSL
+from os import remove
+from os.path import join, isfile
 from urllib import urlencode
 from xbmcaddon import Addon
 from uuid import uuid4
@@ -190,6 +192,34 @@ class KodiHelper:
             'password': self.get_addon().getSetting('password')
         }
 
+    def get_esn(self):
+        """
+        Returns the esn from settings
+        """
+        self.log(msg='Is FILE: ' + str(isfile(self.msl_data_path + 'msl_data.json')))
+        self.log(msg=self.get_addon().getSetting('esn'))
+        return self.get_addon().getSetting('esn')
+
+    def set_esn(self, esn):
+        """
+        Returns the esn from settings
+        """
+        stored_esn = self.get_esn()
+        if not stored_esn and esn:
+            self.set_setting('esn', esn)
+            self.delete_manifest_data()            
+            return esn
+        return stored_esn
+    
+    def delete_manifest_data(self):
+        if isfile(self.msl_data_path + 'msl_data.json'):
+            remove(self.msl_data_path + 'msl_data.json')
+        if isfile(self.msl_data_path + 'manifest.json'):
+            remove(self.msl_data_path + 'manifest.json')
+        msl = MSL(kodi_helper=self)
+        msl.perform_key_handshake()
+        msl.save_msl_data()
+
     def get_dolby_setting(self):
         """
         Returns if the dolby sound is enabled
@@ -644,6 +674,7 @@ class KodiHelper:
         bool
             List could be build
         """
+        self.set_esn(esn)
         addon = self.get_addon()
         inputstream_addon = self.get_inputstream_addon()
         if inputstream_addon == None:
@@ -744,7 +775,9 @@ class KodiHelper:
             if 'mpaa' in entry_keys:
                 infos.update({'mpaa': entry['mpaa']})
             else:
-                infos.update({'mpaa': str(entry['maturity']['board']) + '-' + str(entry['maturity']['value'])})
+                if entry.get('maturity', None) is not None:
+                    if entry['maturity']['board'] is not None and entry['maturity']['value'] is not None:
+                        infos.update({'mpaa': str(entry['maturity']['board'].encode('utf-8')) + '-' + str(entry['maturity']['value'].encode('utf-8'))})
         if 'rating' in entry_keys:
             infos.update({'rating': int(entry['rating']) * 2})
         if 'synopsis' in entry_keys:
index 5b7381d74acfe6140654888086c5ff145f030d29..d0387972ceeab83ea253a55bf22fb89bc2c748eb 100644 (file)
@@ -42,7 +42,7 @@ class MSL:
     last_drm_context = ''
     last_playback_context = ''
     #esn = "NFCDCH-LX-CQE0NU6PA5714R25VPLXVU2A193T36"
-    esn = "WWW-BROWSE-D7GW1G4NPXGR1F0X1H3EQGY3V1F5WE"
+    #esn = "WWW-BROWSE-D7GW1G4NPXGR1F0X1H3EQGY3V1F5WE"
     #esn = "NFCDIE-02-DCH84Q2EK3N6VFVQJ0NLRQ27498N0F"
     current_message_id = 0
     session = requests.session()
@@ -71,13 +71,18 @@ class MSL:
         elif self.file_exists(self.kodi_helper.msl_data_path, 'rsa_key.bin'):
             self.kodi_helper.log(msg='RSA Keys do already exist load old ones')
             self.__load_rsa_keys()
-            self.__perform_key_handshake()
+            if self.kodi_helper.get_esn():
+                self.__perform_key_handshake()
         else:
             self.kodi_helper.log(msg='Create new RSA Keys')
             # Create new Key Pair and save
             self.rsa_key = RSA.generate(2048)
             self.__save_rsa_keys()
-            self.__perform_key_handshake()
+            if self.kodi_helper.get_esn():
+                self.__perform_key_handshake()
+    
+    def perform_key_handshake(self):
+        self.__perform_key_handshake()
 
     def load_manifest(self, viewable_id):
         """
@@ -406,6 +411,7 @@ class MSL:
         }
 
     def __generate_msl_request_data(self, data):
+        self.__load_msl_data()
         header_encryption_envelope = self.__encrypt(self.__generate_msl_header())
         header = {
             'headerdata': base64.standard_b64encode(header_encryption_envelope),
@@ -453,9 +459,10 @@ class MSL:
         :return: The base64 encoded JSON String of the header
         """
         self.current_message_id = self.rndm.randint(0, pow(2, 52))
+        esn = self.kodi_helper.get_esn()
 
         header_data = {
-            'sender': self.esn,
+            'sender': esn,
             'handshake': is_handshake,
             'nonreplayable': False,
             'capabilities': {
@@ -507,10 +514,12 @@ class MSL:
         :param plaintext:
         :return: Serialized JSON String of the encryption Envelope
         """
+        esn = self.kodi_helper.get_esn()
+
         iv = get_random_bytes(16)
         encryption_envelope = {
             'ciphertext': '',
-            'keyid': self.esn + '_' + str(self.sequence_number),
+            'keyid': esn + '_' + str(self.sequence_number),
             'sha256': 'AA==',
             'iv': base64.standard_b64encode(iv)
         }
@@ -535,11 +544,13 @@ class MSL:
 
     def __perform_key_handshake(self):
         header = self.__generate_msl_header(is_key_request=True, is_handshake=True, compressionalgo="", encrypt=False)
+        esn = self.kodi_helper.get_esn()
+
         request = {
             'entityauthdata': {
                 'scheme': 'NONE',
                 'authdata': {
-                    'identity': self.esn
+                    'identity': esn
                 }
             },
             'headerdata': base64.standard_b64encode(header),
@@ -596,6 +607,10 @@ class MSL:
         self.encryption_key = base64.standard_b64decode(msl_data['encryption_key'])
         self.sign_key = base64.standard_b64decode(msl_data['sign_key'])
 
+
+    def save_msl_data(self):
+        self.__save_msl_data()
+
     def __save_msl_data(self):
         """
         Saves the keys and tokens in json file
index a4113a49612c37ee7237fb1d71a8e1a61acd3bee..55d95cada2161d58bdaa91402c67e30224d14347 100644 (file)
@@ -40,6 +40,7 @@ class NetflixHttpSubRessourceHandler:
                 self.profiles = self.netflix_session.profiles
         else:
             self.profiles = []
+        self.kodi_helper.set_esn(self.netflix_session.esn)
 
     def is_logged_in (self, params):
         """Existing login proxy function
index f4c9be3fda59bc99b8c5805fcda5e45b9f8e08e8..ca4bd5cc0272449d88bfaaf71a96ce508b17acb4 100644 (file)
@@ -149,7 +149,7 @@ class NetflixSession:
             :obj:`list` of :obj:`dict`
                 List of all the serialized data pulled out of the pagws <script/> tags
         """
-        scripts = page_soup.find_all('script', attrs={'src': None});
+        scripts = page_soup.find_all('script', attrs={'src': None})
         self.log(msg='Trying sloppy inline data parser')
         inline_data = self._sloppy_parse_inline_data(scripts=scripts)
         if self._verfify_auth_and_profiles_data(data=inline_data) != False:
@@ -1240,6 +1240,11 @@ class NetflixSession:
           },
         }
         """
+        mpaa = ''
+        if episode.get('maturity', None) is not None:
+            if episode['maturity'].get('board', None) is not None and episode['maturity'].get('value', None) is not None:
+                mpaa = str(episode['maturity'].get('board', '').encode('utf-8')) + '-' + str(episode['maturity'].get('value', '').encode('utf-8'))
+
         return {
             episode['summary']['id']: {
                 'id': episode['summary']['id'],
@@ -1250,7 +1255,7 @@ class NetflixSession:
                 'title': episode['info']['title'],
                 'year': episode['info']['releaseYear'],
                 'genres': self.parse_genres_for_video(video=episode, genres=genres),
-                'mpaa': str(episode['maturity']['rating']['board']) + ' ' + str(episode['maturity']['rating']['value']),
+                'mpaa': mpaa,
                 'maturity': episode['maturity'],
                 'playcount': (0, 1)[episode['watched']],
                 'rating': episode['userRating'].get('average', 0) if episode['userRating'].get('average', None) != None else episode['userRating'].get('predicted', 0),
@@ -1360,7 +1365,7 @@ class NetflixSession:
         response = self._path_request(paths=paths)
         return self._process_response(response=response, component='Search results')
 
-    def fetch_video_list (self, list_id, list_from=0, list_to=20):
+    def fetch_video_list (self, list_id, list_from=0, list_to=26):
         """Fetches the JSON which contains the contents of a given video list
 
         Parameters
@@ -1884,14 +1889,14 @@ class NetflixSession:
             User Agent for platform
         """
         import platform
-        if platform == 'linux' or platform == 'linux2':
-            return 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
-        elif platform == 'darwin':
+        self.log(msg='Building User Agent for platform: ' + str(platform.system()) + ' - ' + str(platform.machine()))
+        if platform.system() == 'Darwin':
             return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
-        elif platform == 'win32':
+        if platform.system() == 'Windows':
             return 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
-        else:
-            return 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
+        if platform.machine().startswith('arm'):
+            return 'Mozilla/5.0 (X11; CrOS armv7l 7647.78.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36'
+        return 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
 
     def _session_post (self, component, type='document', data={}, headers={}, params={}):
         """Executes a get request using requests for the current session & measures the duration of that request
@@ -2207,7 +2212,6 @@ class NetflixSession:
         important_fields = [
             'profileName',
             'isActive',
-            'isFirstUse',
             'isAccountOwner',
             'isKids'
         ]
@@ -2220,7 +2224,7 @@ class NetflixSession:
                         profile.update({important_field: netflix_page_data['profiles'][profile_id]['summary'][important_field]})
                     avatar_base = netflix_page_data['nf'].get(netflix_page_data['profiles'][profile_id]['summary']['avatarName'], False);
                     avatar = 'https://secure.netflix.com/ffe/profiles/avatars_v2/320x320/PICON_029.png' if avatar_base == False else avatar_base['images']['byWidth']['320']['value']
-                    profile.update({'avatar': avatar})
+                    profile.update({'avatar': avatar, 'isFirstUse': False})
                     profiles.update({profile_id: profile})
             return profiles
 
@@ -2295,11 +2299,29 @@ class NetflixSession:
             :obj:`str` of :obj:`str
             ESN, something like: NFCDCH-MC-D7D6F54LOPY8J416T72MQXX3RD20ME
         """
-        esn = ''
+        # we generate an esn from device strings for android
+        import subprocess
+        manufacturer = subprocess.check_output(["/system/bin/getprop", "ro.product.manufacturer"])
+
+        if manufacturer :
+            esn = 'NFANDROID1-PRV-'
+            input = subprocess.check_output(["/system/bin/getprop", "ro.nrdp.modelgroup"])
+            if not input:
+                esn = esn + 'T-L3-'
+            else:
+                esn = esn + input.strip(' \t\n\r') + '-'
+            esn = esn + '{:5}'.format(manufacturer.strip(' \t\n\r').upper())
+            input = subprocess.check_output(["/system/bin/getprop" ,"ro.product.model"])
+            esn = esn + input.strip(' \t\n\r').replace(' ', '=').upper()
+            self.log(msg='Android generated ESN:' + esn)
+            return esn
+
         # values are accessible via dict (sloppy parsing successfull)
         if type(netflix_page_data) == dict:
             return netflix_page_data.get('esn', '')
 
+        esn = ''
+
         # values are stored in lists (returned from JS parser)
         for item in netflix_page_data:
             if 'esnGeneratorModel' in dict(item).keys():