Skip to content

Commit 5dd9808

Browse files
authored
Add support for content presented on homepage (#12)
* Add support for content presented on homepage * Make menu building from webpage more flexible * Add support for topics * Prevent upcoming live events from being played * Prevent broken next page urls to create a next page button * Undo special handling of HeroStage elements Since we now exclude all the broken next page buttons from being created, it is not necessary anymore to prevent the next page button for the HeroStage from being built separately. * Add support for query `media-section-with-show`
1 parent ad97029 commit 5dd9808

File tree

5 files changed

+173
-36
lines changed

5 files changed

+173
-36
lines changed

lib/srgssr.py

Lines changed: 109 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def __init__(self, plugin_handle, bu='srf', addon_id=ADDON_ID):
8383
self.host_url = f'https://www.{bu}.ch'
8484
if bu == 'swi':
8585
self.host_url = 'https://play.swissinfo.ch'
86+
self.playtv_url = f'{self.host_url}/play/tv'
8687
self.apiv3_url = f'{self.host_url}/play/v3/api/{bu}/production/'
88+
self.data_regex = \
89+
r'<script>window.__SSR_VIDEO_DATA__\s*=\s*(.+?)</script>'
8790
self.data_uri = f'special://home/addons/{self.addon_id}/resources/data'
8891
self.media_uri = \
8992
f'special://home/addons/{self.addon_id}/resources/media'
@@ -142,10 +145,14 @@ def build_url(mode=None, name=None, url=None, page_hash=None, page=None):
142145
page -- an integer used to indicate the current page in
143146
the list of items
144147
"""
145-
if mode:
148+
try:
146149
mode = str(mode)
147-
if page:
150+
except Exception:
151+
pass
152+
try:
148153
page = str(page)
154+
except Exception:
155+
pass
149156
added = False
150157
queries = (url, mode, name, page_hash, page)
151158
query_names = ('url', 'mode', 'name', 'page_hash', 'page')
@@ -233,7 +240,7 @@ def build_main_menu(self, identifiers=[]):
233240
'identifier': 'Topics',
234241
'name': self.plugin_language(30058),
235242
'mode': 13,
236-
'displayItem': False, # not (yet) supported
243+
'displayItem': self.get_boolean_setting('Topics'),
237244
'icon': self.icon,
238245
}, {
239246
# Most searched TV shows
@@ -271,6 +278,13 @@ def build_main_menu(self, identifiers=[]):
271278
'mode': 27,
272279
'displayItem': self.get_boolean_setting('Search'),
273280
'icon': self.icon,
281+
}, {
282+
# Homepage
283+
'identifier': 'Homepage',
284+
'name': self.plugin_language(30060),
285+
'mode': 200,
286+
'displayItem': self.get_boolean_setting('Homepage'),
287+
'icon': self.icon,
274288
}, {
275289
# YouTube
276290
'identifier': '%s_YouTube' % self.bu.upper(),
@@ -319,7 +333,8 @@ def build_menu_apiv3(self, queries, mode=1000, page=1, page_hash=None,
319333
Keyword arguments:
320334
queries -- the query string or a list of several queries
321335
mode -- mode for the URL of the next folder
322-
page -- current page
336+
page -- current page; if page is set to 0, do not build
337+
a next page button
323338
page_hash -- cursor for fetching the next items
324339
is_show -- indicates if the menu contains only shows
325340
whitelist_ids -- list of ids that should be displayed, if it is set
@@ -332,6 +347,7 @@ def build_menu_apiv3(self, queries, mode=1000, page=1, page_hash=None,
332347
data = json.loads(self.open_url(self.apiv3_url + query))
333348
if data:
334349
data = utils.try_get(data, ['data', 'data'], list, []) or \
350+
utils.try_get(data, ['data', 'medias'], list, []) or \
335351
utils.try_get(data, ['data', 'results'], list, []) or \
336352
utils.try_get(data, 'data', list, [])
337353
for item in data:
@@ -349,8 +365,9 @@ def build_menu_apiv3(self, queries, mode=1000, page=1, page_hash=None,
349365
cursor = None
350366

351367
if cursor:
352-
data = json.loads(self.open_url(self.apiv3_url + queries + (
353-
'&' if '?' in queries else '?') + 'next=' + cursor))
368+
symb = '&' if '?' in queries else '?'
369+
url = f'{self.apiv3_url}{queries}{symb}next={cursor}'
370+
data = json.loads(self.open_url(url))
354371
else:
355372
data = json.loads(self.open_url(self.apiv3_url + queries))
356373
cursor = utils.try_get(data, 'next') or utils.try_get(
@@ -363,13 +380,24 @@ def build_menu_apiv3(self, queries, mode=1000, page=1, page_hash=None,
363380
return
364381

365382
items = utils.try_get(data, 'data', list, []) or \
383+
utils.try_get(data, 'medias', list, []) or \
366384
utils.try_get(data, 'results', list, []) or data
367385

368386
for item in items:
369387
self.build_entry_apiv3(
370388
item, is_show=is_show, whitelist_ids=whitelist_ids)
371389

372390
if cursor:
391+
if page == 0 or page == '0':
392+
return
393+
394+
# Next page urls containing the string 'urns=' do not work
395+
# properly. So in this case prevent the next page button from
396+
# being created. Note that might lead to not having a next
397+
# page butten where there should be one.
398+
if 'urns=' in cursor:
399+
return
400+
373401
if page:
374402
url = self.build_url(
375403
mode=mode, name=queries, page=int(page)+1,
@@ -443,37 +471,67 @@ def build_newest_favourite_menu(self, page=1):
443471
queries.append('videos-by-show-id?showId=' + sid)
444472
return self.build_menu_apiv3(queries)
445473

446-
def extract_id_list(self, url, editor_picks=False):
474+
def build_homepage_menu(self):
447475
"""
448-
Opens a webpage and extracts video ids (of the form "id": "<vid>")
449-
from JavaScript snippets.
476+
Builds the homepage menu.
477+
"""
478+
self.build_menu_from_page(self.playtv_url, (
479+
'initialData', 'pacPageConfigs', 'videoHomeSections'))
450480

451-
Keyword argmuents:
452-
url -- the URL of the webpage
453-
editor_picks -- if set, only extracts ids of editor picks
454-
(default: False)
481+
def build_menu_from_page(self, url, path):
455482
"""
456-
self.log(f'extract_id_list, url = {url}')
457-
response = self.open_url(url)
458-
string_response = utils.str_or_none(response, default='')
459-
if not string_response:
460-
self.log(f'No video ids found on {url}')
461-
return []
462-
readable_string_response = string_response.replace('&quot;', '"')
463-
id_regex = r'''(?x)
464-
\"id\"
465-
\s*:\s*
466-
\"
467-
(?P<id>
468-
%s
469-
)
470-
\"
471-
''' % IDREGEX
472-
if editor_picks:
473-
id_regex += r'.+\"isEditorPick\"\s*:\s*true'
474-
id_list = [m.group('id') for m in re.finditer(
475-
id_regex, readable_string_response)]
476-
return id_list
483+
Builds a menu by extracting some content directly from a website.
484+
485+
Keyword arguments:
486+
url -- the url of the website
487+
path -- the path to the relevant data in the json (as tuple
488+
or list of strings)
489+
"""
490+
html = self.open_url(url)
491+
m = re.search(self.data_regex, html)
492+
if not m:
493+
self.log('build_menu_from_page: No data found in html')
494+
return
495+
content = m.groups()[0]
496+
try:
497+
js = json.loads(content)
498+
except Exception:
499+
self.log('build_menu_from_page: Invalid json')
500+
return
501+
data = utils.try_get(js, path, list, [])
502+
if not data:
503+
self.log('build_menu_from_page: Could not find any data in json')
504+
return
505+
for elem in data:
506+
try:
507+
id = elem['id']
508+
section_type = elem['sectionType']
509+
title = utils.try_get(elem, ('representation', 'title'))
510+
if section_type in ('MediaSection', 'ShowSection',
511+
'MediaSectionWithShow'):
512+
if section_type == 'MediaSection' and not title and \
513+
utils.try_get(
514+
elem, ('representation', 'name')
515+
) == 'HeroStage':
516+
title = self.language(30053)
517+
if not title:
518+
continue
519+
list_item = xbmcgui.ListItem(label=title)
520+
list_item.setArt({
521+
'thumb': self.icon,
522+
'fanart': self.fanart,
523+
})
524+
if section_type == 'MediaSection':
525+
name = f'media-section?sectionId={id}'
526+
elif section_type == 'ShowSection':
527+
name = f'show-section?sectionId={id}'
528+
elif section_type == 'MediaSectionWithShow':
529+
name = f'media-section-with-show?sectionId={id}'
530+
url = self.build_url(mode=1000, name=name, page=1)
531+
xbmcplugin.addDirectoryItem(
532+
self.handle, url, list_item, isFolder=True)
533+
except Exception:
534+
pass
477535

478536
def build_episode_menu(self, video_id, include_segments=True,
479537
segment_option=False, audio=False):
@@ -637,8 +695,21 @@ def build_entry_apiv3(self, data, is_show=False, whitelist_ids=None):
637695
'banner': show_image_url or image_url,
638696
})
639697
url = self.build_url(mode=100, name=urn)
698+
is_folder = True
699+
700+
# Prevent upcoming live events from being played:
701+
if 'swisstxt' in urn:
702+
url = self.build_url(mode=500, name=urn)
703+
is_folder = False
704+
640705
xbmcplugin.addDirectoryItem(
641-
self.handle, url, list_item, isFolder=True)
706+
self.handle, url, list_item, isFolder=is_folder)
707+
708+
def playback_not_supported_dialog(self, urn):
709+
heading = self.language(30500)
710+
message = self.language(30501) + f' {urn} ' + self.language(30502)
711+
dialog = xbmcgui.Dialog()
712+
dialog.notification(heading, message)
642713

643714
def build_menu_by_urn(self, urn):
644715
"""
@@ -652,7 +723,9 @@ def build_menu_by_urn(self, urn):
652723
self.build_menu_apiv3(f'videos-by-show-id?showId={id}')
653724
elif 'video' in urn:
654725
self.build_episode_menu(id)
655-
# TODO: Add 'topic'
726+
elif 'topic' in urn:
727+
self.build_menu_from_page(self.playtv_url, (
728+
'initialData', 'pacPageConfigs', 'topicSections', urn))
656729

657730
def build_entry(self, json_entry, is_folder=False, audio=False,
658731
fanart=None, urn=None, show_image_url=None,

resources/language/resource.language.de_de/strings.po

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ msgstr ""
1515
"Language: de\n"
1616
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
1717

18+
msgctxt "#30053"
19+
msgid "Recommendations"
20+
msgstr "Empfehlungen"
21+
1822
msgctxt "#30058"
1923
msgid "Today"
2024
msgstr "Heute"
@@ -102,3 +106,15 @@ msgstr "Kürzlich gesuchte Audios"
102106
msgctxt "#30118"
103107
msgid "Recently searched shows"
104108
msgstr "Kürzlich gesuchte Sendungen"
109+
110+
msgctxt "#30500"
111+
msgid "Playback not supported"
112+
msgstr "Wiedergabe wird nicht unterstützt"
113+
114+
msgctxt "#30501"
115+
msgid "Playback for item"
116+
msgstr "Wiedergabe für das Medium"
117+
118+
msgctxt "#30502"
119+
msgid "not supported"
120+
msgstr "wird nicht unterstützt"

resources/language/resource.language.en_gb/strings.po

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ msgstr ""
1515
"Language: en\n"
1616
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
1717

18+
msgctxt "#30053"
19+
msgid "Recommendations"
20+
msgstr ""
21+
1822
msgctxt "#30058"
1923
msgid "Today"
2024
msgstr ""
@@ -102,3 +106,15 @@ msgstr ""
102106
msgctxt "#30118"
103107
msgid "Recently searched shows"
104108
msgstr ""
109+
110+
msgctxt "#30500"
111+
msgid "Playback not supported"
112+
msgstr ""
113+
114+
msgctxt "#30501"
115+
msgid "Playback for item"
116+
msgstr ""
117+
118+
msgctxt "#30502"
119+
msgid "not supported"
120+
msgstr ""

resources/language/resource.language.fr_fr/strings.po

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ msgstr ""
1515
"Language: fr\n"
1616
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
1717

18+
msgctxt "#30053"
19+
msgid "Recommendations"
20+
msgstr "Recommandations"
21+
1822
msgctxt "#30058"
1923
msgid "Today"
2024
msgstr "Aujourd'hui"
@@ -102,3 +106,15 @@ msgstr "Audios récemment recherchées"
102106
msgctxt "#30118"
103107
msgid "Recently searched shows"
104108
msgstr "Émissions récemment recherchées"
109+
110+
msgctxt "#30500"
111+
msgid "Playback not supported"
112+
msgstr "La lecture n'est pas prise en charge"
113+
114+
msgctxt "#30501"
115+
msgid "Playback for item"
116+
msgstr "La lecture de l'élément"
117+
118+
msgctxt "#30502"
119+
msgid "not supported"
120+
msgstr "n'est pas prise en charge"

resources/language/resource.language.it_it/strings.po

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ msgstr ""
1515
"Language: it\n"
1616
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
1717

18+
msgctxt "#30053"
19+
msgid "Recommendations"
20+
msgstr "Consigli"
21+
1822
msgctxt "#30058"
1923
msgid "Today"
2024
msgstr "Oggi"
@@ -102,3 +106,15 @@ msgstr "Audios cercato di recente"
102106
msgctxt "#30118"
103107
msgid "Recently searched shows"
104108
msgstr "Show cercato di recente"
109+
110+
msgctxt "#30500"
111+
msgid "Playback not supported"
112+
msgstr "Riproduzione non supportata"
113+
114+
msgctxt "#30501"
115+
msgid "Playback for item"
116+
msgstr "Riproduzione per l'elemento"
117+
118+
msgctxt "#30502"
119+
msgid "not supported"
120+
msgstr "non supportata"

0 commit comments

Comments
 (0)