diff --git a/README.md b/README.md
index 1e6fc1a8..103089cd 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ you can send a message to [our Facebook page](https://www.facebook.com/kodivrtnu
## Releases
#### v1.7.0 (2019-03-23)
+- Add full proxy support (@dagwieers)
- Indicate when content will disappear in the next 3 months (@dagwieers)
- Indicate when content is geo-blocked (@dagwieers)
- Add fanart to menus (@dagwieers)
diff --git a/plugin.video.vrt.nu/addon.xml b/plugin.video.vrt.nu/addon.xml
index dc5e0c83..8ad30d28 100644
--- a/plugin.video.vrt.nu/addon.xml
+++ b/plugin.video.vrt.nu/addon.xml
@@ -26,6 +26,7 @@
GNU General Public License, v3
v1.7.0 (2019-03-23)
+- Add full proxy support (@dagwieers)
- Indicate when content will disappear in the next 3 months (@dagwieers)
- Indicate when content is geo-blocked (@dagwieers)
- Add fanart to menus (@dagwieers)
diff --git a/plugin.video.vrt.nu/resources/language/resource.language.en_gb/strings.po b/plugin.video.vrt.nu/resources/language/resource.language.en_gb/strings.po
index cdac3063..a2c6031f 100644
--- a/plugin.video.vrt.nu/resources/language/resource.language.en_gb/strings.po
+++ b/plugin.video.vrt.nu/resources/language/resource.language.en_gb/strings.po
@@ -63,6 +63,14 @@ msgctxt "#32054"
msgid "Woops something went wrong, check the log for more details"
msgstr "Woops something went wrong, check the log for more details"
+msgctxt "#32061"
+msgid "SOCKS proxies are currently unsupported"
+msgstr "SOCKS proxies are currently unsupported"
+
+msgctxt "#32062"
+msgid "Using a SOCKS proxy requires the requests[socks] library installed."
+msgstr "Using a SOCKS proxy requires the requests[socks] library installed."
+
msgctxt "#32080"
msgid "A-Z"
msgstr "A-Z"
diff --git a/plugin.video.vrt.nu/resources/language/resource.language.nl_nl/strings.po b/plugin.video.vrt.nu/resources/language/resource.language.nl_nl/strings.po
index 5d6b7100..8dd39adc 100644
--- a/plugin.video.vrt.nu/resources/language/resource.language.nl_nl/strings.po
+++ b/plugin.video.vrt.nu/resources/language/resource.language.nl_nl/strings.po
@@ -64,6 +64,14 @@ msgctxt "#32054"
msgid "Woops something went wrong, check the log for more details"
msgstr "Oeps, er ging iets mis, check de log voor meer informatie"
+msgctxt "#32061"
+msgid "SOCKS proxies are currently unsupported"
+msgstr "SOCKS proxies zijn momenteel niet ondersteund"
+
+msgctxt "#32062"
+msgid "Using a SOCKS proxy requires the requests[socks] library installed."
+msgstr "Het gebruik van SOCKS proxies vereist dat de requests[socks] library geïnstalleerd is."
+
msgctxt "#32080"
msgid "A-Z"
msgstr "A-Z"
diff --git a/plugin.video.vrt.nu/resources/lib/kodiwrappers/kodiwrapper.py b/plugin.video.vrt.nu/resources/lib/kodiwrappers/kodiwrapper.py
index 0c17bf13..633a3dfc 100644
--- a/plugin.video.vrt.nu/resources/lib/kodiwrappers/kodiwrapper.py
+++ b/plugin.video.vrt.nu/resources/lib/kodiwrappers/kodiwrapper.py
@@ -24,6 +24,7 @@ def __init__(self, handle, url, addon):
self._handle = handle
self._url = url
self._addon = addon
+ self._addon_id = addon.getAddonInfo('id')
def show_listing(self, list_items, sort=None, content_type='episodes'):
listing = []
@@ -91,6 +92,53 @@ def get_setting(self, setting_id):
def open_settings(self):
self._addon.openSettings()
+ def get_global_setting(self, setting):
+ json_result = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Settings.GetSettingValue", "params": {"setting": "%s"}, "id": 1}' % setting)
+ return json.loads(json_result)['result']['value']
+
+ def get_proxies(self):
+ usehttpproxy = self.get_global_setting('network.usehttpproxy')
+ if usehttpproxy is False:
+ return dict()
+
+ httpproxytype = self.get_global_setting('network.httpproxytype')
+
+ if httpproxytype != 0:
+ title = self.get_localized_string(32061)
+ message = self.get_localized_string(32062)
+ self.show_ok_dialog(title, message)
+
+ if httpproxytype == 0:
+ httpproxyscheme = 'http'
+ elif httpproxytype == 1:
+ httpproxyscheme = 'socks4'
+ elif httpproxytype == 2:
+ httpproxyscheme = 'socks4a'
+ elif httpproxytype == 3:
+ httpproxyscheme = 'socks5'
+ elif httpproxytype == 4:
+ httpproxyscheme = 'socks5h'
+ else:
+ httpproxyscheme = 'http'
+
+ httpproxyserver = self.get_global_setting('network.httpproxyserver')
+ httpproxyport = self.get_global_setting('network.httpproxyport')
+ httpproxyusername = self.get_global_setting('network.httpproxyusername')
+ httpproxypassword = self.get_global_setting('network.httpproxypassword')
+
+ if httpproxyserver and httpproxyport and httpproxyusername and httpproxypassword:
+ proxy_address = '%s://%s:%s@%s:%s' % (httpproxyscheme, httpproxyusername, httpproxypassword, httpproxyserver, httpproxyport)
+ elif httpproxyserver and httpproxyport and httpproxyusername:
+ proxy_address = '%s://%s@%s:%s' % (httpproxyscheme, httpproxyusername, httpproxyserver, httpproxyport)
+ elif httpproxyserver and httpproxyport:
+ proxy_address = '%s://%s:%s' % (httpproxyscheme, httpproxyserver, httpproxyport)
+ elif httpproxyserver:
+ proxy_address = '%s://%s' % (httpproxyscheme, httpproxyserver)
+ else:
+ return dict()
+
+ return dict(http=proxy_address, https=proxy_address)
+
# NOTE: normally inputstream adaptive will always be installed, this only applies for people uninstalling inputstream adaptive while this addon is disabled
def has_inputstream_adaptive_installed(self):
return xbmc.getCondVisibility('System.HasAddon("{0}")'.format('inputstream.adaptive')) == 1
@@ -121,7 +169,9 @@ def delete_path(self, path):
return xbmcvfs.delete(path)
def log_notice(self, message):
- xbmc.log(message, xbmc.LOGNOTICE)
+ ''' Log info messages to Kodi '''
+ xbmc.log(msg='[%s] %s' % (self._addon_id, message), level=xbmc.LOGNOTICE)
def log_error(self, message):
- xbmc.log(message, xbmc.LOGERROR)
+ ''' Log error messages to Kodi '''
+ xbmc.log(msg='[%s] %s' % (self._addon_id, message), level=xbmc.LOGERROR)
diff --git a/plugin.video.vrt.nu/resources/lib/vrtplayer/streamservice.py b/plugin.video.vrt.nu/resources/lib/vrtplayer/streamservice.py
index a6dc3418..effc5a55 100644
--- a/plugin.video.vrt.nu/resources/lib/vrtplayer/streamservice.py
+++ b/plugin.video.vrt.nu/resources/lib/vrtplayer/streamservice.py
@@ -18,6 +18,7 @@ class StreamService:
def __init__(self, vrt_base, vrtnu_base_url, kodi_wrapper, token_resolver):
self._kodi_wrapper = kodi_wrapper
+ self._proxies = self._kodi_wrapper.get_proxies()
self.token_resolver = token_resolver
self._vrt_base = vrt_base
self._vrtnu_base_url = vrtnu_base_url
@@ -26,7 +27,7 @@ def __init__(self, vrt_base, vrtnu_base_url, kodi_wrapper, token_resolver):
self._license_url = self._get_license_url()
def _get_license_url(self):
- return requests.get(self._VUPLAY_API_URL).json()['drm_providers']['widevine']['la_url']
+ return requests.get(self._VUPLAY_API_URL, proxies=self._proxies).json()['drm_providers']['widevine']['la_url']
def _create_settings_dir(self):
settingsdir = self._kodi_wrapper.get_userdata_path()
@@ -74,7 +75,7 @@ def _get_license_key(self, key_url, key_type='R', key_headers=None, key_value=No
return ''.join((key_url, '|', header.strip('&'), '|', key_value, '|'))
def _get_api_data(self, video_url):
- html_page = requests.get(video_url).text
+ html_page = requests.get(video_url, proxies=self._proxies).text
strainer = SoupStrainer('div', {'class': 'cq-dd-vrtvideo'})
soup = BeautifulSoup(html_page, 'html.parser', parse_only=strainer)
video_data = soup.find(lambda tag: tag.name == 'div' and tag.get('class') == ['vrtvideo']).attrs
@@ -105,7 +106,7 @@ def _get_video_json(self, api_data):
# Construct api_url and get video json
api_url = api_data.media_api_url + '/videos/' + api_data.publication_id + \
api_data.video_id + '?vrtPlayerToken=' + playertoken + '&client=' + api_data.client
- video_json = requests.get(api_url).json()
+ video_json = requests.get(api_url, proxies=self._proxies).json()
return video_json
@@ -187,7 +188,7 @@ def _select_stream(self, stream_dict, vudrm_token):
# Speed up HLS selection, workaround for slower kodi selection
def _select_hls_substreams(self, master_hls_url):
base_url = master_hls_url.split('.m3u8')[0]
- m3u8 = requests.get(master_hls_url).text
+ m3u8 = requests.get(master_hls_url, proxies=self._proxies).text
direct_audio_url = None
direct_video_url = None
direct_subtitle_url = None
diff --git a/plugin.video.vrt.nu/resources/lib/vrtplayer/tokenresolver.py b/plugin.video.vrt.nu/resources/lib/vrtplayer/tokenresolver.py
index 3dc4f5fd..4756e5bc 100644
--- a/plugin.video.vrt.nu/resources/lib/vrtplayer/tokenresolver.py
+++ b/plugin.video.vrt.nu/resources/lib/vrtplayer/tokenresolver.py
@@ -21,6 +21,7 @@ class TokenResolver:
def __init__(self, kodi_wrapper):
self._kodi_wrapper = kodi_wrapper
+ self._proxies = self._kodi_wrapper.get_proxies()
def get_ondemand_playertoken(self, token_url, xvrttoken):
token_path = self._kodi_wrapper.get_userdata_path() + self._ONDEMAND_COOKIE
@@ -29,7 +30,7 @@ def get_ondemand_playertoken(self, token_url, xvrttoken):
if token is None:
cookie_value = 'X-VRT-Token=' + xvrttoken
headers = {'Content-Type': 'application/json', 'Cookie': cookie_value}
- token = TokenResolver._get_new_playertoken(token_path, token_url, headers)
+ token = self._get_new_playertoken(token_path, token_url, headers)
return token
def get_live_playertoken(self, token_url, xvrttoken):
@@ -41,7 +42,7 @@ def get_live_playertoken(self, token_url, xvrttoken):
headers = {'Content-Type': 'application/json', 'Cookie' : cookie_value}
else:
headers = {'Content-Type': 'application/json'}
- token = TokenResolver._get_new_playertoken(token_path, token_url, headers)
+ token = self._get_new_playertoken(token_path, token_url, headers)
return token
def get_xvrttoken(self, get_roaming_token=False):
@@ -59,9 +60,8 @@ def get_xvrttoken_from_cookiejar(cookiejar):
if cookie.name == 'X-VRT-Token':
yield cookie
- @staticmethod
- def _get_new_playertoken(path, token_url, headers):
- playertoken = requests.post(token_url, headers=headers).json()
+ def _get_new_playertoken(self, path, token_url, headers):
+ playertoken = requests.post(token_url, proxies=self._proxies, headers=headers).json()
json.dump(playertoken, open(path, 'w'))
return playertoken['vrtPlayerToken']
@@ -86,19 +86,19 @@ def _get_new_xvrttoken(self, path, get_roaming_token):
self._kodi_wrapper.open_settings()
cred.reload()
data = {'loginID': cred.username, 'password': cred.password, 'sessionExpiration': '-1', 'APIKey': self._API_KEY, 'targetEnv': 'jssdk'}
- logon_json = requests.post(self._LOGIN_URL, data).json()
+ logon_json = requests.post(self._LOGIN_URL, data, proxies=self._proxies).json()
token = None
if logon_json['errorCode'] == 0:
login_token = logon_json['sessionInfo']['login_token']
login_cookie = ''.join(('glt_', self._API_KEY, '=', login_token))
payload = {'uid': logon_json['UID'], 'uidsig': logon_json['UIDSignature'], 'ts': logon_json['signatureTimestamp'], 'email': cred.username}
headers = {'Content-Type': 'application/json', 'Cookie': login_cookie}
- cookie_jar = requests.post(self._TOKEN_GATEWAY_URL, headers=headers, json=payload).cookies
+ cookie_jar = requests.post(self._TOKEN_GATEWAY_URL, proxies=self._proxies, headers=headers, json=payload).cookies
xvrttoken = TokenResolver._create_token_dictionary(cookie_jar)
token = xvrttoken['X-VRT-Token']
if get_roaming_token:
- xvrttoken = TokenResolver._get_roaming_xvrttoken(login_cookie, xvrttoken)
+ xvrttoken = self._get_roaming_xvrttoken(login_cookie, xvrttoken)
token = xvrttoken['X-VRT-Token'] if xvrttoken is not None else None
json.dump(xvrttoken, open(path, 'w'))
else:
@@ -107,19 +107,18 @@ def _get_new_xvrttoken(self, path, get_roaming_token):
self._kodi_wrapper.show_ok_dialog(title, message)
return token
- @staticmethod
- def _get_roaming_xvrttoken(login_cookie, xvrttoken):
+ def _get_roaming_xvrttoken(self, login_cookie, xvrttoken):
url = 'https://token.vrt.be/vrtnuinitloginEU?destination=https://www.vrt.be/vrtnu/'
cookie_value = 'X-VRT-Token=' + xvrttoken['X-VRT-Token']
headers = {'Cookie': cookie_value}
- r = requests.get(url, headers=headers, allow_redirects=False)
+ r = requests.get(url, proxies=self._proxies, headers=headers, allow_redirects=False)
url = r.headers.get('Location')
- r = requests.get(url, headers=headers, allow_redirects=False)
+ r = requests.get(url, proxies=self._proxies, headers=headers, allow_redirects=False)
url = r.headers.get('Location')
headers = {'Cookie': login_cookie}
roaming_xvrttoken = None
if url is not None:
- cookie_jar = requests.get(url, headers=headers).cookies
+ cookie_jar = requests.get(url, proxies=self._proxies, headers=headers).cookies
roaming_xvrttoken = TokenResolver._create_token_dictionary(cookie_jar)
return roaming_xvrttoken
diff --git a/plugin.video.vrt.nu/resources/lib/vrtplayer/vrtapihelper.py b/plugin.video.vrt.nu/resources/lib/vrtplayer/vrtapihelper.py
index 69b2eb53..d413de58 100644
--- a/plugin.video.vrt.nu/resources/lib/vrtplayer/vrtapihelper.py
+++ b/plugin.video.vrt.nu/resources/lib/vrtplayer/vrtapihelper.py
@@ -13,21 +13,22 @@
class VRTApiHelper:
- def __init__(self, kodi_wrapper):
- self._kodi_wrapper = kodi_wrapper
-
_VRT_BASE = 'https://www.vrt.be'
_VRTNU_API_BASE = 'https://vrtnu-api.vrt.be'
_VRTNU_SEARCH_URL = ''.join((_VRTNU_API_BASE, '/search'))
_VRTNU_SUGGEST_URL = ''.join((_VRTNU_API_BASE, '/suggest'))
_VRTNU_SCREENSHOT_URL = ''.join((_VRTNU_API_BASE, '/screenshots'))
+ def __init__(self, kodi_wrapper):
+ self._kodi_wrapper = kodi_wrapper
+ self._proxies = self._kodi_wrapper.get_proxies()
+
def get_tvshow_items(self, path):
if path == 'az':
api_url = ''.join((self._VRTNU_SUGGEST_URL, '?facets[transcodingStatus]=AVAILABLE'))
else:
api_url = ''.join((self._VRTNU_SUGGEST_URL, '?facets[categories]=', path))
- tvshows = requests.get(api_url).json()
+ tvshows = requests.get(api_url, proxies=self._proxies).json()
tvshow_items = []
for tvshow in tvshows:
metadata_creator = metadatacreator.MetadataCreator()
@@ -61,11 +62,11 @@ def get_episode_items(self, path):
sort_method = None
if path == 'recent':
api_url = ''.join((self._VRTNU_SEARCH_URL, '?i=video&size=50&facets[transcodingStatus]=AVAILABLE&facets[brands]=[een,canvas,sporza,radio1,klara,stubru,mnm]'))
- api_json = requests.get(api_url).json()
+ api_json = requests.get(api_url, proxies=self._proxies).json()
episode_items, sort_method = self._map_to_episode_items(api_json['results'], path)
else:
api_url = ''.join((self._VRTNU_SEARCH_URL, '?i=video&size=150&facets[programUrl]=//www.vrt.be', path.replace('.relevant', ''))) if '.relevant/' in path else path
- api_json = requests.get(api_url).json()
+ api_json = requests.get(api_url, proxies=self._proxies).json()
# Look for seasons items if not yet done
if 'facets[seasonTitle]' not in path:
episode_items = self._get_season_items(api_url, api_json['facets']['facets'])
diff --git a/plugin.video.vrt.nu/resources/lib/vrtplayer/vrtplayer.py b/plugin.video.vrt.nu/resources/lib/vrtplayer/vrtplayer.py
index 436dd84c..44bd8791 100644
--- a/plugin.video.vrt.nu/resources/lib/vrtplayer/vrtplayer.py
+++ b/plugin.video.vrt.nu/resources/lib/vrtplayer/vrtplayer.py
@@ -23,6 +23,7 @@ class VRTPlayer:
def __init__(self, addon_path, kodi_wrapper, stream_service, api_helper):
self._addon_path = addon_path
self._kodi_wrapper = kodi_wrapper
+ self._proxies = self._kodi_wrapper.get_proxies()
self._api_helper = api_helper
self._stream_service = stream_service
@@ -93,7 +94,7 @@ def __get_media(self, file_name):
return os.path.join(self._addon_path, 'resources', 'media', file_name)
def __get_category_menu_items(self, url, soupstrainer_parser_selector, routing_action, video_dict_action=None):
- response = requests.get(url)
+ response = requests.get(url, proxies=self._proxies)
tiles = SoupStrainer('a', soupstrainer_parser_selector)
soup = BeautifulSoup(response.content, 'html.parser', parse_only=tiles)
listing = []
diff --git a/plugin.video.vrt.nu/vrtnutests/vrtplayertests.py b/plugin.video.vrt.nu/vrtnutests/vrtplayertests.py
index 39b1f2ca..05bbca23 100644
--- a/plugin.video.vrt.nu/vrtnutests/vrtplayertests.py
+++ b/plugin.video.vrt.nu/vrtnutests/vrtplayertests.py
@@ -2,41 +2,37 @@
# GNU General Public License v2.0 (see COPYING or https://www.gnu.org/licenses/gpl-2.0.txt)
+import mock
import unittest
-from resources.lib.vrtplayer import vrtplayer
-from resources.lib.vrtplayer import vrtapihelper
-from mock import MagicMock
+
+from resources.lib.vrtplayer import vrtapihelper, vrtplayer
class TestVRTPlayer(unittest.TestCase):
+ _kodi_wrapper = mock.MagicMock()
+ _kodi_wrapper.get_proxies = mock.MagicMock(return_value=dict())
+
def test_show_videos_single_episode_shows_videos(self):
- mock = MagicMock()
- mock.show_listing()
- player = vrtplayer.VRTPlayer(None, mock, None, vrtapihelper.VRTApiHelper(mock))
+ player = vrtplayer.VRTPlayer(None, self._kodi_wrapper, None, vrtapihelper.VRTApiHelper(self._kodi_wrapper))
+ self._kodi_wrapper.show_listing()
player.show_episodes('/vrtnu/a-z/tussen-nu-en-morgen/2018/tussen-nu-en-morgen.relevant/')
- self.assertTrue(mock.show_listing.called)
+ self.assertTrue(self._kodi_wrapper.show_listing.called)
def test_show_videos_single_season_shows_videos(self):
- mock = MagicMock()
- mock.show_listing()
- player = vrtplayer.VRTPlayer(None, mock, None, vrtapihelper.VRTApiHelper(mock))
+ player = vrtplayer.VRTPlayer(None, self._kodi_wrapper, None, vrtapihelper.VRTApiHelper(self._kodi_wrapper))
player.show_episodes('/vrtnu/a-z/apocalyps--de-eerste-wereldoorlog/1/apocalyps--de-eerste-wereldoorlog-s1a3.relevant/')
- self.assertTrue(mock.show_listing.called)
+ self.assertTrue(self._kodi_wrapper.show_listing.called)
def test_show_videos_multiple_seasons_shows_videos(self):
- mock = MagicMock()
- mock.show_listing()
- player = vrtplayer.VRTPlayer(None, mock, None, vrtapihelper.VRTApiHelper(mock))
+ player = vrtplayer.VRTPlayer(None, self._kodi_wrapper, None, vrtapihelper.VRTApiHelper(self._kodi_wrapper))
player.show_episodes('vrtnu/a-z/animal-babies.relevant/')
- self.assertTrue(mock.show_listing.called)
+ self.assertTrue(self._kodi_wrapper.show_listing.called)
def test_show_videos_specific_seasons_shows_videos(self):
- mock = MagicMock()
- mock.show_listing()
- player = vrtplayer.VRTPlayer(None, mock, None, vrtapihelper.VRTApiHelper(mock))
+ player = vrtplayer.VRTPlayer(None, self._kodi_wrapper, None, vrtapihelper.VRTApiHelper(self._kodi_wrapper))
player.show_episodes('/vrtnu/a-z/thuis/24.lists.all-episodes.relevant/')
- self.assertTrue(mock.show_listing.called)
+ self.assertTrue(self._kodi_wrapper.show_listing.called)
if __name__ == '__main__':