Skip to content

Commit

Permalink
Implemented numeric mask input
Browse files Browse the repository at this point in the history
  • Loading branch information
CastagnaIT committed Apr 16, 2020
1 parent 290695c commit 3eb915c
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 59 deletions.
10 changes: 8 additions & 2 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ msgctxt "#30005"
msgid "E-mail"
msgstr ""

# Unused 30006
msgctxt "#30006"
msgid "Enter PIN to access this profile"
msgstr ""

msgctxt "#30007"
msgid "Parental Control PIN (4 digits)"
Expand Down Expand Up @@ -403,7 +405,7 @@ msgid "Netflix returned the following error:"
msgstr ""

msgctxt "#30103"
msgid "Error details have been written to the Kodi logfile. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, please report it via GitHub. Please provide a full debug log with your error report (enable \"Debug Logging\" in Kodi settings)."
msgid "Error details have been written to the Kodi log file. If the error message does not provide guidance on how to resolve the issue or you continue to receive this error, make a report by following the ReadMe instructions on GitHub."
msgstr ""

msgctxt "#30104"
Expand Down Expand Up @@ -945,3 +947,7 @@ msgstr ""
msgctxt "#30239"
msgid "Color of supplemental plot info"
msgstr ""

msgctxt "#30240"
msgid "The background services cannot be started due to this problem:\r\n{}"
msgstr ""
15 changes: 15 additions & 0 deletions resources/lib/api/api_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,18 @@ def verify_pin(pin):
).get('success', False)
except Exception: # pylint: disable=broad-except
return False


@common.time_execution(immediate=False)
def verify_profile_lock(guid, pin):
"""Send profile PIN to Netflix and verify it."""
try:
return common.make_call(
'post',
{'component': 'profile_lock',
'data': {'pin': pin,
'action': 'verify',
'guid': guid}}
).get('success', False)
except Exception: # pylint: disable=broad-except
return False
12 changes: 7 additions & 5 deletions resources/lib/kodi/ui/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,14 @@ def ask_for_rating():
return None


def ask_for_pin():
def ask_for_pin(message):
"""Ask the user for the adult pin"""
return xbmcgui.Dialog().numeric(
heading=common.get_local_string(30002),
type=0,
defaultt='') or None
args = {'heading': message,
'type': 0,
'defaultt': ''}
if not g.KODI_VERSION.is_major_ver('18'): # Kodi => 19.x support mask input
args['bHiddenInput'] = True
return xbmcgui.Dialog().numeric(**args) or None


def ask_for_search_term():
Expand Down
13 changes: 10 additions & 3 deletions resources/lib/navigation/directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from resources.lib.database.db_utils import TABLE_MENU_DATA
from resources.lib.globals import g
from resources.lib.navigation.directory_utils import (finalize_directory, convert_list, custom_viewmode,
end_of_directory, get_title)
end_of_directory, get_title, verify_profile_pin)

# What means dynamic menus (and dynamic id):
# Are considered dynamic menus all menus which context name do not exists in the 'lolomo_contexts' of
Expand Down Expand Up @@ -49,8 +49,6 @@ def __init__(self, params):
if self.perpetual_range_start == '0':
# For cache identifier purpose
self.perpetual_range_start = None
if 'switch_profile_guid' in params:
api.activate_profile(params['switch_profile_guid'])

def root(self, pathitems=None): # pylint: disable=unused-argument
"""Show profiles or home listing when profile auto-selection is enabled"""
Expand Down Expand Up @@ -95,6 +93,15 @@ def profiles(self, pathitems=None): # pylint: disable=unused-argument
@custom_viewmode(g.VIEW_MAINMENU)
def home(self, pathitems=None, cache_to_disc=True): # pylint: disable=unused-argument
"""Show home listing"""
if 'switch_profile_guid' in self.params:
# This is executed only when you have selected a profile from the profile list
pin_result = verify_profile_pin(self.params.get('switch_profile_guid'))
if not pin_result:
if pin_result is not None:
ui.show_notification(common.get_local_string(30106), time=8000)
xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False)
return
api.activate_profile(self.params['switch_profile_guid'])
common.debug('Showing home listing')
list_data, extra_data = common.make_call('get_mainmenu') # pylint: disable=unused-variable

Expand Down
10 changes: 10 additions & 0 deletions resources/lib/navigation/directory_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
import xbmcplugin

import resources.lib.common as common
from resources.lib.api.api_requests import verify_profile_lock
from resources.lib.database.db_utils import TABLE_MENU_DATA
from resources.lib.globals import g
from resources.lib.kodi.ui import ask_for_pin


def custom_viewmode(partial_setting_id):
Expand Down Expand Up @@ -123,3 +125,11 @@ def get_title(menu_data, extra_data):
g.LOCAL_DB.get_value(menu_data['path'][1],
{},
table=TABLE_MENU_DATA).get('title', '')))


def verify_profile_pin(guid):
"""Verify if the profile is locked by a PIN and ask the PIN"""
if not g.LOCAL_DB.get_profile_config('isPinLocked', False, guid=guid):
return True
pin = ask_for_pin(common.get_local_string(30006))
return None if not pin else verify_profile_lock(guid, pin)
4 changes: 2 additions & 2 deletions resources/lib/navigation/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def play(videoid):
pin_result = _verify_pin(metadata[0].get('requiresPin', False))
if not pin_result:
if pin_result is not None:
ui.show_notification(common.get_local_string(30106), time=10000)
ui.show_notification(common.get_local_string(30106), time=8000)
xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False)
return

Expand Down Expand Up @@ -188,7 +188,7 @@ def get_inputstream_listitem(videoid):
def _verify_pin(pin_required):
if not pin_required:
return True
pin = ui.ask_for_pin()
pin = ui.ask_for_pin(common.get_local_string(30002))
return None if not pin else api.verify_pin(pin)


Expand Down
71 changes: 44 additions & 27 deletions resources/lib/run_addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@
from resources.lib.globals import g
from resources.lib.common import (info, debug, warn, error, check_credentials, BackendNotReady,
log_time_trace, reset_log_level_global_var,
get_current_kodi_profile_name)
get_current_kodi_profile_name, get_local_string)
from resources.lib.upgrade_controller import check_addon_upgrade


def _handle_endofdirectory(succeeded=False):
from xbmcplugin import endOfDirectory
endOfDirectory(handle=g.PLUGIN_HANDLE, succeeded=succeeded)


def lazy_login(func):
"""
Decorator to ensure that a valid login is present when calling a method
Expand Down Expand Up @@ -67,6 +72,23 @@ def route(pathitems):
_execute(nav_handler, pathitems[1:], g.REQUEST_PARAMS)


def _get_nav_handler(root_handler):
nav_handler = None
if root_handler == g.MODE_DIRECTORY:
from resources.lib.navigation.directory import DirectoryBuilder
nav_handler = DirectoryBuilder
if root_handler == g.MODE_ACTION:
from resources.lib.navigation.actions import AddonActionExecutor
nav_handler = AddonActionExecutor
if root_handler == g.MODE_LIBRARY:
from resources.lib.navigation.library import LibraryActionExecutor
nav_handler = LibraryActionExecutor
if root_handler == g.MODE_HUB:
from resources.lib.navigation.hub import HubBrowser
nav_handler = HubBrowser
return nav_handler


def _execute(executor_type, pathitems, params):
"""Execute an action as specified by the path"""
try:
Expand All @@ -78,6 +100,15 @@ def _execute(executor_type, pathitems, params):
executor(pathitems=pathitems)


def _get_service_status(window_cls, prop_nf_service_status):
from json import loads
try:
status = window_cls.getProperty(prop_nf_service_status)
return loads(status) if status else {}
except Exception: # pylint: disable=broad-except
return {}


def _check_addon_external_call(window_cls, prop_nf_service_status):
"""Check system to verify if the calls to the add-on are originated externally"""
# The calls that are made from outside do not respect and do not check whether the services required
Expand Down Expand Up @@ -107,7 +138,7 @@ def _check_addon_external_call(window_cls, prop_nf_service_status):
if is_other_plugin_name or not getCondVisibility("Window.IsMedia"):
monitor = Monitor()
sec_elapsed = 0
while not window_cls.getProperty(prop_nf_service_status) == 'running':
while not _get_service_status(window_cls, prop_nf_service_status).get('status') == 'running':
if sec_elapsed >= limit_sec or monitor.abortRequested() or monitor.waitForAbort(0.5):
break
sec_elapsed += 0.5
Expand All @@ -117,23 +148,6 @@ def _check_addon_external_call(window_cls, prop_nf_service_status):
return False


def _get_nav_handler(root_handler):
nav_handler = None
if root_handler == g.MODE_DIRECTORY:
from resources.lib.navigation.directory import DirectoryBuilder
nav_handler = DirectoryBuilder
if root_handler == g.MODE_ACTION:
from resources.lib.navigation.actions import AddonActionExecutor
nav_handler = AddonActionExecutor
if root_handler == g.MODE_LIBRARY:
from resources.lib.navigation.library import LibraryActionExecutor
nav_handler = LibraryActionExecutor
if root_handler == g.MODE_HUB:
from resources.lib.navigation.hub import HubBrowser
nav_handler = HubBrowser
return nav_handler


def _check_valid_credentials():
"""Check that credentials are valid otherwise request user credentials"""
# This function check only if credentials exist, instead lazy_login
Expand All @@ -151,11 +165,6 @@ def _check_valid_credentials():
return True


def _handle_endofdirectory(succeeded=False):
from xbmcplugin import endOfDirectory
endOfDirectory(handle=g.PLUGIN_HANDLE, succeeded=succeeded)


def run(argv):
# pylint: disable=broad-except,ungrouped-imports
# Initialize globals right away to avoid stale values from the last addon invocation.
Expand All @@ -173,11 +182,19 @@ def run(argv):
# If you use multiple Kodi profiles you need to distinguish the property of current profile
prop_nf_service_status = g.py2_encode('nf_service_status_' + get_current_kodi_profile_name())
is_external_call = _check_addon_external_call(window_cls, prop_nf_service_status)
service_status = _get_service_status(window_cls, prop_nf_service_status)

if window_cls.getProperty(prop_nf_service_status) != 'running':
if service_status.get('status') != 'running':
if not is_external_call:
from resources.lib.kodi.ui import show_backend_not_ready
show_backend_not_ready()
if service_status.get('status') == 'error':
# The services are not started due to an error exception
from resources.lib.kodi.ui import show_error_info
show_error_info(get_local_string(30105), get_local_string(30240).format(service_status.get('message')),
False, False)
else:
# The services are not started yet
from resources.lib.kodi.ui import show_backend_not_ready
show_backend_not_ready()
success = False

if success:
Expand Down
42 changes: 34 additions & 8 deletions resources/lib/run_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from __future__ import absolute_import, division, unicode_literals

import threading
from socket import gaierror

from xbmcgui import Window

Expand Down Expand Up @@ -52,21 +53,38 @@ class NetflixService(object):
'thread': None
}
]
HOST_ADDRESS = '127.0.0.1'

def __init__(self):
self.window_cls = Window(10000) # Kodi home window
# If you use multiple Kodi profiles you need to distinguish the property of current profile
self.prop_nf_service_status = g.py2_encode('nf_service_status_' + get_current_kodi_profile_name())
for server in self.SERVERS:
self.init_server(server)
self.controller = None
self.library_updater = None
self.settings_monitor = None

def init_server(self, server):
def init_servers(self):
"""Initialize the http servers"""
try:
for server in self.SERVERS:
self._init_server(server)
return True
except Exception as exc: # pylint: disable=broad-except
error('Background services do not start due to the following error')
import traceback
error(g.py2_decode(traceback.format_exc(), 'latin-1'))
if isinstance(exc, gaierror):
message = ('Something is wrong in your network localhost configuration.\r\n'
'It is possible that the hostname {} can not be resolved.').format(self.HOST_ADDRESS)
else:
message = unicode(exc)
self._set_service_status('error', message)
return False

def _init_server(self, server):
server['class'].allow_reuse_address = True
server['instance'] = server['class'](
('127.0.0.1', select_port(server['name']))
(self.HOST_ADDRESS, select_port(server['name']))
)
server['thread'] = threading.Thread(target=server['instance'].serve_forever)

Expand All @@ -86,7 +104,7 @@ def start_services(self):
self.library_updater = LibraryUpdateService()
self.settings_monitor = SettingsMonitor()
# Mark the service as active
self.window_cls.setProperty(self.prop_nf_service_status, 'running')
self._set_service_status('running')
if not g.ADDON.getSettingBool('disable_startup_notification'):
from resources.lib.kodi.ui import show_notification
show_notification(get_local_string(30110))
Expand All @@ -95,7 +113,7 @@ def shutdown(self):
"""
Stop the background services
"""
self.window_cls.setProperty(self.prop_nf_service_status, 'stopped')
self._set_service_status('stopped')
for server in self.SERVERS:
server['instance'].shutdown()
server['instance'].server_close()
Expand All @@ -109,7 +127,7 @@ def run(self):
try:
self.start_services()
except Exception as exc: # pylint: disable=broad-except
self.window_cls.setProperty(self.prop_nf_service_status, 'stopped')
self._set_service_status('stopped')
import traceback
from resources.lib.kodi.ui import show_addon_error_info
error(g.py2_decode(traceback.format_exc(), 'latin-1'))
Expand All @@ -133,11 +151,19 @@ def _tick_and_wait_for_abort(self):
show_notification(': '.join((exc.__class__.__name__, unicode(exc))))
return self.controller.waitForAbort(1)

def _set_service_status(self, status, message=None):
"""Save the service status to a Kodi property"""
from json import dumps
status = {'status': status, 'message': message}
self.window_cls.setProperty(self.prop_nf_service_status, dumps(status))


def run(argv):
# Initialize globals right away to avoid stale values from the last addon invocation.
# Otherwise Kodi's reuseLanguageInvoker will cause some really quirky behavior!
# PR: https://github.com/xbmc/xbmc/pull/13814
g.init_globals(argv)
check_service_upgrade()
NetflixService().run()
netflix_service = NetflixService()
if netflix_service.init_servers():
netflix_service.run()
3 changes: 2 additions & 1 deletion resources/lib/services/nfsession/nfsession_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
'profiles': {'endpoint': '/profiles/manage', 'is_api_call': False},
'switch_profile': {'endpoint': '/SwitchProfile', 'is_api_call': False},
'activate_profile': {'endpoint': '/profiles/switch', 'is_api_call': True},
'profile_lock': {'endpoint': '/profileLock', 'is_api_call': True},
'pin': {'endpoint': '/pin', 'is_api_call': False},
'pin_reset': {'endpoint': '/pin/reset', 'is_api_call': True},
'pin_service': {'endpoint': '/pin/service', 'is_api_call': True},
Expand Down Expand Up @@ -154,7 +155,7 @@ def _prepare_request_properties(self, component, kwargs):
data = kwargs.get('data', {})
headers = kwargs.get('headers', {})
params = kwargs.get('params', {})
if component in ['set_video_rating', 'set_thumb_rating', 'update_my_list', 'pin_service']:
if component in ['set_video_rating', 'set_thumb_rating', 'update_my_list', 'pin_service', 'profile_lock']:
headers.update({
'Content-Type': 'application/json',
'Accept': 'application/json, text/javascript, */*'})
Expand Down

0 comments on commit 3eb915c

Please sign in to comment.