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

Integrate My List #96

Merged
merged 2 commits into from
Oct 9, 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
8 changes: 8 additions & 0 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ msgctxt "#30016"
msgid "Show the VTM GO recommendations"
msgstr ""

msgctxt "#30017"
msgid "My List"
msgstr ""

msgctxt "#30018"
msgid "Show your personal favourites"
msgstr ""


### CODE
msgctxt "#30200"
Expand Down
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 @@ -81,6 +81,14 @@ msgctxt "#30016"
msgid "Show the VTM GO recommendations"
msgstr "Bekijk de VTM GO aanbevelingen"

msgctxt "#30017"
msgid "My List"
msgstr "Mijn lijst"

msgctxt "#30018"
msgid "Show your personal favourites"
msgstr "Bekijk je persoonlijke favorieten"


### CODE
msgctxt "#30200"
Expand Down
58 changes: 56 additions & 2 deletions resources/lib/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ def show_index():
route_recommendations = show_kids_recommendations if kids else show_recommendations
listing.append((plugin.url_for(route_recommendations), listitem, True))

listitem = ListItem(localize(30017), offscreen=True) # My List
listitem.setArt({'icon': 'DefaultMusicPlaylist.png'})
listitem.setInfo('video', {
'plot': localize(30018),
})
route_mylist = show_kids_mylist if kids else show_mylist
listing.append((plugin.url_for(route_mylist), listitem, True))

# Only provide YouTube option when plugin.video.youtube is available
if get_cond_visibility('System.HasAddon(plugin.video.youtube)') != 0:
listitem = ListItem(localize(30007), offscreen=True) # YouTube
Expand Down Expand Up @@ -371,8 +379,6 @@ def show_recommendations_category(category):
})
listing.append((plugin.url_for(show_program, program=item.content_id), listitem, True))

xbmcplugin.setContent(plugin.handle, 'tvshows')

xbmcplugin.setContent(plugin.handle, 'files')

# Sort categories by default like in VTM GO.
Expand All @@ -384,6 +390,54 @@ def show_recommendations_category(category):
xbmcplugin.endOfDirectory(plugin.handle, ok)


@plugin.route('/kids/mylist')
def show_kids_mylist():
show_mylist()


@plugin.route('/mylist')
def show_mylist():
""" Show the items in My List. """
kids = _get_kids_mode()

listing = []
try:
_vtmGo = VtmGo(kids=kids)
mylist = _vtmGo.get_mylist()
except Exception as ex:
notification(message=str(ex))
raise

for item in mylist:
listitem = ListItem(item.title, offscreen=True)
listitem.setArt({
'thumb': item.cover,
})

if item.video_type == Content.CONTENT_TYPE_MOVIE:
listitem.setInfo('video', {
'mediatype': 'movie',
})
listitem.setProperty('IsPlayable', 'true')
listing.append((plugin.url_for(play, category='movies', item=item.content_id), listitem, False))

elif item.video_type == Content.CONTENT_TYPE_PROGRAM:
listitem.setInfo('video', {
'mediatype': None, # This shows a folder icon
})
listing.append((plugin.url_for(show_program, program=item.content_id), listitem, True))

xbmcplugin.setContent(plugin.handle, 'files')

# Sort categories by default like in VTM GO.
xbmcplugin.addSortMethod(plugin.handle, xbmcplugin.SORT_METHOD_UNSORTED)
xbmcplugin.addSortMethod(plugin.handle, xbmcplugin.SORT_METHOD_LABEL)
xbmcplugin.setPluginCategory(plugin.handle, category=_breadcrumb(localize(30017)))

ok = xbmcplugin.addDirectoryItems(plugin.handle, listing, len(listing))
xbmcplugin.endOfDirectory(plugin.handle, ok)


@plugin.route('/kids/catalog')
def show_kids_catalog():
show_catalog()
Expand Down
98 changes: 94 additions & 4 deletions resources/lib/vtmgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,22 @@ class Content:
CONTENT_TYPE_MOVIE = 'MOVIE'
CONTENT_TYPE_PROGRAM = 'PROGRAM'

def __init__(self, content_id=None, title=None, description=None, cover=None, video_type=None, geoblocked=None):
def __init__(self, content_id=None, title=None, description=None, cover=None, video_type=None, my_list=False, geoblocked=None):
""" Defines an item from the catalogue.
:type content_id: str
:type title: str
:type description: str
:type cover: str
:type video_type: str
:type my_list: bool
:type geoblocked: bool
"""
self.content_id = content_id
self.title = title
self.description = description if description else ''
self.cover = cover
self.video_type = video_type
self.my_list = my_list
self.geoblocked = geoblocked

def __repr__(self):
Expand Down Expand Up @@ -219,10 +221,10 @@ def get_config(self):
def get_recommendations(self):
""" Returns the config for the dashboard. """
response = self._get_url('/%s/main' % self._mode)
results = json.loads(response)
recommendations = json.loads(response)

categories = []
for cat in results.get('rows', []):
for cat in recommendations.get('rows', []):
if cat.get('rowType') in ['SWIMLANE_DEFAULT']:
items = []

Expand All @@ -243,6 +245,36 @@ def get_recommendations(self):

return categories

def get_mylist(self):
""" Returns the contents of My List """
response = self._get_url('/%s/main/swimlane/my-list' % self._mode)

# My list can be empty
if not response:
return []

result = json.loads(response)

items = []
for item in result.get('teasers'):
items.append(Content(
content_id=item.get('target', {}).get('id'),
video_type=item.get('target', {}).get('type'),
title=item.get('title'),
geoblocked=item.get('geoBlocked'),
cover=item.get('imageUrl'),
))

return items

def add_mylist(self, video_type, content_id):
""" Add an item to My List """
self._put_url('/%s/userData/myList/{type}/{id}'.format(type=video_type, id=content_id))

def del_mylist(self, video_type, content_id):
""" Delete an item from My List """
self._delete_url('/%s/userData/myList/{type}/{id}'.format(type=video_type, id=content_id))

def get_live(self):
""" Get a list of all the live tv channels.
:rtype list[LiveChannel]
Expand Down Expand Up @@ -452,7 +484,65 @@ def _get_url(self, url):
if response.status_code == 404:
raise UnavailableException()

if response.status_code != 200:
if response.status_code not in [200, 204]:
raise Exception('Error %s.' % response.status_code)

return response.text

def _put_url(self, url):
""" Makes a PUT request for the specified URL.
:type url: str
:rtype str
"""
headers = {
'x-app-version': '5',
'x-persgroep-mobile-app': 'true',
'x-persgroep-os': 'android',
'x-persgroep-os-version': '23',
'User-Agent': 'VTMGO/6.5.0 (be.vmma.vtm.zenderapp; build:11019; Android 23) okhttp/3.12.1'
}

token = self._auth.get_token()
if token:
headers['x-dpp-jwt'] = token

logger.debug('Putting %s...', url)

response = requests.session().put('https://api.vtmgo.be' + url, headers=headers, verify=False, proxies=proxies)

if response.status_code == 404:
raise UnavailableException()

if response.status_code not in [200, 204]:
raise Exception('Error %s.' % response.status_code)

return response.text

def _delete_url(self, url):
""" Makes a DELETE request for the specified URL.
:type url: str
:rtype str
"""
headers = {
'x-app-version': '5',
'x-persgroep-mobile-app': 'true',
'x-persgroep-os': 'android',
'x-persgroep-os-version': '23',
'User-Agent': 'VTMGO/6.5.0 (be.vmma.vtm.zenderapp; build:11019; Android 23) okhttp/3.12.1'
}

token = self._auth.get_token()
if token:
headers['x-dpp-jwt'] = token

logger.debug('Fetching %s...', url)

response = requests.session().delete('https://api.vtmgo.be' + url, headers=headers, verify=False, proxies=proxies)

if response.status_code == 404:
raise UnavailableException()

if response.status_code not in [200, 204]:
raise Exception('Error %s.' % response.status_code)

return response.text
4 changes: 2 additions & 2 deletions resources/lib/vtmgoauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class InvalidLoginException(Exception):


class VtmGoAuth:
username = None
password = None
username = ''
password = ''
hash = None

def __init__(self):
Expand Down
28 changes: 27 additions & 1 deletion test/test_routing.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals

import json
import logging
import os
import unittest
import warnings

from urllib3.exceptions import InsecureRequestWarning

from resources.lib import plugin
from resources.lib import plugin, vtmgoauth

xbmc = __import__('xbmc')
xbmcaddon = __import__('xbmcaddon')
Expand All @@ -22,6 +24,23 @@

class TestRouting(unittest.TestCase):

def __init__(self, *args, **kwargs):
super(TestRouting, self).__init__(*args, **kwargs)

# Read credentials from credentials.json
settings = {}
if 'VTMGO_USERNAME' in os.environ and 'VTMGO_PASSWORD' in os.environ:
logger.warning('Using credentials from the environment variables VTMGO_USERNAME and VTMGO_PASSWORD')
settings['username'] = os.environ.get('VTMGO_USERNAME')
settings['password'] = os.environ.get('VTMGO_PASSWORD')
else:
with open('test/userdata/credentials.json') as f:
settings = json.load(f)

if settings['username'] and settings['password']:
vtmgoauth.VtmGoAuth.username = settings['username']
vtmgoauth.VtmGoAuth.password = settings['password']

def setUp(self):
# Don't warn that we don't close our HTTPS connections, this is on purpose.
# warnings.simplefilter("ignore", ResourceWarning)
Expand Down Expand Up @@ -116,6 +135,13 @@ def test_recommendations_menu(self):
self.assertEqual(addon.url_for(plugin.show_recommendations_category, category='775de6ef-003d-4571-8a6e-8433be0ef379'),
'plugin://plugin.video.vtm.go/recommendations/775de6ef-003d-4571-8a6e-8433be0ef379')

# My List menu: '/mylist'
def test_mylist_menu(self):
plugin.run(['plugin://plugin.video.vtm.go/mylist', '0', ''])
self.assertEqual(addon.url_for(plugin.show_mylist), 'plugin://plugin.video.vtm.go/mylist')
plugin.run(['plugin://plugin.video.vtm.go/kids/mylist', '0', ''])
self.assertEqual(addon.url_for(plugin.show_kids_mylist), 'plugin://plugin.video.vtm.go/kids/mylist')

# Play Live TV: '/play/livetv/<channel>'
def test_play_livetv(self):
plugin.run(['plugin://plugin.video.vtm.go/play/channels/ea826456-6b19-4612-8969-864d1c818347?.pvr', '0', ''])
Expand Down
59 changes: 24 additions & 35 deletions test/test_vtmgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,24 @@ class TestVtmGo(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(TestVtmGo, self).__init__(*args, **kwargs)

self._vtmgo = vtmgo.VtmGo()
self._vtmgostream = vtmgostream.VtmGoStream()

# Read credentials from credentials.json
try:
settings = {}
if 'VTMGO_USERNAME' in os.environ and 'VTMGO_PASSWORD' in os.environ:
logger.warning('Using credentials from the environment variables VTMGO_USERNAME and VTMGO_PASSWORD')
settings['username'] = os.environ.get('VTMGO_USERNAME')
settings['password'] = os.environ.get('VTMGO_PASSWORD')
else:
with open('test/userdata/credentials.json') as f:
settings = json.load(f)

settings = {}
if 'VTMGO_USERNAME' in os.environ and 'VTMGO_PASSWORD' in os.environ:
logger.warning('Using credentials from the environment variables VTMGO_USERNAME and VTMGO_PASSWORD')
settings['username'] = os.environ.get('VTMGO_USERNAME')
settings['password'] = os.environ.get('VTMGO_PASSWORD')
else:
with open('test/userdata/credentials.json') as f:
settings = json.load(f)

if settings['username'] and settings['password']:
vtmgoauth.VtmGoAuth.username = settings['username']
vtmgoauth.VtmGoAuth.password = settings['password']
self._vtmgoauth = vtmgoauth.VtmGoAuth()
except Exception as exc:
logger.warning("Could not apply credentials: %s", exc)
self._vtmgoauth = None

# Enable debug logging for urllib
# try:
# import http.client as http_client
# except ImportError:
# # Python 2
# import httplib as http_client
# http_client.HTTPConnection.debuglevel = 1
#
# logging.basicConfig()
# logging.getLogger().setLevel(logging.DEBUG)
# requests_log = logging.getLogger("requests.packages.urllib3")
# requests_log.setLevel(logging.DEBUG)
# requests_log.propagate = True

self._vtmgoauth = vtmgoauth.VtmGoAuth()
self._vtmgo = vtmgo.VtmGo()
self._vtmgo_kids = vtmgo.VtmGo(kids=True)
self._vtmgostream = vtmgostream.VtmGoStream()

def setUp(self):
# Don't warn that we don't close our HTTPS connections, this is on purpose.
Expand All @@ -62,10 +46,6 @@ def setUp(self):
warnings.simplefilter("ignore", InsecureRequestWarning)

def test_login(self):
if self._vtmgoauth is None:
logger.warning('Skipping test_login since we have no credentials available')
return

token = self._vtmgoauth.get_token()
self.assertTrue(token)

Expand All @@ -79,6 +59,15 @@ def test_get_recommendations(self):
self.assertTrue(recommendations)
# print(main)

def test_get_mylist(self):
mylist = self._vtmgo.get_mylist()
self.assertIsInstance(mylist, list)
# print(mylist)

mylist = self._vtmgo_kids.get_mylist()
self.assertIsInstance(mylist, list)
# print(mylist)

def test_get_categories(self):
categories = self._vtmgo.get_categories()
self.assertTrue(categories)
Expand Down
Loading