Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add full proxy support #87

Merged
merged 6 commits into from
Mar 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions plugin.video.vrt.nu/addon.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<license>GNU General Public License, v3</license>
<news>
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
54 changes: 52 additions & 2 deletions plugin.video.vrt.nu/resources/lib/kodiwrappers/kodiwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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)
dagwieers marked this conversation as resolved.
Show resolved Hide resolved

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
Expand Down Expand Up @@ -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)
9 changes: 5 additions & 4 deletions plugin.video.vrt.nu/resources/lib/vrtplayer/streamservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
25 changes: 12 additions & 13 deletions plugin.video.vrt.nu/resources/lib/vrtplayer/tokenresolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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']

Expand All @@ -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:
Expand All @@ -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

Expand Down
13 changes: 7 additions & 6 deletions plugin.video.vrt.nu/resources/lib/vrtplayer/vrtapihelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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'])
Expand Down
3 changes: 2 additions & 1 deletion plugin.video.vrt.nu/resources/lib/vrtplayer/vrtplayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 = []
Expand Down
34 changes: 15 additions & 19 deletions plugin.video.vrt.nu/vrtnutests/vrtplayertests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__':
Expand Down