@@ -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 ('"' , '"' )
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 ,
0 commit comments