Skip to content
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
3 changes: 3 additions & 0 deletions plugin.video.vrt.nu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ You can report issues at [our GitHub issues page](https://github.com/add-ons/plu
</table>

## Releases
### v2.5.48 (2026-01-08)
- Fix menu listings after api change (@mediaminister)

### v2.5.47 (2025-12-21)
- Fix IPTV Manager EPG (@mediaminister)

Expand Down
5 changes: 4 additions & 1 deletion plugin.video.vrt.nu/addon.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.vrt.nu" name="VRT MAX" version="2.5.47+matrix.1" provider-name="Martijn Moreel, dagwieers, mediaminister">
<addon id="plugin.video.vrt.nu" name="VRT MAX" version="2.5.48+matrix.1" provider-name="Martijn Moreel, dagwieers, mediaminister">
<requires>
<import addon="resource.images.studios.white" version="0.0.30"/>
<import addon="script.module.dateutil" version="2.8.2"/>
Expand Down Expand Up @@ -41,6 +41,9 @@
<website>https://github.com/add-ons/plugin.video.vrt.nu/wiki</website>
<source>https://github.com/add-ons/plugin.video.vrt.nu</source>
<news>
v2.5.48 (2026-01-08)
- Fix menu listings after api change

v2.5.47 (2025-12-21)
- Fix IPTV Manager EPG

Expand Down
80 changes: 40 additions & 40 deletions plugin.video.vrt.nu/resources/lib/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,13 @@ def resumepoints_is_activated():

def get_resumepoint_data(episode_id):
"""Get resumepoint data from GraphQL API"""
data_json = get_single_episode_data(episode_id)
video_id = data_json.get('data').get('page').get('episode').get('watchAction').get('videoId')
resumepoint_title = data_json.get('data').get('page').get('episode').get('watchAction').get('resumePointTitle')
return video_id, resumepoint_title
data_json = get_stream_data(episode_id)
return next(
(mode.get('resumePointTemplate', {})
for mode in data_json.get('data', {}).get('page', {}).get('player', {}).get('modes', [])
if mode.get('__typename') == 'VideoPlayerMode'),
None
)


def get_next_info(episode_id):
Expand Down Expand Up @@ -261,14 +264,14 @@ def get_next_info(episode_id):
'current_episode': current_episode,
'next_episode': next_episode,
'play_info': {
'episode_id': next_ep.get('id'),
'episode_id': next_ep.get('shareAction').get('url'),
}
}
return next_info


def get_stream_id_data(vrtmax_url):
"""Get stream_id from from GraphQL API"""
def get_stream_data(vrtmax_url):
"""Get stream data from from GraphQL API"""
page_id = vrtmax_url.split('www.vrt.be')[1]
graphql_query = """
query StreamId($pageId: ID!) {
Expand All @@ -286,13 +289,21 @@ def get_stream_id_data(vrtmax_url):
}
}
... on EpisodePage {
episode {
watchAction {
streamId
player {
modes {
... on VideoPlayerMode {
streamId
resumePointTemplate {
mediaId
mediaName
}
}
__typename
}
}
}
}
__typename
}
"""
operation_name = 'StreamId'
Expand Down Expand Up @@ -357,17 +368,8 @@ def get_single_episode_data(episode_id):
season {
titleRaw
}
watchAction {
avodUrl
completed
resumePoint
resumePointTotal
resumePointProgress
resumePointTitle
episodeId
videoId
publicationId
streamId
shareAction {
url
}
favoriteAction {
favorite
Expand Down Expand Up @@ -1740,7 +1742,7 @@ def convert_episode(episode_tile, destination=None):
"""Convert paginated episode item to TitleItem"""
import dateutil.parser
from datetime import datetime, timedelta
from base64 import b64decode, b64encode
from base64 import b64decode
import dateutil.tz

title_item = TitleItem(label=None, art_dict={}, info_dict={})
Expand Down Expand Up @@ -1772,16 +1774,13 @@ def convert_episode(episode_tile, destination=None):
if episode:
analytics = episode.get('analytics', {})
program = episode.get('program', {})
watch_action = episode.get('watchAction', {})
share_action = episode.get('shareAction', {})

# IDs and paths
episode_id = episode.get('id')
episode_page = analytics.get('pageName', '')
video_id = watch_action.get('videoId')
publication_id = watch_action.get('publicationId')
encoded_page = b64encode(episode_page.encode('utf-8')).decode('utf-8')
video_url = share_action.get('url') if share_action else ''

path = url_for('play_id', video_id=video_id, publication_id=publication_id, episode_id=encoded_page)
path = url_for('play_url', video_url=video_url)
program_name = url_to_program(program.get('link'))
program_id = program.get('id')
program_title = program.get('title')
Expand Down Expand Up @@ -1852,9 +1851,11 @@ def convert_episode(episode_tile, destination=None):
program_name, program_id, program_title, program_type, favorited, is_continue, episode_id
)

# Resume point logic
position = watch_action.get('resumePoint')
total = watch_action.get('resumePointTotal')
# FIXME: Resume point logic is broken
# position = watch_action.get('resumePoint')
# total = watch_action.get('resumePointTotal')
position = 0
total = 0
prop_dict = {}
playcount = -1

Expand Down Expand Up @@ -2020,8 +2021,8 @@ def get_offline_programs(end_cursor='', use_favorites=False):
def get_favorite_programs(end_cursor=''):
"""Get favorite programs"""
import base64
list_id = 'tl-fp|o%25|o%9|video-program%|video-program|b%0%'
list_id = '#{}'.format(base64.b64encode(list_id.encode('utf-8')).decode('utf-8'))
list_id = 'o%25|o%9|video-program%|video-program|b%0%'
list_id = '${}'.format(base64.b64encode(list_id.encode('utf-8')).decode('utf-8'))
programs = get_programs(list_id=list_id, destination='favorites_programs', end_cursor=end_cursor)
return programs

Expand Down Expand Up @@ -2099,8 +2100,8 @@ def get_search(keywords, end_cursor=''):
if query_string:
search_dict['q'] = query_string

list_id = 'tl-pag-srch|o%14|{}|{}%'.format(dumps(search_dict), result_type)
list_id = '#{}'.format(base64.b64encode(list_id.encode('utf-8')).decode('utf-8'))
list_id = 'o%14|{}|{}%'.format(dumps(search_dict), result_type)
list_id = '${}'.format(base64.b64encode(list_id.encode('utf-8')).decode('utf-8'))

if entity_type == 'video-program' and not end_cursor:
programs = get_programs(keywords=keywords, end_cursor=end_cursor)
Expand Down Expand Up @@ -2149,8 +2150,8 @@ def get_programs(list_id=None, destination=None, end_cursor='', category=None, c
if query_string:
search_dict['q'] = query_string

list_id = 'tl-pag-srch|o%14|{}|{}%'.format(dumps(search_dict), 'watch')
list_id = '#{}'.format(base64.b64encode(list_id.encode('utf-8')).decode('utf-8'))
list_id = 'o%14|{}|{}%'.format(dumps(search_dict), 'watch')
list_id = '${}'.format(base64.b64encode(list_id.encode('utf-8')).decode('utf-8'))

# kodi paging
kodi_page_size = get_setting_int('itemsperpage', default=50)
Expand Down Expand Up @@ -2362,7 +2363,7 @@ def find_episode_by_id(target_id):
tile = program.get('mostRelevantEpisodeTile')
if tile:
episode = (tile.get('tile', {}).get('episode', {}))
path = episode.get('watchAction', {}).get('videoUrl')
path = episode.get('shareAction', {}).get('url')

# Build entry
entry = {
Expand Down Expand Up @@ -2710,8 +2711,7 @@ def get_episode_by_air_date(channel_name, start_date, end_date=None):
_, _, _, video_item = convert_episode(episode)
video = {
'listitem': video_item,
'video_id': episode.get('episode').get('watchAction').get('videoId'),
'publication_id': episode.get('episode').get('watchAction').get('publicationId')
'video_url': episode.get('episode').get('shareAction').get('url'),
}
if video:
return video
Expand Down
14 changes: 2 additions & 12 deletions plugin.video.vrt.nu/resources/lib/graphql_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,8 @@
value
__typename
}
watchAction {
avodUrl
completed
resumePoint
resumePointTotal
resumePointProgress
resumePointTitle
episodeId
videoId
videoUrl
publicationId
streamId
shareAction {
url
}
favoriteAction {
favorite
Expand Down
13 changes: 8 additions & 5 deletions plugin.video.vrt.nu/resources/lib/playerinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ def onPlayBackStarted(self): # pylint: disable=invalid-name
return

# Get resumepoint data
from base64 import b64decode
self.video_id, self.resumepoint_title = get_resumepoint_data(episode_id=b64decode(self.path.split('/')[-1].encode('utf-8')).decode('utf-8'))
episode_id = 'https://' + self.path.split('https://')[1]
resumepoint_data = get_resumepoint_data(episode_id=episode_id)
self.video_id = resumepoint_data.get('mediaId')
self.resumepoint_title = resumepoint_data.get('mediaName')

# Kodi 17 doesn't have onAVStarted
if kodi_version_major() < 18:
Expand Down Expand Up @@ -172,7 +174,7 @@ def add_upnext(self, episode_id):
# Reset vrtnu_resumepoints property
set_property('vrtnu_resumepoints', None)

url = url_for('play_upnext', episode_id=episode_id)
url = url_for('play_url', video_url=episode_id)
self.update_position()
self.update_total()
if self.isPlaying() and self.total - self.last_pos < 1:
Expand All @@ -185,8 +187,9 @@ def add_upnext(self, episode_id):
def push_upnext(self):
"""Push episode info to Up Next service add-on"""
if has_addon('service.upnext') and get_setting_bool('useupnext', default=True) and self.isPlaying():
from base64 import b64decode, b64encode
next_info = get_next_info(episode_id=b64decode(self.path.split('/')[-1].encode('utf-8')).decode('utf-8'))
from base64 import b64encode
episode_id = 'https://' + self.path.split('https://')[1]
next_info = get_next_info(episode_id=episode_id)
if next_info:
from json import dumps
data = [b64encode(dumps(next_info).encode('utf-8')).decode('utf-8')]
Expand Down
18 changes: 8 additions & 10 deletions plugin.video.vrt.nu/resources/lib/streamservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,19 @@ def _get_api_data(self, video):
elif video_id and not video_url:
api_data = ApiData(self._CLIENT, self._VUALTO_API_URL, video_id, '', True)
elif video_url:
from api import get_stream_id_data
data_json = get_stream_id_data(video_url)
episode_data = data_json.get('data').get('page')
stream_id = ''
from api import get_stream_data
is_live_stream = False
if episode_data and episode_data.get('episode'):
stream_id = episode_data.get('episode').get('watchAction').get('streamId')
elif episode_data and episode_data.get('player'):
stream_id = ''
episode_page = get_stream_data(video_url).get('data', {}).get('page', {})
if episode_page:
stream_id = next(
(mode.get('streamId')
for mode in episode_data.get('player', {}).get('modes', [])
for mode in episode_page.get('player', {}).get('modes', [])
if mode.get('__typename') == 'VideoPlayerMode'),
None
''
)
is_live_stream = True
if episode_page.get('__typename') == 'LivestreamPage':
is_live_stream = True
api_data = ApiData(self._CLIENT, self._VUALTO_API_URL, stream_id, '', is_live_stream)
return api_data

Expand Down