# Created on: 13.01.2017
import sys
-from resources.lib.NetflixSession import NetflixSession
from resources.lib.KodiHelper import KodiHelper
from resources.lib.Navigation import Navigation
from resources.lib.Library import Library
plugin_handle=plugin_handle,
base_url=base_url
)
-netflix_session = NetflixSession(
- cookie_path=kodi_helper.cookie_path,
- data_path=kodi_helper.data_path,
- verify_ssl=kodi_helper.get_ssl_verification_setting(),
- log_fn=kodi_helper.log
-)
library = Library(
root_folder=kodi_helper.base_data_path,
library_settings=kodi_helper.get_custom_library_settings(),
log_fn=kodi_helper.log
)
navigation = Navigation(
- netflix_session=netflix_session,
kodi_helper=kodi_helper,
library=library,
base_url=base_url,
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="plugin.video.netflix" name="Netflix" version="0.9.11" provider-name="libdev + jojo + asciidisco">
+<addon id="plugin.video.netflix" name="Netflix" version="0.10.6" provider-name="libdev + jojo + asciidisco">
<requires>
<import addon="xbmc.python" version="2.24.0"/>
<import addon="script.module.beautifulsoup4" version="4.3.2"/>
# Kodi Media Center language file
# Addon Name: Netflix
# Addon id: plugin.video.netflix
-# Addon version: 0.9.11
+# Addon version: 0.10.6
# Addon Provider: libdev + jojo + asciidisco
msgid ""
msgstr ""
msgctxt "#30034"
msgid "ESN (set automatically, can be changed manually)"
msgstr ""
+
+msgctxt "#30035"
+msgid "Inputstream Addon Settings..."
+msgstr ""
# Kodi Media Center language file
# Addon Name: Netflix
# Addon id: plugin.video.netflix
-# Addon version: 0.9.11
+# Addon version: 0.10.6
# Addon Provider: libdev + jojo + asciidisco
msgid ""
msgstr ""
msgctxt "#30034"
msgid "ESN (set automatically, can be changed manually)"
msgstr "ESN (änderbar, wird auto. gesetzt)"
+
+msgctxt "#30035"
+msgid "Inputstream Addon Settings..."
+msgstr "Inputstream Addon Settings..."
class KodiHelper:
"""Consumes all the configuration data from Kodi as well as turns data into lists of folders and videos"""
- def __init__ (self, plugin_handle, base_url):
+ def __init__ (self, plugin_handle=None, base_url=None):
"""Fetches all needed info from Kodi & configures the baseline of the plugin
Parameters
"""
for video_list_id in video_list:
video = video_list[video_list_id]
- if type != 'queue' or (type == 'queue' and video['in_my_list'] == True):
- li = xbmcgui.ListItem(label=video['title'])
- # add some art to the item
- li = self._generate_art_info(entry=video, li=li)
- # it´s a show, so we need a subfolder & route (for seasons)
- isFolder = True
- url = build_url({'action': actions[video['type']], 'show_id': video_list_id})
- # lists can be mixed with shows & movies, therefor we need to check if its a movie, so play it right away
- if video_list[video_list_id]['type'] == 'movie':
- # it´s a movie, so we need no subfolder & a route to play it
- isFolder = False
- url = build_url({'action': 'play_video', 'video_id': video_list_id})
- # add list item info
- li = self._generate_entry_info(entry=video, li=li)
- li = self._generate_context_menu_items(entry=video, li=li)
- xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=isFolder)
+ li = xbmcgui.ListItem(label=video['title'])
+ # add some art to the item
+ li = self._generate_art_info(entry=video, li=li)
+ # it´s a show, so we need a subfolder & route (for seasons)
+ isFolder = True
+ url = build_url({'action': actions[video['type']], 'show_id': video_list_id})
+ # lists can be mixed with shows & movies, therefor we need to check if its a movie, so play it right away
+ if video_list[video_list_id]['type'] == 'movie':
+ # it´s a movie, so we need no subfolder & a route to play it
+ isFolder = False
+ url = build_url({'action': 'play_video', 'video_id': video_list_id})
+ # add list item info
+ li = self._generate_entry_info(entry=video, li=li)
+ li = self._generate_context_menu_items(entry=video, li=li)
+ xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=isFolder)
xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Module: MSL
+# Created on: 26.01.2017
+
import base64
import gzip
import json
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Module: MSLHttpRequestHandler
+# Created on: 26.01.2017
+
import BaseHTTPServer
import base64
from urlparse import urlparse, parse_qs
from MSL import MSL
from KodiHelper import KodiHelper
-kodi_helper = KodiHelper(
- plugin_handle=None,
- base_url=None
-)
+kodi_helper = KodiHelper()
msl = MSL(kodi_helper)
# Module: Navigation
# Created on: 13.01.2017
-from urllib import urlencode, unquote
+import urllib
+import urllib2
+import json
+from xbmcaddon import Addon
from urlparse import parse_qsl
from utils import noop, log
class Navigation:
"""Routes to the correct subfolder, dispatches actions & acts as a controller for the Kodi view & the Netflix model"""
- def __init__ (self, netflix_session, kodi_helper, library, base_url, log_fn=noop):
+ def __init__ (self, kodi_helper, library, base_url, log_fn=noop):
"""Takes the instances & configuration options needed to drive the plugin
Parameters
----------
- netflix_session : :obj:`NetflixSession`
- instance of the NetflixSession class
-
kodi_helper : :obj:`KodiHelper`
instance of the KodiHelper class
log_fn : :obj:`fn`
optional log function
"""
- self.netflix_session = netflix_session
self.kodi_helper = kodi_helper
self.library = library
self.base_url = base_url
"""
params = self.parse_paramters(paramstring=paramstring)
+ # open foreign settings dialog
+ if 'mode' in params.keys() and params['mode'] == 'openSettings':
+ return self.open_settings(params['url'])
+
# log out the user
if 'action' in params.keys() and params['action'] == 'logout':
- return self.netflix_session.logout()
+ return self.call_netflix_service({'method': 'logout'})
# check login & try to relogin if necessary
account = self.kodi_helper.get_credentials()
if account['email'] != '' and account['password'] != '':
- if self.netflix_session.is_logged_in(account=account) != True:
+ if self.call_netflix_service({'method': 'is_logged_in'}) != True:
if self.establish_session(account=account) != True:
return self.kodi_helper.show_login_failed_notification()
return self.add_to_list(video_id=params['id'])
elif params['action'] == 'export':
# adds a title to the users list on Netflix
- alt_title = self.kodi_helper.show_add_to_library_title_dialog(original_title=unquote(params['title']).decode('utf8'))
+ alt_title = self.kodi_helper.show_add_to_library_title_dialog(original_title=urllib.unquote(params['title']).decode('utf8'))
return self.export_to_library(video_id=params['id'], alt_title=alt_title)
elif params['action'] == 'remove':
# adds a title to the users list on Netflix
start_offset : :obj:`str`
Offset to resume playback from (in seconds)
"""
- # widevine esn
- esn = self.netflix_session.esn
+ esn = self.call_netflix_service({'method': 'get_esn'})
return self.kodi_helper.play_item(esn=esn, video_id=video_id, start_offset=start_offset)
@log
bool
If no results are available
"""
- has_search_results = False
- search_results_raw = self.netflix_session.fetch_search_results(search_str=term)
- # check for any errors
- if self._is_dirty_response(response=search_results_raw):
- return False
-
- # determine if we found something
- if 'search' in search_results_raw['value']:
- for key in search_results_raw['value']['search'].keys():
- if self.netflix_session._is_size_key(key=key) == False:
- has_search_results = search_results_raw['value']['search'][key]['titles']['length'] > 0
- if has_search_results == False:
- if search_results_raw['value']['search'][key].get('suggestions', False) != False:
- for entry in search_results_raw['value']['search'][key]['suggestions']:
- if self.netflix_session._is_size_key(key=entry) == False:
- if search_results_raw['value']['search'][key]['suggestions'][entry]['relatedvideos']['length'] > 0:
- has_search_results = True
-
-
- # display that we haven't found a thing
- if has_search_results == False:
- return self.kodi_helper.build_no_search_results_available(build_url=self.build_url, action='search')
-
- # list the search results
- search_results = self.netflix_session.parse_search_results(response_data=search_results_raw)
- # add more menaingful data to the search results
- raw_search_contents = self.netflix_session.fetch_video_list_information(video_ids=search_results.keys())
+ search_contents = self.call_netflix_service({'method': 'search', 'term': term})
# check for any errors
- if self._is_dirty_response(response=raw_search_contents):
+ if self._is_dirty_response(response=search_contents):
return False
- search_contents = self.netflix_session.parse_video_list(response_data=raw_search_contents)
actions = {'movie': 'play_video', 'show': 'season_list'}
return self.kodi_helper.build_search_result_listing(video_list=search_contents, actions=actions, build_url=self.build_url)
user_list_id : :obj:`str`
Type of list to display
"""
- video_list_ids_raw = self.netflix_session.fetch_video_list_ids()
+ 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_raw):
+ if self._is_dirty_response(response=video_list_ids):
return False
- video_list_ids = self.netflix_session.parse_video_list_ids(response_data=video_list_ids_raw)
return self.kodi_helper.build_user_sub_listing(video_list_ids=video_list_ids[type], type=type, action='video_list', build_url=self.build_url)
def show_episode_list (self, season_id):
if self.kodi_helper.has_cached_item(cache_id=cache_id):
episode_list = self.kodi_helper.get_cached_item(cache_id=cache_id)
else:
- raw_episode_list = self.netflix_session.fetch_episodes_by_season(season_id=season_id)
+ episode_list = self.call_netflix_service({'method': 'fetch_episodes_by_season', 'season_id': season_id})
# check for any errors
- if self._is_dirty_response(response=raw_episode_list):
+ if self._is_dirty_response(response=episode_list):
return False
# parse the raw Netflix data
- episode_list = self.netflix_session.parse_episodes_by_season(response_data=raw_episode_list)
self.kodi_helper.add_cached_item(cache_id=cache_id, contents=episode_list)
# sort seasons by number (they´re coming back unsorted from the api)
if self.kodi_helper.has_cached_item(cache_id=cache_id):
season_list = self.kodi_helper.get_cached_item(cache_id=cache_id)
else:
- season_list_raw = self.netflix_session.fetch_seasons_for_show(id=show_id);
+ season_list = self.call_netflix_service({'method': 'fetch_seasons_for_show', 'show_id': show_id})
# check for any errors
- if self._is_dirty_response(response=season_list_raw):
+ if self._is_dirty_response(response=season_list):
return False
# check if we have sesons, announced shows that are not available yet have none
- if 'seasons' not in season_list_raw['value']:
+ if len(season_list) == 0:
return self.kodi_helper.build_no_seasons_available()
# parse the seasons raw response from Netflix
- season_list = self.netflix_session.parse_seasons(id=show_id, response_data=season_list_raw)
self.kodi_helper.add_cached_item(cache_id=cache_id, contents=season_list)
# sort seasons by index by default (they´re coming back unsorted from the api)
seasons_sorted = []
if self.kodi_helper.has_cached_item(cache_id=video_list_id):
video_list = self.kodi_helper.get_cached_item(cache_id=video_list_id)
else:
- raw_video_list = self.netflix_session.fetch_video_list(list_id=video_list_id)
+ video_list = self.call_netflix_service({'method': 'fetch_video_list', 'list_id': video_list_id})
# check for any errors
- if self._is_dirty_response(response=raw_video_list):
+ if self._is_dirty_response(response=video_list):
return False
# parse the video list ids
- if 'videos' in raw_video_list['value'].keys():
- video_list = self.netflix_session.parse_video_list(response_data=raw_video_list)
+ if len(video_list) > 0:
self.kodi_helper.add_cached_item(cache_id=video_list_id, contents=video_list)
- else:
- video_list = []
actions = {'movie': 'play_video', 'show': 'season_list'}
return self.kodi_helper.build_video_listing(video_list=video_list, actions=actions, type=type, build_url=self.build_url)
video_list_ids = self.kodi_helper.get_cached_item(cache_id=cache_id)
else:
# fetch video lists
- raw_video_list_ids = self.netflix_session.fetch_video_list_ids()
+ video_list_ids = self.call_netflix_service({'method': 'fetch_video_list_ids'})
# check for any errors
- if self._is_dirty_response(response=raw_video_list_ids):
+ if self._is_dirty_response(response=video_list_ids):
return False
# parse the video list ids
- video_list_ids = self.netflix_session.parse_video_list_ids(response_data=raw_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']
@log
def show_profiles (self):
"""List the profiles for the active account"""
- credentials = self.kodi_helper.get_credentials()
- self.netflix_session.refresh_session_data(account=credentials)
- profiles = self.netflix_session.profiles
+ profiles = self.call_netflix_service({'method': 'list_profiles'})
+ if len(profiles) == 0:
+ return self.kodi_helper.show_login_failed_notification()
return self.kodi_helper.build_profiles_listing(profiles=profiles, action='video_lists', build_url=self.build_url)
@log
ID of the video list that should be displayed
"""
rating = self.kodi_helper.show_rating_dialog()
- return self.netflix_session.rate_video(video_id=video_id, rating=rating)
+ return self.call_netflix_service({'method': 'rate_video', 'video_id': video_id, 'rating': rating})
@log
def remove_from_list (self, video_id):
video_list_id : :obj:`str`
ID of the video list that should be displayed
"""
- self.netflix_session.remove_from_list(video_id=video_id)
+ self.call_netflix_service({'method': 'remove_from_list', 'video_id': video_id})
return self.kodi_helper.refresh()
@log
video_list_id : :obj:`str`
ID of the video list that should be displayed
"""
- self.netflix_session.add_to_list(video_id=video_id)
+ self.call_netflix_service({'method': 'add_to_list', 'video_id': video_id})
return self.kodi_helper.refresh()
@log
alt_title : :obj:`str`
Alternative title (for the folder written to disc)
"""
- metadata = self.netflix_session.fetch_metadata(id=video_id)
+ metadata = self.call_netflix_service({'method': 'fetch_metadata', 'video_id': video_id})
# check for any errors
if self._is_dirty_response(response=metadata):
return False
video_id : :obj:`str`
ID of the movie or show
"""
- metadata = self.netflix_session.fetch_metadata(id=video_id)
+ metadata = self.call_netflix_service({'method': 'fetch_metadata', 'video_id': video_id})
# check for any errors
if self._is_dirty_response(response=metadata):
return False
bool
If we don't have an active session & the user couldn't be logged in
"""
- return True if self.netflix_session.is_logged_in(account=account) else self.netflix_session.login(account=account)
+ is_logged_in = self.call_netflix_service({'method': 'is_logged_in'})
+ return True if is_logged_in else self.call_netflix_service({'method': 'login', 'email': account['email'], 'password': account['password']})
@log
def before_routing_action (self, params):
# check and switch the profile if needed
if self.check_for_designated_profile_change(params=params):
self.kodi_helper.invalidate_memcache()
- self.netflix_session.switch_profile(profile_id=params['profile_id'], account=credentials)
+ self.call_netflix_service({'method': 'switch_profile', 'profile_id': params['profile_id']})
# check login, in case of main menu
if 'action' not in params:
self.establish_session(account=credentials)
Profile should be switched or not
"""
# check if we need to switch the user
- if 'guid' not in self.netflix_session.user_data:
+ user_data = self.call_netflix_service({'method': 'get_user_data'})
+ if 'guid' not in user_data:
return False
- current_profile_id = self.netflix_session.user_data['guid']
+ current_profile_id = user_data['guid']
return 'profile_id' in params and current_profile_id != params['profile_id']
def parse_paramters (self, paramstring):
str
Url + querystring based on the param
"""
- return self.base_url + '?' + urlencode(query)
+ return self.base_url + '?' + urllib.urlencode(query)
+
+ def get_netflix_service_url (self):
+ """Returns URL & Port of the internal Netflix HTTP Proxy service
+
+ Returns
+ -------
+ str
+ Url + Port
+ """
+ return 'http://localhost:' + str(self.kodi_helper.addon.getSetting('netflix_service_port'))
+
+ def call_netflix_service (self, params):
+ """Makes a GET request to the internal Netflix HTTP proxy and returns the result
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ List of paramters to be url encoded
+
+ Returns
+ -------
+ :obj:`dict`
+ Netflix Service RPC result
+ """
+ url_values = urllib.urlencode(params)
+ url = self.get_netflix_service_url()
+ full_url = url + '?' + url_values
+ data = urllib2.urlopen(full_url).read()
+ parsed_json = json.loads(data)
+ return parsed_json.get('result', None)
+
+ def open_settings(self, url):
+ """Opens a foreign settings dialog"""
+ is_addon = self.kodi_helper.get_inputstream_addon()
+ url = is_addon if url == 'is' else url
+ return Addon(url).openSettings()
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Module: NetflixHttpRequestHandler
+# Created on: 07.03.2017
+
+import BaseHTTPServer
+import json
+from types import FunctionType
+from urlparse import urlparse, parse_qs
+from resources.lib.KodiHelper import KodiHelper
+from resources.lib.NetflixSession import NetflixSession
+from resources.lib.NetflixHttpSubRessourceHandler import NetflixHttpSubRessourceHandler
+
+kodi_helper = KodiHelper()
+
+netflix_session = NetflixSession(
+ cookie_path=kodi_helper.cookie_path,
+ data_path=kodi_helper.data_path,
+ verify_ssl=kodi_helper.get_ssl_verification_setting(),
+ log_fn=kodi_helper.log
+)
+
+# get list of methods & instance form the sub ressource handler
+methods = [x for x, y in NetflixHttpSubRessourceHandler.__dict__.items() if type(y) == FunctionType]
+sub_res_handler = NetflixHttpSubRessourceHandler(kodi_helper=kodi_helper, netflix_session=netflix_session)
+
+class NetflixHttpRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """ Represents the callable internal server that dispatches requests to Netflix"""
+
+ def do_GET(self):
+ """GET request handler (we only need this, as we only do GET requests internally)"""
+ url = urlparse(self.path)
+ params = parse_qs(url.query)
+ method = params.get('method', [None])[0]
+
+ # not method given
+ if method == None:
+ self.send_error(500, 'No method declared')
+ return
+
+ # no existing method given
+ if method not in methods:
+ self.send_error(404, 'Method "' + str(method) + '" not found. Available methods: ' + str(methods))
+ return
+
+ # call method & get the result
+ result = getattr(sub_res_handler, method)(params)
+ self.send_response(200)
+ self.send_header('Content-type', 'application/json')
+ self.end_headers()
+ self.wfile.write(json.dumps({'method': method, 'result': result}));
+ return
+
+ def log_message(self, format, *args):
+ """Disable the BaseHTTPServer Log"""
+ return
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Module: NetflixHttpSubRessourceHandler
+# Created on: 07.03.2017
+
+class NetflixHttpSubRessourceHandler:
+ """ Represents the callable internal server routes & translates/executes them to requests for Netflix"""
+
+ def __init__ (self, kodi_helper, netflix_session):
+ """Sets up credentials & video_list_cache cache
+ Assigns the netflix_session/kodi_helper instacnes
+ Does the initial login if we have user data
+
+ Parameters
+ ----------
+ kodi_helper : :obj:`KodiHelper`
+ instance of the KodiHelper class
+
+ netflix_session : :obj:`NetflixSession`
+ instance of the NetflixSession class
+ """
+ self.kodi_helper = kodi_helper
+ self.netflix_session = netflix_session
+ self.credentials = self.kodi_helper.get_credentials()
+ self.video_list_cache = {}
+
+ # check if we have stored credentials, if so, do the login before the user requests it
+ # if that is done, we cache the profiles
+ if self.credentials['email'] != '' and self.credentials['password'] != '':
+ if self.netflix_session.is_logged_in(account=self.credentials):
+ self.netflix_session.refresh_session_data(account=self.credentials)
+ else:
+ self.netflix_session.login(account=self.credentials)
+ self.profiles = self.netflix_session.profiles
+ else:
+ self.profiles = []
+
+ def is_logged_in (self, params):
+ """Existing login proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`Requests.Response`
+ Response of the remote call
+ """
+ if self.credentials['email'] == '' or self.credentials['password'] == '':
+ return False
+ return self.netflix_session.is_logged_in(account=self.credentials)
+
+ def logout (self, params):
+ """Logout proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`Requests.Response`
+ Response of the remote call
+ """
+ self.profiles = []
+ self.credentials = {'email': '', 'password': ''}
+ return self.netflix_session.logout()
+
+ def login (self, params):
+ """Logout proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`Requests.Response`
+ Response of the remote call
+ """
+ email = params.get('email', [''])[0]
+ password = params.get('password', [''])[0]
+ if email != '' and password != '':
+ self.credentials = {'email': email, 'password': password}
+ _ret = self.netflix_session.login(account=self.credentials)
+ self.profiles = self.netflix_session.profiles
+ return _ret
+ return None
+
+ def list_profiles (self, params):
+ """Returns the cached list of profiles
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`dict` of :obj:`str`
+ List of profiles
+ """
+ return self.profiles
+
+ def get_esn (self, params):
+ """ESN getter function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`str`
+ Exracted ESN
+ """
+ return self.netflix_session.esn
+
+ def fetch_video_list_ids (self, params):
+ """Video list ids proxy function (caches video lists)
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`list`
+ Transformed response of the remote call
+ """
+ cached_list = self.video_list_cache.get(self.netflix_session.user_data['guid'], None)
+ if cached_list != None:
+ self.kodi_helper.log('Serving cached list for user: ' + self.netflix_session.user_data['guid'])
+ return cached_list
+ video_list_ids_raw = self.netflix_session.fetch_video_list_ids()
+ if 'error' in 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 (self, params):
+ """Video list proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`list`
+ Transformed response of the remote call
+ """
+ list_id = params.get('list_id', [''])[0]
+ raw_video_list = self.netflix_session.fetch_video_list(list_id=list_id)
+ if 'error' in raw_video_list:
+ return raw_video_list
+ # parse the video list ids
+ if 'videos' in raw_video_list.get('value', {}).keys():
+ return self.netflix_session.parse_video_list(response_data=raw_video_list)
+ return []
+
+ def fetch_episodes_by_season (self, params):
+ """Episodes for season proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`list`
+ Transformed response of the remote call
+ """
+ raw_episode_list = self.netflix_session.fetch_episodes_by_season(season_id=params.get('season_id')[0])
+ if 'error' in raw_episode_list:
+ return raw_episode_list
+ return self.netflix_session.parse_episodes_by_season(response_data=raw_episode_list)
+
+ def fetch_seasons_for_show (self, params):
+ """Season for show proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`list`
+ Transformed response of the remote call
+ """
+ show_id = params.get('show_id', [''])[0]
+ raw_season_list = self.netflix_session.fetch_seasons_for_show(id=show_id)
+ if 'error' in raw_season_list:
+ return raw_season_list
+ # check if we have sesons, announced shows that are not available yet have none
+ if 'seasons' not in raw_season_list.get('value', {}):
+ return []
+ return self.netflix_session.parse_seasons(id=show_id, response_data=raw_season_list)
+
+ def rate_video (self, params):
+ """Video rating proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`Requests.Response`
+ Response of the remote call
+ """
+ video_id = params.get('video_id', [''])[0]
+ rating = params.get('rating', [''])[0]
+ return self.netflix_session.rate_video(video_id=video_id, rating=rating)
+
+ def remove_from_list (self, params):
+ """Remove from my list proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`Requests.Response`
+ Response of the remote call
+ """
+ video_id = params.get('video_id', [''])[0]
+ return self.netflix_session.remove_from_list(video_id=video_id)
+
+ def add_to_list (self, params):
+ """Add to my list proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`Requests.Response`
+ Response of the remote call
+ """
+ video_id = params.get('video_id', [''])[0]
+ return self.netflix_session.add_to_list(video_id=video_id)
+
+ def fetch_metadata (self, params):
+ """Metadata proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`Requests.Response`
+ Response of the remote call
+ """
+ video_id = params.get('video_id', [''])[0]
+ return self.netflix_session.fetch_metadata(id=video_id)
+
+ def switch_profile (self, params):
+ """Switch profile proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`Requests.Response`
+ Response of the remote call
+ """
+ profile_id = params.get('profile_id', [''])[0]
+ return self.netflix_session.switch_profile(profile_id=profile_id, account=self.credentials)
+
+ def get_user_data (self, params):
+ """User data getter function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`str`
+ Exracted User Data
+ """
+ return self.netflix_session.user_data
+
+ def search (self, params):
+ """Search proxy function
+
+ Parameters
+ ----------
+ params : :obj:`dict` of :obj:`str`
+ Request params
+
+ Returns
+ -------
+ :obj:`list`
+ Transformed response of the remote call
+ """
+ term = params.get('term', [''])[0]
+ has_search_results = False
+ raw_search_results = self.netflix_session.fetch_search_results(search_str=term)
+ # check for any errors
+ if 'error' in raw_search_results:
+ return raw_search_results
+
+ # determine if we found something
+ if 'search' in raw_search_results['value']:
+ for key in raw_search_results['value']['search'].keys():
+ if self.netflix_session._is_size_key(key=key) == False:
+ has_search_results = raw_search_results['value']['search'][key]['titles']['length'] > 0
+ if has_search_results == False:
+ if raw_search_results['value']['search'][key].get('suggestions', False) != False:
+ for entry in raw_search_results['value']['search'][key]['suggestions']:
+ if self.netflix_session._is_size_key(key=entry) == False:
+ if raw_search_results['value']['search'][key]['suggestions'][entry]['relatedvideos']['length'] > 0:
+ has_search_results = True
+
+ # display that we haven't found a thing
+ if has_search_results == False:
+ return []
+
+ # list the search results
+ search_results = self.netflix_session.parse_search_results(response_data=raw_search_results)
+ # add more menaingful data to the search results
+ raw_search_contents = self.netflix_session.fetch_video_list_information(video_ids=search_results.keys())
+ # check for any errors
+ if 'error' in raw_search_contents:
+ return raw_search_contents
+ return self.netflix_session.parse_video_list(response_data=raw_search_contents)
import os
import json
from requests import session, cookies
-from urllib import quote
+from urllib import quote, unquote
from time import time
from base64 import urlsafe_b64encode
from bs4 import BeautifulSoup, SoupStrainer
urls = {
'login': '/login',
- 'browse': '/browse',
- 'video_list_ids': '/warmer',
+ 'browse': '/profiles/manage',
+ 'video_list_ids': '/preflight',
'shakti': '/pathEvaluator',
- 'profiles': '/browse',
+ 'profiles': '/profiles/manage',
'switch_profiles': '/profiles/switch',
'adult_pin': '/pin/service',
'metadata': '/metadata',
if response.status_code != 200:
return False
- # fetch the index page again, so that we can fetch the corresponding user data
- browse_response = self._session_get(component='browse')
- only_script_tags = SoupStrainer('script')
- browse_soup = BeautifulSoup(browse_response.text, 'html.parser', parse_only=only_script_tags)
account_hash = self._generate_account_hash(account=account)
self.user_data['guid'] = profile_id;
- self._save_data(filename=self.data_path + '_' + account_hash)
- return True
+ return self._save_data(filename=self.data_path + '_' + account_hash)
def send_adult_pin (self, pin):
"""Send the adult pin to Netflix in case an adult rated video requests it
'_': int(time()),
'authURL': self.user_data['authURL']
}
+
+ # check if we have a root lolomo for that user within our cookies
+ for cookie in self.session.cookies:
+ if cookie.name == 'lhpuuidh-browse-' + self.user_data['guid']:
+ value = unquote(cookie.value)
+ payload['lolomoid'] = value[value.rfind(':')+1:];
+
response = self._session_get(component='video_list_ids', params=payload, type='api')
return self._process_response(response=response, component=self._get_api_url_for(component='video_list_ids'))
})
params = {
- 'withSize': True,
- 'materialize': True,
'model': self.user_data['gpsModel']
}
self.esn = self._parse_esn_data(netflix_page_data=netflix_page_data)
self.api_data = self._parse_api_base_data(netflix_page_data=netflix_page_data)
self.profiles = self._parse_profile_data(netflix_page_data=netflix_page_data)
- self.log('Found ESN "' + self.esn)
+ self.log('Found ESN "' + self.esn + '"')
return netflix_page_data
<setting id="email" type="text" label="30005" default=""/>
<setting id="password" type="text" option="hidden" label="30004" default=""/>
<setting id="logout" type="action" label="30017" action="RunPlugin(plugin://plugin.video.netflix/?action=logout)" option="close"/>
+ <setting id="is_settings" type="action" label="30035" action="RunPlugin(plugin://plugin.video.netflix/?mode=openSettings&url=is)" option="close"/>
</category>
<category label="30025">
<setting id="enablelibraryfolder" type="bool" label="30026" default="false"/>
<setting id="enable_dolby_sound" type="bool" label="30033" default="true"/>
<setting id="ssl_verification" type="bool" label="30024" default="true"/>
<setting id="enable_tracking" type="bool" label="30032" default="true"/>
- <setting id="esn" type="text" label="30034" value="" default=""/>
+ <setting id="esn" type="text" label="30034" value="" default=""/>
<setting id="tracking_id" value="" visible="false"/>
<setting id="msl_service_port" value="8000" visible="false"/>
+ <setting id="netflix_service_port" value="8001" visible="false"/>
</category>
</settings>
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Module: service
+# Created on: 26.01.2017
+
import threading
import SocketServer
import xbmc
-import xbmcaddon
import socket
+from xbmcaddon import Addon
from resources.lib.KodiHelper import KodiHelper
from resources.lib.MSLHttpRequestHandler import MSLHttpRequestHandler
+from resources.lib.NetflixHttpRequestHandler import NetflixHttpRequestHandler
-addon = xbmcaddon.Addon()
-kodi_helper = KodiHelper(
- plugin_handle=None,
- base_url=None
-)
-
+# helper function to select an unused port on the host machine
def select_unused_port():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 0))
sock.close()
return port
-port = select_unused_port()
-addon.setSetting('msl_service_port', str(port))
-kodi_helper.log(msg='Picked Port: ' + str(port))
+addon = Addon()
+kodi_helper = KodiHelper()
+
+# pick & store a port for the MSL service
+msl_port = select_unused_port()
+addon.setSetting('msl_service_port', str(msl_port))
+kodi_helper.log(msg='[MSL] Picked Port: ' + str(msl_port))
-#Config the HTTP Server
+# pick & store a port for the internal Netflix HTTP proxy service
+ns_port = select_unused_port()
+addon.setSetting('netflix_service_port', str(ns_port))
+kodi_helper.log(msg='[NS] Picked Port: ' + str(ns_port))
+
+# server defaults
SocketServer.TCPServer.allow_reuse_address = True
-server = SocketServer.TCPServer(('127.0.0.1', port), MSLHttpRequestHandler)
-server.server_activate()
-server.timeout = 1
+
+# configure the MSL Server
+msl_server = SocketServer.TCPServer(('127.0.0.1', msl_port), MSLHttpRequestHandler)
+msl_server.server_activate()
+msl_server.timeout = 1
+
+# configure the Netflix Data Server
+nd_server = SocketServer.TCPServer(('127.0.0.1', ns_port), NetflixHttpRequestHandler)
+nd_server.server_activate()
+nd_server.timeout = 1
if __name__ == '__main__':
monitor = xbmc.Monitor()
- thread = threading.Thread(target=server.serve_forever)
- thread.daemon = True
- thread.start()
+ # start thread for MLS servie
+ msl_thread = threading.Thread(target=msl_server.serve_forever)
+ msl_thread.daemon = True
+ msl_thread.start()
+
+ # start thread for Netflix HTTP service
+ nd_thread = threading.Thread(target=nd_server.serve_forever)
+ nd_thread.daemon = True
+ nd_thread.start()
+
+ # kill the services if kodi monitor tells us to
while not monitor.abortRequested():
if monitor.waitForAbort(5):
- server.shutdown()
+ msl_server.shutdown()
+ nd_server.shutdown()
break
- server.server_close()
- server.socket.close()
- server.shutdown()
+ # MSL service shutdown sequence
+ msl_server.server_close()
+ msl_server.socket.close()
+ msl_server.shutdown()
kodi_helper.log(msg='Stopped MSL Service')
+
+ # Netflix service shutdown sequence
+ nd_server.server_close()
+ nd_server.socket.close()
+ nd_server.shutdown()
+ kodi_helper.log(msg='Stopped HTTP Service')