@@ -83,7 +83,10 @@ def __init__(self, plugin_handle, bu='srf', addon_id=ADDON_ID):
83
83
self .host_url = f'https://www.{ bu } .ch'
84
84
if bu == 'swi' :
85
85
self .host_url = 'https://play.swissinfo.ch'
86
+ self .playtv_url = f'{ self .host_url } /play/tv'
86
87
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>'
87
90
self .data_uri = f'special://home/addons/{ self .addon_id } /resources/data'
88
91
self .media_uri = \
89
92
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):
142
145
page -- an integer used to indicate the current page in
143
146
the list of items
144
147
"""
145
- if mode :
148
+ try :
146
149
mode = str (mode )
147
- if page :
150
+ except Exception :
151
+ pass
152
+ try :
148
153
page = str (page )
154
+ except Exception :
155
+ pass
149
156
added = False
150
157
queries = (url , mode , name , page_hash , page )
151
158
query_names = ('url' , 'mode' , 'name' , 'page_hash' , 'page' )
@@ -233,7 +240,7 @@ def build_main_menu(self, identifiers=[]):
233
240
'identifier' : 'Topics' ,
234
241
'name' : self .plugin_language (30058 ),
235
242
'mode' : 13 ,
236
- 'displayItem' : False , # not (yet) supported
243
+ 'displayItem' : self . get_boolean_setting ( 'Topics' ),
237
244
'icon' : self .icon ,
238
245
}, {
239
246
# Most searched TV shows
@@ -271,6 +278,13 @@ def build_main_menu(self, identifiers=[]):
271
278
'mode' : 27 ,
272
279
'displayItem' : self .get_boolean_setting ('Search' ),
273
280
'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 ,
274
288
}, {
275
289
# YouTube
276
290
'identifier' : '%s_YouTube' % self .bu .upper (),
@@ -319,7 +333,8 @@ def build_menu_apiv3(self, queries, mode=1000, page=1, page_hash=None,
319
333
Keyword arguments:
320
334
queries -- the query string or a list of several queries
321
335
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
323
338
page_hash -- cursor for fetching the next items
324
339
is_show -- indicates if the menu contains only shows
325
340
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,
332
347
data = json .loads (self .open_url (self .apiv3_url + query ))
333
348
if data :
334
349
data = utils .try_get (data , ['data' , 'data' ], list , []) or \
350
+ utils .try_get (data , ['data' , 'medias' ], list , []) or \
335
351
utils .try_get (data , ['data' , 'results' ], list , []) or \
336
352
utils .try_get (data , 'data' , list , [])
337
353
for item in data :
@@ -349,8 +365,9 @@ def build_menu_apiv3(self, queries, mode=1000, page=1, page_hash=None,
349
365
cursor = None
350
366
351
367
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 ))
354
371
else :
355
372
data = json .loads (self .open_url (self .apiv3_url + queries ))
356
373
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,
363
380
return
364
381
365
382
items = utils .try_get (data , 'data' , list , []) or \
383
+ utils .try_get (data , 'medias' , list , []) or \
366
384
utils .try_get (data , 'results' , list , []) or data
367
385
368
386
for item in items :
369
387
self .build_entry_apiv3 (
370
388
item , is_show = is_show , whitelist_ids = whitelist_ids )
371
389
372
390
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
+
373
401
if page :
374
402
url = self .build_url (
375
403
mode = mode , name = queries , page = int (page )+ 1 ,
@@ -443,37 +471,67 @@ def build_newest_favourite_menu(self, page=1):
443
471
queries .append ('videos-by-show-id?showId=' + sid )
444
472
return self .build_menu_apiv3 (queries )
445
473
446
- def extract_id_list (self , url , editor_picks = False ):
474
+ def build_homepage_menu (self ):
447
475
"""
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' ))
450
480
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 ):
455
482
"""
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
477
535
478
536
def build_episode_menu (self , video_id , include_segments = True ,
479
537
segment_option = False , audio = False ):
@@ -637,8 +695,21 @@ def build_entry_apiv3(self, data, is_show=False, whitelist_ids=None):
637
695
'banner' : show_image_url or image_url ,
638
696
})
639
697
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
+
640
705
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 )
642
713
643
714
def build_menu_by_urn (self , urn ):
644
715
"""
@@ -652,7 +723,9 @@ def build_menu_by_urn(self, urn):
652
723
self .build_menu_apiv3 (f'videos-by-show-id?showId={ id } ' )
653
724
elif 'video' in urn :
654
725
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 ))
656
729
657
730
def build_entry (self , json_entry , is_folder = False , audio = False ,
658
731
fanart = None , urn = None , show_image_url = None ,
0 commit comments