From bc82711886f3c682e6d817f7dccbb120e6318dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Fri, 21 Jul 2023 22:54:53 +0200 Subject: [PATCH] Add support for proxies (#126) * Add support for proxies --- requirements.txt | 2 + resources/lib/kodiutils.py | 62 +++++++++++++++++++ .../lib/viervijfzes/aws/cognito_identity.py | 2 - resources/lib/viervijfzes/aws/cognito_idp.py | 1 - resources/lib/viervijfzes/content.py | 19 +++--- resources/lib/viervijfzes/epg.py | 6 +- resources/lib/viervijfzes/search.py | 6 +- 7 files changed, 84 insertions(+), 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index d0448ee..45634bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,9 @@ pytest pytest-cov pytest-timeout python-dateutil +pysocks requests git+https://github.com/tamland/kodi-plugin-routing@master#egg=routing six sakee +win-inet-pton; platform_system=="Windows" \ No newline at end of file diff --git a/resources/lib/kodiutils.py b/resources/lib/kodiutils.py index bdf7e69..564de8e 100644 --- a/resources/lib/kodiutils.py +++ b/resources/lib/kodiutils.py @@ -17,6 +17,7 @@ try: # Python 3 from html import unescape except ImportError: # Python 2 from HTMLParser import HTMLParser + unescape = HTMLParser().unescape ADDON = xbmcaddon.Addon() @@ -478,6 +479,67 @@ def set_global_setting(key, value): return jsonrpc(method='Settings.SetSettingValue', params={'setting': key, 'value': value}) +def has_socks(): + """Test if socks is installed, and use a static variable to remember""" + if hasattr(has_socks, 'cached'): + return getattr(has_socks, 'cached') + try: + import socks # noqa: F401; pylint: disable=unused-variable,unused-import + except ImportError: + has_socks.cached = False + return None # Detect if this is the first run + has_socks.cached = True + return True + + +def get_proxies(): + """Return a usable proxies dictionary from Kodi proxy settings""" + # Use proxy settings from environment variables + env_http_proxy = os.environ.get('HTTP_PROXY') + env_https_proxy = os.environ.get('HTTPS_PROXY') + if env_http_proxy: + return {'http': env_http_proxy, 'https': env_https_proxy or env_http_proxy} + + usehttpproxy = get_global_setting('network.usehttpproxy') + if usehttpproxy is not True: + return None + + try: + httpproxytype = int(get_global_setting('network.httpproxytype')) + except ValueError: + httpproxytype = 0 + + socks_supported = has_socks() + if httpproxytype != 0 and not socks_supported: + # Only open the dialog the first time (to avoid multiple popups) + if socks_supported is None: + ok_dialog('', localize(30966)) # Requires PySocks + return None + + proxy_types = ['http', 'socks4', 'socks4a', 'socks5', 'socks5h'] + + proxy = { + 'scheme': proxy_types[httpproxytype] if 0 <= httpproxytype < 5 else 'http', + 'server': get_global_setting('network.httpproxyserver'), + 'port': get_global_setting('network.httpproxyport'), + 'username': get_global_setting('network.httpproxyusername'), + 'password': get_global_setting('network.httpproxypassword') + } + + if proxy.get('username') and proxy.get('password') and proxy.get('server') and proxy.get('port'): + proxy_address = '{scheme}://{username}:{password}@{server}:{port}'.format(**proxy) + elif proxy.get('username') and proxy.get('server') and proxy.get('port'): + proxy_address = '{scheme}://{username}@{server}:{port}'.format(**proxy) + elif proxy.get('server') and proxy.get('port'): + proxy_address = '{scheme}://{server}:{port}'.format(**proxy) + elif proxy.get('server'): + proxy_address = '{scheme}://{server}'.format(**proxy) + else: + return None + + return {'http': proxy_address, 'https': proxy_address} + + def get_cond_visibility(condition): """Test a condition in XBMC""" return xbmc.getCondVisibility(condition) diff --git a/resources/lib/viervijfzes/aws/cognito_identity.py b/resources/lib/viervijfzes/aws/cognito_identity.py index 49fcb85..b99713b 100644 --- a/resources/lib/viervijfzes/aws/cognito_identity.py +++ b/resources/lib/viervijfzes/aws/cognito_identity.py @@ -44,7 +44,6 @@ class CognitoIdentity: 'x-amz-target': 'AWSCognitoIdentityService.GetId', 'content-type': 'application/x-amz-json-1.1', }) - _LOGGER.debug(response.text) result = json.loads(response.text) @@ -64,7 +63,6 @@ class CognitoIdentity: 'x-amz-target': 'AWSCognitoIdentityService.GetCredentialsForIdentity', 'content-type': 'application/x-amz-json-1.1', }) - _LOGGER.debug(response.text) result = json.loads(response.text) diff --git a/resources/lib/viervijfzes/aws/cognito_idp.py b/resources/lib/viervijfzes/aws/cognito_idp.py index c9e87ce..aeb9db9 100644 --- a/resources/lib/viervijfzes/aws/cognito_idp.py +++ b/resources/lib/viervijfzes/aws/cognito_idp.py @@ -77,7 +77,6 @@ class CognitoIdp: self.k = self.__hex_to_long(self.__hex_hash('00' + self.n_hex + '0' + self.g_hex)) # pylint: disable=invalid-name self.small_a_value = self.__generate_random_small_a() self.large_a_value = self.__calculate_a() - _LOGGER.debug("Created %s", self) def authenticate(self, username, password): """ Authenticate with a username and password. """ diff --git a/resources/lib/viervijfzes/content.py b/resources/lib/viervijfzes/content.py index 7c0c2f7..219278c 100644 --- a/resources/lib/viervijfzes/content.py +++ b/resources/lib/viervijfzes/content.py @@ -13,6 +13,7 @@ from datetime import datetime import requests +from resources.lib import kodiutils from resources.lib.kodiutils import STREAM_DASH, STREAM_HLS, html_to_kodi from resources.lib.viervijfzes import ResolvedStream @@ -29,6 +30,8 @@ CACHE_AUTO = 1 # Allow to use the cache, and query the API if no cache is avail CACHE_ONLY = 2 # Only use the cache, don't use the API CACHE_PREVENT = 3 # Don't use the cache +PROXIES = kodiutils.get_proxies() + class UnavailableException(Exception): """ Is thrown when an item is unavailable. """ @@ -396,7 +399,8 @@ class ContentApi: # No manifest url found, get manifest from Server-Side Ad Insertion service if data.get('adType') == 'SSAI' and data.get('ssai'): - url = 'https://pubads.g.doubleclick.net/ondemand/dash/content/%s/vid/%s/streams' % (data.get('ssai').get('contentSourceID'), data.get('ssai').get('videoID')) + url = 'https://pubads.g.doubleclick.net/ondemand/dash/content/%s/vid/%s/streams' % ( + data.get('ssai').get('contentSourceID'), data.get('ssai').get('videoID')) ad_data = json.loads(self._post_url(url, data='')) # Server-Side Ad Insertion DASH stream @@ -409,7 +413,6 @@ class ContentApi: raise UnavailableException - def get_program_tree(self, cache=CACHE_AUTO): """ Get a content tree with information about all the programs. :type cache: str @@ -772,9 +775,9 @@ class ContentApi: if authentication: response = self._session.get(url, params=params, headers={ 'authorization': authentication, - }) + }, proxies=PROXIES) else: - response = self._session.get(url, params=params) + response = self._session.get(url, params=params, proxies=PROXIES) if response.status_code != 200: _LOGGER.error(response.text) @@ -791,9 +794,9 @@ class ContentApi: if authentication: response = self._session.post(url, params=params, json=data, headers={ 'authorization': authentication, - }) + }, proxies=PROXIES) else: - response = self._session.post(url, params=params, json=data) + response = self._session.post(url, params=params, json=data, proxies=PROXIES) if response.status_code not in (200, 201): _LOGGER.error(response.text) @@ -810,9 +813,9 @@ class ContentApi: if authentication: response = self._session.delete(url, params=params, headers={ 'authorization': authentication, - }) + }, proxies=PROXIES) else: - response = self._session.delete(url, params=params) + response = self._session.delete(url, params=params, proxies=PROXIES) if response.status_code != 200: _LOGGER.error(response.text) diff --git a/resources/lib/viervijfzes/epg.py b/resources/lib/viervijfzes/epg.py index 6e616b1..6174adc 100644 --- a/resources/lib/viervijfzes/epg.py +++ b/resources/lib/viervijfzes/epg.py @@ -11,6 +11,8 @@ import dateutil.parser import dateutil.tz import requests +from resources.lib import kodiutils + _LOGGER = logging.getLogger(__name__) GENRE_MAPPING = { @@ -31,6 +33,8 @@ GENRE_MAPPING = { 'Voetbal': 0x43, } +PROXIES = kodiutils.get_proxies() + class EpgProgram: """ Defines a Program in the EPG. """ @@ -177,7 +181,7 @@ class EpgApi: :type url: str :rtype str """ - response = self._session.get(url) + response = self._session.get(url, proxies=PROXIES) if response.status_code != 200: raise Exception('Could not fetch data') diff --git a/resources/lib/viervijfzes/search.py b/resources/lib/viervijfzes/search.py index 15f401a..ed46b29 100644 --- a/resources/lib/viervijfzes/search.py +++ b/resources/lib/viervijfzes/search.py @@ -13,6 +13,8 @@ from resources.lib.viervijfzes.content import CACHE_ONLY, ContentApi, Program _LOGGER = logging.getLogger(__name__) +PROXIES = kodiutils.get_proxies() + class SearchApi: """ GoPlay Search API """ @@ -37,9 +39,9 @@ class SearchApi: "query": query, "page": 0, "mode": "programs" - } + }, + proxies=PROXIES ) - _LOGGER.debug(response.content) response.raise_for_status() data = json.loads(response.text)