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 invisible setting 'usefavorites' to disable "My programs" (for unit tests)
- Disable brand filter in "My programs" recent listing (should be configurable really)
  • Loading branch information
dagwieers committed May 15, 2019
1 parent 69745ae commit 68f01a1
Show file tree
Hide file tree
Showing 15 changed files with 458 additions and 38 deletions.
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -42,6 +42,7 @@ unit:
PYTHONPATH=$(pwd) python test/apihelpertests.py
PYTHONPATH=$(pwd) python test/tvguidetests.py
PYTHONPATH=$(pwd) python test/searchtests.py
PYTHONPATH=$(pwd) python test/favoritestests.py
@echo -e "$(white)=$(blue) Unit tests finished successfully.$(reset)"

zip: test
Expand Down
21 changes: 19 additions & 2 deletions addon.py
Expand Up @@ -39,6 +39,21 @@ 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(program=params.get('program'), path=params.get('path'))
return
if action == actions.UNFOLLOW:
from resources.lib.vrtplayer import favorites
_favorites = favorites.Favorites(_kodiwrapper)
_favorites.unfollow(program=params.get('program'), path=params.get('path'))
return
if action == actions.REFRESH_FAVORITES:
from resources.lib.vrtplayer import favorites
_favorites = favorites.Favorites(_kodiwrapper)
_favorites.update_favorites()
return

from resources.lib.vrtplayer import vrtapihelper, vrtplayer
_apihelper = vrtapihelper.VRTApiHelper(_kodiwrapper)
Expand All @@ -47,21 +62,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_favorites_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
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
49 changes: 48 additions & 1 deletion resources/language/resource.language.en_gb/strings.po
Expand Up @@ -20,10 +20,14 @@ msgid "Interface"
msgstr ""

msgctxt "#30001"
msgid "Show episode permalink in plot"
msgid "Enable My programs"
msgstr ""

msgctxt "#30002"
msgid "Show episode permalink in plot"
msgstr ""

msgctxt "#30003"
msgid "Enable menu caching"
msgstr ""

Expand Down Expand Up @@ -75,6 +79,10 @@ msgctxt "#30042"
msgid "Install Widevine (for DRM content)"
msgstr ""

msgctxt "#30047"
msgid "Refresh favorites"
msgstr ""

msgctxt "#30048"
msgid "Clear VRT cookies"
msgstr ""
Expand Down Expand Up @@ -115,6 +123,14 @@ msgctxt "#30061"
msgid "Using a SOCKS proxy requires the PySocks library (script.module.pysocks) installed."
msgstr ""

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

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

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

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

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

msgctxt "#30415"
msgid "No followed programs found"
msgstr ""

msgctxt "#30416"
msgid "We could not find any programs that were followed.\n\nEither right-click on a program or an episode to follow a program, or follow a program on the VRT NU website."
msgstr ""

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

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

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

msgctxt "#30423"
msgid "Recently published episodes of My TV programs"
msgstr ""
50 changes: 49 additions & 1 deletion resources/language/resource.language.nl_nl/strings.po
Expand Up @@ -21,10 +21,14 @@ msgid "Interface"
msgstr "Interface"

msgctxt "#30001"
msgid "Enable My programs"
msgstr "Toon Mijn TV programma's"

msgctxt "#30002"
msgid "Show episode permalink in plot"
msgstr "Toon aflevering permalink in beschrijving"

msgctxt "#30002"
msgctxt "#30003"
msgid "Enable menu caching"
msgstr "Gebruik menu caching"

Expand Down Expand Up @@ -84,6 +88,10 @@ msgctxt "#30042"
msgid "Install Widevine (for DRM content)"
msgstr "Installeer Widevine (voor DRM content)"

msgctxt "#30047"
msgid "Refresh favorites"
msgstr "Ververs gevolgde programma's"

msgctxt "#30048"
msgid "Clear VRT cookies"
msgstr "Verwijder VRT cookies"
Expand Down Expand Up @@ -124,6 +132,14 @@ msgctxt "#30061"
msgid "Using a SOCKS proxy requires the PySocks library (script.module.pysocks) installed."
msgstr "Het gebruik van SOCKS proxies vereist dat de PySocks library (script.module.pysocks) geïnstalleerd is."

msgctxt "#30078"
msgid "My programs"
msgstr "Mijn TV programma's"

msgctxt "#30079"
msgid "Browse only the programs you follow"
msgstr "Bekijk enkel de programma's die je volgt"

msgctxt "#30080"
msgid "A-Z"
msgstr "A-Z"
Expand Down Expand Up @@ -259,3 +275,35 @@ msgstr "Morgen"
msgctxt "#30334"
msgid "In 2 days"
msgstr "Overmorgen"

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

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

msgctxt "#30415"
msgid "No followed programs found"
msgstr "Geen programma's worden gevolgd"

msgctxt "#30416"
msgid "We could not find any programs that were followed.\n\nEither right-click on a program or an episode to follow a program, or follow a program on the VRT NU website."
msgstr "We konden geen programma's vonden die je volgt.\n\nJe kan een programma volgen door rechts te klikken op een programma of aflevering, of om ze op de VRT NU website to volgen."

msgctxt "#30420"
msgid "My A-Z"
msgstr "Mijn TV programma's"

msgctxt "#30421"
msgid "Alphabetically sorted list of My TV programs"
msgstr "Alle TV-programma's die je volgt in alfabetische volgorde"

msgctxt "#30422"
msgid "My recent items"
msgstr "Mijn recente afleveringen"

msgctxt "#30423"
msgid "Recently published episodes of My TV programs"
msgstr "Recent gepubliceerde afleveringen van TV-programma's die je volgt"
3 changes: 2 additions & 1 deletion resources/lib/helperobjects/helperobjects.py
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
18 changes: 17 additions & 1 deletion resources/lib/kodiwrappers/kodiwrapper.py
Expand Up @@ -155,6 +155,9 @@ def show_listing(self, list_items, sort='unsorted', ascending=True, content=None
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 @@ -199,7 +202,13 @@ def get_search_string(self):

def show_ok_dialog(self, title, message):
import xbmcgui
xbmcgui.Dialog().ok(self._addon.getAddonInfo('name'), title, message)
if not title:
title = self._addon.getAddonInfo('name')
xbmcgui.Dialog().ok(title, message)

def show_notification(self, message, time=4000):
import xbmcgui
xbmcgui.Dialog().notification(self._addon.getAddonInfo('name'), message, xbmcgui.NOTIFICATION_INFO, time)

def set_locale(self):
import locale
Expand Down Expand Up @@ -334,10 +343,17 @@ def open_file(self, path, flags='r'):
yield f
f.close()

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

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

def container_refresh(self):
xbmc.executebuiltin('Container.Refresh')

def log_access(self, url, query_string, log_level='Verbose'):
''' Log addon access '''
if log_levels.get(log_level, 0) <= self._max_log_level:
Expand Down
4 changes: 4 additions & 0 deletions resources/lib/vrtplayer/actions.py
Expand Up @@ -5,14 +5,18 @@
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'
REFRESH_FAVORITES = 'refreshfavorites'
SEARCH = 'search'
UNFOLLOW = 'unfollow'
100 changes: 100 additions & 0 deletions resources/lib/vrtplayer/favorites.py
@@ -0,0 +1,100 @@
# -*- 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()

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.mktime(time.localtime()) - (2 * 60):
self._kodiwrapper.log_notice('CACHE: %s vs %s' % (self._kodiwrapper.stat_file(self._cache_file).st_mtime(), time.mktime(time.localtime()) - (5 * 60)), 'Debug')
with self._kodiwrapper.open_file(self._cache_file) as f:
self._favorites = json.loads(f.read())
return
self.update_favorites()

def update_favorites(self):
xvrttoken = self._tokenresolver.get_fav_xvrttoken()
headers = {
'authorization': 'Bearer ' + xvrttoken,
'content-type': 'application/json',
# 'Cookie': 'X-VRT-Token=' + xvrttoken,
'Referer': 'https://www.vrt.be/vrtnu',
}
req = Request('https://video-user-data.vrt.be/favorites', headers=headers)
self._favorites = json.loads(urlopen(req).read())
self.write_favorites()

def set_favorite(self, program, path, value=True):
if value is not self.is_favorite(path):
xvrttoken = self._tokenresolver.get_fav_xvrttoken()
headers = {
'authorization': 'Bearer ' + xvrttoken,
'content-type': 'application/json',
# 'Cookie': 'X-VRT-Token=' + xvrttoken,
'Referer': 'https://www.vrt.be/vrtnu',
}
payload = dict(isFavorite=value, programUrl=path, title=program)
self._kodiwrapper.log_notice('URL post: https://video-user-data.vrt.be/favorites/%s' % self.uuid(path), 'Verbose')
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, program, path):
self._kodiwrapper.show_notification('Follow ' + program)
self.set_favorite(program, path, True)
self._kodiwrapper.container_refresh()

def unfollow(self, program, path):
self._kodiwrapper.show_notification('Unfollow ' + program)
self.set_favorite(program, path, False)
self._kodiwrapper.container_refresh()

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')]

0 comments on commit 68f01a1

Please sign in to comment.