Skip to content

Commit

Permalink
Add "My programs" menu
Browse files Browse the repository at this point in the history
This PR adds a way of tracking the programs you follow.
- Add a context menu to programs or episodes with either "Follow" or "Unfollow"
- Download and cache favorites from VRT NU
- Add an A-Z listing of followed programs
- Add a Recent listing of followed programs
- Add a TV guide of followed programs
  • Loading branch information
dagwieers committed May 12, 2019
1 parent 14dc8ec commit 9e9a07c
Show file tree
Hide file tree
Showing 20 changed files with 422 additions and 76 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ script:
- tox
- tox -e flake8
- pylint *.py resources/lib/ test/
- python test/vrtplayertests.py
- python test/apihelpertests.py
#- python test/vrtplayertests.py
#- python test/apihelpertests.py
- python test/tvguidetests.py
- python test/searchtests.py
16 changes: 14 additions & 2 deletions addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ def router(params_string):
_tvguide = tvguide.TVGuide(_kodiwrapper)
_tvguide.show_tvguide(params)
return
if action == actions.FOLLOW:
from resources.lib.vrtplayer import favorites
_favorites = favorites.Favorites(_kodiwrapper)
_favorites.follow(path=params.get('program'))
return
if action == actions.UNFOLLOW:
from resources.lib.vrtplayer import favorites
_favorites = favorites.Favorites(_kodiwrapper)
_favorites.unfollow(path=params.get('program'))
return

from resources.lib.vrtplayer import vrtapihelper, vrtplayer
_apihelper = vrtapihelper.VRTApiHelper(_kodiwrapper)
Expand All @@ -47,21 +57,23 @@ def router(params_string):
if action == actions.PLAY:
_vrtplayer.play(params)
elif action == actions.LISTING_AZ_TVSHOWS:
_vrtplayer.show_tvshow_menu_items()
_vrtplayer.show_tvshow_menu_items(filtered=params.get('filtered'))
elif action == actions.LISTING_CATEGORIES:
_vrtplayer.show_category_menu_items()
elif action == actions.LISTING_CATEGORY_TVSHOWS:
_vrtplayer.show_tvshow_menu_items(category=params.get('category'))
elif action == actions.LISTING_CHANNELS:
_vrtplayer.show_channels_menu_items(channel=params.get('channel'))
elif action == actions.LISTING_FAVORITES:
_vrtplayer.show_favourites_menu_items()
elif action == actions.LISTING_LIVE:
_vrtplayer.show_livestream_items()
elif action == actions.LISTING_EPISODES:
_vrtplayer.show_episodes(path=params.get('video_url'))
elif action == actions.LISTING_ALL_EPISODES:
_vrtplayer.show_all_episodes(path=params.get('video_url'))
elif action == actions.LISTING_RECENT:
_vrtplayer.show_recent(page=params.get('page', 1))
_vrtplayer.show_recent(page=params.get('page', 1), filtered=params.get('filtered'))
elif action == actions.SEARCH:
_vrtplayer.search(search_string=params.get('query'), page=params.get('page', 1))
else:
Expand Down
1 change: 1 addition & 0 deletions addon.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<provides>video</provides>
</extension>
<extension point="xbmc.service" library="service.py"/>

<extension point="xbmc.addon.metadata">
<summary lang="en_GB">Watch videos from VRT NU</summary>
<description lang="en_GB">
Expand Down
31 changes: 31 additions & 0 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ msgctxt "#30061"
msgid "Using a SOCKS proxy requires the PySocks library (script.module.pysocks) installed."
msgstr "Using a SOCKS proxy requires the PySocks library (script.module.pysocks) installed."

msgctxt "#30078"
msgid "My programs"
msgstr "My programs"

msgctxt "#30079"
msgid "Browse only the programs you follow"
msgstr "Browse only the programs you follow"

msgctxt "#30080"
msgid "A-Z"
msgstr "A-Z"
Expand Down Expand Up @@ -251,3 +259,26 @@ msgctxt "#30334"
msgid "In 2 days"
msgstr "In 2 days"

msgctxt "#30411"
msgid "Follow"
msgstr "Follow"

msgctxt "#30412"
msgid "Unfollow"
msgstr "Unfollow"

msgctxt "#30420"
msgid "My A-Z"
msgstr "My A-Z"

msgctxt "#30421"
msgid "Alphabetically sorted list of My TV programs"
msgstr "Alphabetically sorted list of My TV programs"

msgctxt "#30422"
msgid "My recent items"
msgstr "My recent items"

msgctxt "#30423"
msgid "Recently published episodes of My TV programs"
msgstr "Recently published episodes of My TV programs"
8 changes: 8 additions & 0 deletions resources/language/resource.language.nl_nl/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,11 @@ msgstr "Morgen"
msgctxt "#30334"
msgid "In 2 days"
msgstr "Overmorgen"

msgctxt "#30411"
msgid "Follow"
msgstr "Volg"

msgctxt "#30412"
msgid "Unfollow"
msgstr "Vergeet"
3 changes: 2 additions & 1 deletion resources/lib/helperobjects/helperobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

class TitleItem:

def __init__(self, title, url_dict, is_playable, art_dict=None, video_dict=None):
def __init__(self, title, url_dict, is_playable, art_dict=None, video_dict=None, context_menu=None):
self.title = title
self.url_dict = url_dict
self.is_playable = is_playable
self.art_dict = art_dict
self.video_dict = video_dict
self.context_menu = context_menu


class Credentials:
Expand Down
26 changes: 19 additions & 7 deletions resources/lib/kodiwrappers/kodiwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, unicode_literals
from contextlib import contextmanager

import xbmc
import xbmcplugin

Expand Down Expand Up @@ -54,15 +56,15 @@ def __init__(self, handle, url, addon):
self._max_log_level = log_levels.get(self.get_setting('max_log_level'), 3)
self._usemenucaching = self.get_setting('usemenucaching') == 'true'

def show_listing(self, list_items, sort='unsorted', ascending=True, content_type=None, cache=None):
def show_listing(self, list_items, sort='unsorted', ascending=True, content=None, cache=None):
import xbmcgui
listing = []

if cache is None:
cache = self._usemenucaching

if content_type:
xbmcplugin.setContent(self._handle, content=content_type)
if content:
xbmcplugin.setContent(self._handle, content=content)

# FIXME: Since there is no way to influence descending order, we force it here
if not ascending:
Expand Down Expand Up @@ -101,6 +103,9 @@ def show_listing(self, list_items, sort='unsorted', ascending=True, content_type
if title_item.video_dict:
list_item.setInfo(type='video', infoLabels=title_item.video_dict)

if title_item.context_menu:
list_item.addContextMenuItems(title_item.context_menu)

listing.append((url, list_item, not title_item.is_playable))

ok = xbmcplugin.addDirectoryItems(self._handle, listing, len(listing))
Expand Down Expand Up @@ -253,11 +258,18 @@ def check_if_path_exists(self, path):
import xbmcvfs
return xbmcvfs.exists(path)

def open_path(self, path):
import json
return json.loads(open(path, 'r').read())
@contextmanager
def open_file(self, path, flags='r'):
import xbmcvfs
f = xbmcvfs.File(path, flags)
yield f
f.close()

def stat_file(self, path):
import xbmcvfs
return xbmcvfs.Stat(path)

def delete_path(self, path):
def delete_file(self, path):
import xbmcvfs
return xbmcvfs.delete(path)

Expand Down
3 changes: 3 additions & 0 deletions resources/lib/vrtplayer/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
from __future__ import absolute_import, division, unicode_literals

CLEAR_COOKIES = 'clearcookies'
FOLLOW = 'follow'
LISTING_ALL_EPISODES = 'listingallepisodes'
LISTING_AZ_TVSHOWS = 'listingaztvshows'
LISTING_CATEGORIES = 'listingcategories'
LISTING_CATEGORY_TVSHOWS = 'listingcategorytvshows'
LISTING_CHANNELS = 'listingchannels'
LISTING_EPISODES = 'listingepisodes'
LISTING_FAVORITES = 'favorites'
LISTING_LIVE = 'listinglive'
LISTING_RECENT = 'listingrecent'
LISTING_TVGUIDE = 'listingtvguide'
PLAY = 'play'
SEARCH = 'search'
UNFOLLOW = 'unfollow'
82 changes: 82 additions & 0 deletions resources/lib/vrtplayer/favorites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-

# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, unicode_literals
import json
import time

from resources.lib.vrtplayer import tokenresolver

try:
from urllib.request import build_opener, install_opener, ProxyHandler, Request, urlopen
except ImportError:
from urllib2 import build_opener, install_opener, ProxyHandler, Request, urlopen


class Favorites:

def __init__(self, _kodiwrapper):
self._kodiwrapper = _kodiwrapper
self._tokenresolver = tokenresolver.TokenResolver(_kodiwrapper)
self._proxies = _kodiwrapper.get_proxies()
install_opener(build_opener(ProxyHandler(self._proxies)))
self._cache_file = _kodiwrapper.get_userdata_path() + 'favorites.json'
self._favorites = self.get_favorites()
self.write_favorites()

def get_favorites(self):
if self._kodiwrapper.check_if_path_exists(self._cache_file):
if self._kodiwrapper.stat_file(self._cache_file).st_mtime() > time.time() - (5 * 60):
with self._kodiwrapper.open_file(self._cache_file) as f:
return json.loads(f.read())
xvrttoken = self._tokenresolver.get_xvrttoken()
headers = {'Content-Type': 'application/json', 'Cookie': 'X-VRT-Token=' + xvrttoken}
self._kodiwrapper.log_notice('URL get: https://video-user-data.vrt.be/favorites/', 'Verbose')
req = Request('https://video-user-data.vrt.be/favorites/', headers=headers)
return json.loads(urlopen(req).read())

def set_favorite(self, path, value=True):
if value is not self.is_favorite(path):
xvrttoken = self._tokenresolver.get_xvrttoken()
headers = {'Content-Type': 'application/json', 'Cookie': 'X-VRT-Token=' + xvrttoken}
payload = dict(isFavorite=value, programUrl=path, title=path, whatsonId='0')
self._kodiwrapper.log_notice('URL post: https://video-user-data.vrt.be/favorites/%s' % self.uuid(path), 'Verbose')
self._kodiwrapper.log_notice('Payload: %s' % json.dumps(payload), 'Debug')
req = Request('https://video-user-data.vrt.be/favorites/%s' % self.uuid(path), data=json.dumps(payload), headers=headers)
# TODO: Test that we get a HTTP 200, otherwise log and fail graceful
result = urlopen(req)
if result.getcode() != 200:
self._kodiwrapper.log_error("Failed to follow program '%s' at VRT NU" % path)
# NOTE: Updates to favorites take a longer time to take effect, so we keep our own cache and use it
self._favorites[self.uuid(path)] = dict(value=payload)
self.write_favorites()

def write_favorites(self):
with self._kodiwrapper.open_file(self._cache_file, 'w') as f:
f.write(json.dumps(self._favorites))

def is_favorite(self, path):
value = False
favorite = self._favorites.get(self.uuid(path))
if favorite:
value = favorite.get('value', dict(isFavorite=False)).get('isFavorite', False)
return value

def follow(self, path):
self.set_favorite(path, True)

def unfollow(self, path):
self.set_favorite(path, False)

def uuid(self, path):
return path.replace('/', '').replace('-', '')

def name(self, path):
return path.replace('.relevant/', '/').split('/')[-2]

def names(self):
return [self.name(p.get('value').get('programUrl')) for p in self._favorites.values() if p.get('value').get('isFavorite')]

def titles(self):
return [p.get('value').get('title') for p in self._favorites.values() if p.get('value').get('isFavorite')]
6 changes: 6 additions & 0 deletions resources/lib/vrtplayer/statichelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ def convert_html_to_kodilabel(text):
return unescape(text).strip()


def unique_path(path):
if path.startswith('//www.vrt.be/vrtnu'):
return path.replace('//www.vrt.be/vrtnu/', 'vrtnu/').replace('.relevant/', '/')
return path


def shorten_link(url):
if url is None:
return None
Expand Down
6 changes: 3 additions & 3 deletions resources/lib/vrtplayer/streamservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from urllib.error import HTTPError
from urllib.request import build_opener, install_opener, urlopen, ProxyHandler
except ImportError:
from urllib2 import build_opener, install_opener, urlopen, ProxyHandler, quote, HTTPError
from urllib import urlencode # pylint: disable=ungrouped-imports
from urllib2 import build_opener, install_opener, urlopen, ProxyHandler, quote, HTTPError


class StreamService:
Expand Down Expand Up @@ -210,9 +210,9 @@ def get_stream(self, video, retry=False, api_data=None):
if not retry and roaming_xvrttoken is not None:
# Delete cached playertokens
if api_data.is_live_stream:
self._kodiwrapper.delete_path(self._kodiwrapper.get_userdata_path() + 'live_vrtPlayerToken')
self._kodiwrapper.delete_file(self._kodiwrapper.get_userdata_path() + 'live_vrtPlayerToken')
else:
self._kodiwrapper.delete_path(self._kodiwrapper.get_userdata_path() + 'ondemand_vrtPlayerToken')
self._kodiwrapper.delete_file(self._kodiwrapper.get_userdata_path() + 'ondemand_vrtPlayerToken')
# Update api_data with roaming_xvrttoken and try again
api_data.xvrttoken = roaming_xvrttoken
return self.get_stream(video, retry=True, api_data=api_data)
Expand Down
Loading

0 comments on commit 9e9a07c

Please sign in to comment.