Merge branch 'master' of https://github.com/asciidisco/plugin.video.netflix
authorJohannes Trum <johannes@die-trums.de>
Thu, 2 Mar 2017 13:12:54 +0000 (14:12 +0100)
committerJohannes Trum <johannes@die-trums.de>
Thu, 2 Mar 2017 13:12:54 +0000 (14:12 +0100)
.gitignore
resources/language/English/strings.po
resources/language/German/strings.po
resources/lib/KodiHelper.py
resources/lib/MSL.py
resources/lib/MSLHttpRequestHandler.py
resources/settings.xml
service.py

index aec41d801287a454d781aef1d20afb7f5553e2b9..2bdcf64adb7c611fac753e46aa4953555b8c1e92 100644 (file)
@@ -1,3 +1,4 @@
 .DS_Store
 *.pyo
 *.pyc
+.idea
index eb5e3785fa2e5eac2df3d0bb42b3efa40d312831..7ec2d52aa56ddbeec15a172ccc9efdf7fbf9e4bd 100644 (file)
@@ -85,14 +85,6 @@ msgctxt "#30014"
 msgid "Account"
 msgstr ""
 
-msgctxt "#30015"
-msgid "Logging"
-msgstr ""
-
-msgctxt "#30016"
-msgid "Verbose logging"
-msgstr ""
-
 msgctxt "#30017"
 msgid "Logout"
 msgstr ""
index d82af9c079a36b6ce17468b79cd6a90dd786f2fc..62ee4636dfeaeb4a8682cb36aa60a8023bdd2f5f 100644 (file)
@@ -85,14 +85,6 @@ msgctxt "#30014"
 msgid "Account"
 msgstr "Account"
 
-msgctxt "#30015"
-msgid "Logging"
-msgstr "Logging"
-
-msgctxt "#30016"
-msgid "Verbose logging"
-msgstr "Verbose logging"
-
 msgctxt "#30017"
 msgid "Logout"
 msgstr "Logout"
index b07efaf3cb4b3d95eec40e85f165ca9692e4f64a..364fd7a82115786bf0d8560e640929d0f01c4ea4 100644 (file)
@@ -805,7 +805,7 @@ class KodiHelper:
         li.addContextMenuItems(items)
         return li
 
-    def log (self, msg, level=xbmc.LOGNOTICE):
+    def log (self, msg, level=xbmc.LOGDEBUG):
         """Adds a log entry to the Kodi log
 
         Parameters
@@ -816,12 +816,9 @@ class KodiHelper:
         level : :obj:`int`
             Kodi log level
         """
-        if self.verb_log:
-            if level == xbmc.LOGDEBUG and self.verb_log:
-                level = xbmc.LOGNOTICE
-            if isinstance(msg, unicode):
-                msg = msg.encode('utf-8')
-            xbmc.log('[%s] %s' % (self.plugin, msg.__str__()), level)
+        if isinstance(msg, unicode):
+            msg = msg.encode('utf-8')
+        xbmc.log('[%s] %s' % (self.plugin, msg.__str__()), level)
 
     def get_local_string (self, string_id):
         """Returns the localized version of a string
index 7210c71f31a5817729188e28708db4dec7498aee..71f1810a46ce4670732b46f1098688727531111b 100644 (file)
@@ -5,18 +5,16 @@ import os
 import pprint
 import random
 from StringIO import StringIO
-from hmac import HMAC
-import hashlib
 import requests
 import zlib
 
 import time
-from Crypto.PublicKey import RSA
-from Crypto.Cipher import PKCS1_OAEP
-from Crypto.Cipher import AES
-from Crypto.Random import get_random_bytes
-from Crypto.Hash import HMAC, SHA256
-from Crypto.Util import Padding
+from Cryptodome.PublicKey import RSA
+from Cryptodome.Cipher import PKCS1_OAEP
+from Cryptodome.Cipher import AES
+from Cryptodome.Random import get_random_bytes
+from Crypto.Hash import HMAC, SHA256
+from Cryptodome.Util import Padding
 import xml.etree.ElementTree as ET
 
 pp = pprint.PrettyPrinter(indent=4)
@@ -61,6 +59,7 @@ class MSL:
             pass
 
         if self.file_exists(self.kodi_helper.msl_data_path, 'msl_data.json'):
+            self.kodi_helper.log(msg='MSL Data exists. Use old Tokens.')
             self.__load_msl_data()
             self.handshake_performed = True
         elif self.file_exists(self.kodi_helper.msl_data_path, 'rsa_key.bin'):
@@ -75,6 +74,11 @@ class MSL:
             self.__perform_key_handshake()
 
     def load_manifest(self, viewable_id):
+        """
+        Loads the manifets for the given viewable_id and returns a mpd-XML-Manifest
+        :param viewable_id: The id of of the viewable
+        :return: MPD XML Manifest or False if no success
+        """
         manifest_request_data = {
             'method': 'manifest',
             'lookupType': 'PREPARE',
@@ -106,38 +110,28 @@ class MSL:
             'uiVersion': 'akira'
         }
         request_data = self.__generate_msl_request_data(manifest_request_data)
-
         resp = self.session.post(self.endpoints['manifest'], request_data)
 
-
         try:
+            # if the json() does not fail we have an error because the manifest response is a chuncked json response
             resp.json()
-            self.kodi_helper.log(msg='MANIFEST RESPONE JSON: '+resp.text)
+            self.kodi_helper.log(msg='Error getting Manifest: '+resp.text)
+            return False
         except ValueError:
-            # Maybe we have a CHUNKED response
+            # json() failed so parse the chunked response
+            self.kodi_helper.log(msg='Got chunked Manifest Response: ' + resp.text)
             resp = self.__parse_chunked_msl_response(resp.text)
+            self.kodi_helper.log(msg='Parsed chunked Response: ' + json.dumps(resp))
             data = self.__decrypt_payload_chunk(resp['payloads'][0])
-            # pprint.pprint(data)
             return self.__tranform_to_dash(data)
 
-
     def get_license(self, challenge, sid):
-
         """
-            std::time_t t = std::time(0);  // t is an integer type
-    licenseRequestData["clientTime"] = (int)t;
-    //licenseRequestData["challengeBase64"] = challengeStr;
-    licenseRequestData["licenseType"] = "STANDARD";
-    licenseRequestData["playbackContextId"] = playbackContextId;//"E1-BQFRAAELEB32o6Se-GFvjwEIbvDydEtfj6zNzEC3qwfweEPAL3gTHHT2V8rS_u1Mc3mw5BWZrUlKYIu4aArdjN8z_Z8t62E5jRjLMdCKMsVhlSJpiQx0MNW4aGqkYz-1lPh85Quo4I_mxVBG5lgd166B5NDizA8.";
-    licenseRequestData["drmContextIds"] = Json::arrayValue;
-    licenseRequestData["drmContextIds"].append(drmContextId);
-
-        :param viewable_id:
-        :param challenge:
-        :param kid:
-        :return:
+        Requests and returns a license for the given challenge and sid
+        :param challenge: The base64 encoded challenge
+        :param sid: The sid paired to the challengew
+        :return: Base64 representation of the license key or False if no success
         """
-
         license_request_data = {
             'method': 'license',
             'licenseType': 'STANDARD',
@@ -159,18 +153,19 @@ class MSL:
         resp = self.session.post(self.endpoints['license'], request_data)
 
         try:
+            # If is valid json the request for the licnese failed
             resp.json()
-            self.kodi_helper.log(msg='LICENSE RESPONE JSON: '+resp.text)
+            self.kodi_helper.log(msg='Error getting license: '+resp.text)
+            return False
         except ValueError:
-            # Maybe we have a CHUNKED response
+            # json() failed so we have a chunked json response
             resp = self.__parse_chunked_msl_response(resp.text)
             data = self.__decrypt_payload_chunk(resp['payloads'][0])
-            # pprint.pprint(data)
             if data['success'] is True:
                 return data['result']['licenses'][0]['data']
             else:
-                return ''
-
+                self.kodi_helper.log(msg='Error getting license: ' + json.dumps(data))
+                return False
 
     def __decrypt_payload_chunk(self, payloadchunk):
         payloadchunk = json.JSONDecoder().decode(payloadchunk)
@@ -329,21 +324,7 @@ class MSL:
             'signature': self.__sign(first_payload_encryption_envelope),
         }
 
-
-        # Create Second Payload
-        second_payload = {
-            "messageid": self.current_message_id,
-            "data": "",
-            "endofmsg": True,
-            "sequencenumber": 2
-        }
-        second_payload_encryption_envelope = self.__encrypt(json.dumps(second_payload))
-        second_payload_chunk = {
-            'payload': base64.standard_b64encode(second_payload_encryption_envelope),
-            'signature': base64.standard_b64encode(self.__sign(second_payload_encryption_envelope)),
-        }
-
-        request_data = json.dumps(header) + json.dumps(first_payload_chunk) # + json.dumps(second_payload_chunk)
+        request_data = json.dumps(header) + json.dumps(first_payload_chunk)
         return request_data
 
 
@@ -430,18 +411,18 @@ class MSL:
         encryption_envelope['ciphertext'] = base64.standard_b64encode(ciphertext)
         return json.dumps(encryption_envelope)
 
-    def __sign(self, text):
-        #signature = hmac.new(self.sign_key, text, hashlib.sha256).digest()
-        signature = HMAC(self.sign_key, text, hashlib.sha256).digest()
-
 
-        # hmac = HMAC.new(self.sign_key, digestmod=SHA256)
-        # hmac.update(text)
+    def __sign(self, text):
+        """
+        Calculates the HMAC signature for the given text with the current sign key and SHA256
+        :param text:
+        :return: Base64 encoded signature
+        """
+        signature = HMAC.new(self.sign_key, text, SHA256).digest()
         return base64.standard_b64encode(signature)
 
 
     def __perform_key_handshake(self):
-
         header = self.__generate_msl_header(is_key_request=True, is_handshake=True, compressionalgo="", encrypt=False)
         request = {
             'entityauthdata': {
@@ -456,7 +437,6 @@ class MSL:
         self.kodi_helper.log(msg='Key Handshake Request:')
         self.kodi_helper.log(msg=json.dumps(request))
 
-
         resp = self.session.post(self.endpoints['manifest'], json.dumps(request, sort_keys=True))
         if resp.status_code == 200:
             resp = resp.json()
@@ -510,8 +490,7 @@ class MSL:
 
     def __set_master_token(self, master_token):
         self.mastertoken = master_token
-        self.sequence_number = json.JSONDecoder().decode(base64.standard_b64decode(master_token['tokendata']))[
-            'sequencenumber']
+        self.sequence_number = json.JSONDecoder().decode(base64.standard_b64decode(master_token['tokendata']))['sequencenumber']
 
     def __load_rsa_keys(self):
         loaded_key = self.load_file(self.kodi_helper.msl_data_path, 'rsa_key.bin')
index eba87e9f2ea769c4943ac6016bda088149ab5041..25abf54fdef82843409c125e23e4c935259192b1 100644 (file)
@@ -1,7 +1,7 @@
 import BaseHTTPServer
 import base64
 from urlparse import urlparse, parse_qs
-
+import xbmcaddon
 from MSL import MSL
 from KodiHelper import KodiHelper
 
@@ -9,6 +9,7 @@ kodi_helper = KodiHelper(
     plugin_handle=None,
     base_url=None
 )
+
 account = kodi_helper.get_credentials()
 msl = MSL(account['email'], account['password'], kodi_helper)
 
index 68779c0142cd3450a7e367e4c0b7f8cb61a1052f..b5048518d3a7f715364b25d4888c2c010d296ebe 100644 (file)
@@ -14,7 +14,4 @@
     <setting id="msl_service_port" value="8000" visible="false"/>
     <setting id="msl_service_certificate" visible="false" value="Cr0CCAMSEOVEukALwQ8307Y2+LVP+0MYh/HPkwUijgIwggEKAoIBAQDm875btoWUbGqQD8eAGuBlGY+Pxo8YF1LQR+Ex0pDONMet8EHslcZRBKNQ/09RZFTP0vrYimyYiBmk9GG+S0wB3CRITgweNE15cD33MQYyS3zpBd4z+sCJam2+jj1ZA4uijE2dxGC+gRBRnw9WoPyw7D8RuhGSJ95OEtzg3Ho+mEsxuE5xg9LM4+Zuro/9msz2bFgJUjQUVHo5j+k4qLWu4ObugFmc9DLIAohL58UR5k0XnvizulOHbMMxdzna9lwTw/4SALadEV/CZXBmswUtBgATDKNqjXwokohncpdsWSauH6vfS6FXwizQoZJ9TdjSGC60rUB2t+aYDm74cIuxAgMBAAE6EHRlc3QubmV0ZmxpeC5jb20SgAOE0y8yWw2Win6M2/bw7+aqVuQPwzS/YG5ySYvwCGQd0Dltr3hpik98WijUODUr6PxMn1ZYXOLo3eED6xYGM7Riza8XskRdCfF8xjj7L7/THPbixyn4mULsttSmWFhexzXnSeKqQHuoKmerqu0nu39iW3pcxDV/K7E6aaSr5ID0SCi7KRcL9BCUCz1g9c43sNj46BhMCWJSm0mx1XFDcoKZWhpj5FAgU4Q4e6f+S8eX39nf6D6SJRb4ap7Znzn7preIvmS93xWjm75I6UBVQGo6pn4qWNCgLYlGGCQCUm5tg566j+/g5jvYZkTJvbiZFwtjMW5njbSRwB3W4CrKoyxw4qsJNSaZRTKAvSjTKdqVDXV/U5HK7SaBA6iJ981/aforXbd2vZlRXO/2S+Maa2mHULzsD+S5l4/YGpSt7PnkCe25F+nAovtl/ogZgjMeEdFyd/9YMYjOS4krYmwp3yJ7m9ZzYCQ6I8RQN4x/yLlHG5RH/+WNLNUs6JAZ0fFdCmw="/>
   </category>
-  <category label="30015">
-    <setting id="logging" type="bool" label="30016" default="false"/>
-  </category>
 </settings>
index ed573fc13736ef58c56a46392ac7d9c0ccaf66a3..b6abc1b8e8ed11e98aa9f1078fe43c166e55f92f 100644 (file)
@@ -6,25 +6,27 @@ import socket
 from resources.lib.KodiHelper import KodiHelper
 from resources.lib.MSLHttpRequestHandler import MSLHttpRequestHandler
 
-def select_unused_port():
-  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-  s.bind(('localhost', 0))
-  addr, port = s.getsockname()
-  s.close()
-  return port
-
 addon = xbmcaddon.Addon()
 kodi_helper = KodiHelper(
     plugin_handle=None,
     base_url=None
 )
 
-PORT = select_unused_port()
-addon.setSetting('msl_service_port', str(PORT))
-kodi_helper.log(msg='Picked Port: ' + str(PORT))
-Handler = MSLHttpRequestHandler
+
+def select_unused_port():
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    sock.bind(('localhost', 0))
+    addr, port = sock.getsockname()
+    sock.close()
+    return port
+
+port = select_unused_port()
+addon.setSetting('msl_service_port', str(port))
+kodi_helper.log(msg='Picked Port: ' + str(port))
+
+#Config the HTTP Server
 SocketServer.TCPServer.allow_reuse_address = True
-server = SocketServer.TCPServer(('127.0.0.1', PORT), Handler)
+server = SocketServer.TCPServer(('127.0.0.1', port), MSLHttpRequestHandler)
 server.server_activate()
 server.timeout = 1