Update root menu items

- New recommendations as per YouTube home page (will use account details if logged in)
- Old recommendations -> related videos (requires local or remote history)
- Popular right now -> Trending
- Cache recommended and related video results for 1 hour
This commit is contained in:
MoojMidge 2024-01-11 01:28:29 +11:00
parent e2e7d1329f
commit 75488c3f2b
14 changed files with 235 additions and 68 deletions

View file

@ -354,7 +354,7 @@ msgid "Browse Channels"
msgstr ""
msgctxt "#30513"
msgid "Popular right now"
msgid "Trending"
msgstr ""
msgctxt "#30514"

View file

@ -354,7 +354,7 @@ msgid "Browse Channels"
msgstr ""
msgctxt "#30513"
msgid "Popular right now"
msgid "Trending"
msgstr ""
msgctxt "#30514"

View file

@ -350,7 +350,7 @@ msgid "Browse Channels"
msgstr ""
msgctxt "#30513"
msgid "Popular right now"
msgid "Trending"
msgstr ""
msgctxt "#30514"

View file

@ -355,7 +355,7 @@ msgid "Browse Channels"
msgstr ""
msgctxt "#30513"
msgid "Popular right now"
msgid "Trending"
msgstr ""
msgctxt "#30514"

View file

@ -72,6 +72,7 @@ class AbstractContext(object):
'channel_name',
'client_id',
'client_secret',
'click_tracking',
'event_type',
'item',
'item_id',
@ -85,9 +86,10 @@ class AbstractContext(object):
'rating',
'search_type',
'subscription_id',
'uri',
'videoid', # deprecated
'video_id',
'uri',
'visitor',
}
def __init__(self, path='/', params=None, plugin_name='', plugin_id=''):

View file

@ -139,7 +139,6 @@ class XbmcContext(AbstractContext):
'playlist.select': 30521,
'playlists': 30501,
'please_wait': 30119,
'popular_right_now': 30513,
'prompt': 30566,
'purchases': 30622,
'recommendations': 30551,
@ -199,6 +198,7 @@ class XbmcContext(AbstractContext):
'subtitles.no_auto_generated': 30602,
'subtitles.with_fallback': 30601,
'succeeded': 30575,
'trending': 30513,
'unrated.video': 30718,
'unsubscribe': 30505,
'unsubscribed.from.channel': 30720,

View file

@ -375,7 +375,7 @@ class YouTube(LoginClient):
params=params,
**kwargs)
def get_popular_videos(self, page_token='', **kwargs):
def get_trending_videos(self, page_token='', **kwargs):
params = {'part': 'snippet,status',
'maxResults': str(self._max_results),
'regionCode': self._region,
@ -415,7 +415,141 @@ class YouTube(LoginClient):
params=params,
**kwargs)
def _get_recommendations_for_home(self):
def get_recommended_for_home(self,
visitor='',
page_token='',
click_tracking=''):
payload = {
'kind': 'youtube#activityListResponse',
'items': []
}
post_data = {'browseId': 'FEwhat_to_watch'}
if page_token:
post_data['continuation'] = page_token
if click_tracking or visitor:
context = {}
if click_tracking:
context['clickTracking'] = {
'clickTrackingParams': click_tracking,
}
if visitor:
context['client'] = {
'visitorData': visitor,
}
post_data['context'] = context
result = self.api_request(version=1,
method='POST',
path='browse',
post_data=post_data)
if not result:
return payload
recommended_videos = self.json_traverse(
result,
path=(
(
(
'onResponseReceivedEndpoints',
'onResponseReceivedActions',
),
0,
'appendContinuationItemsAction',
'continuationItems',
) if page_token else (
'contents',
'twoColumnBrowseResultsRenderer',
'tabs',
0,
'tabRenderer',
'content',
'richGridRenderer',
'contents',
)
) + (
slice(None),
(
(
'richItemRenderer',
'content',
'videoRenderer',
# 'videoId',
),
(
'richSectionRenderer',
'content',
'richShelfRenderer',
'contents',
slice(None),
'richItemRenderer',
'content',
(
'videoRenderer',
'reelItemRenderer'
),
# 'videoId',
),
(
'continuationItemRenderer',
'continuationEndpoint',
),
),
)
)
if not recommended_videos:
return payload
v3_response = {
'kind': 'youtube#activityListResponse',
'items': [
{
'kind': "youtube#video",
'id': video['videoId'],
'partial': True,
'snippet': {
'title': self.json_traverse(video, (
('title', 'runs', 0, 'text'),
('headline', 'simpleText'),
)),
'thumbnails': dict(zip(
('default', 'high'),
video['thumbnail']['thumbnails'],
)),
'channelId': self.json_traverse(video, (
('longBylineText', 'shortBylineText'),
'runs',
0,
'navigationEndpoint',
'browseEndpoint',
'browseId',
)),
}
}
for videos in recommended_videos
for video in
(videos if isinstance(videos, list) else (videos,))
if video and 'videoId' in video
]
}
last_item = recommended_videos[-1]
if last_item and 'continuationCommand' in last_item:
if 'clickTrackingParams' in last_item:
v3_response['clickTracking'] = last_item['clickTrackingParams']
token = last_item['continuationCommand'].get('token')
if token:
v3_response['nextPageToken'] = token
visitor = self.json_traverse(result, (
'responseContext',
'visitorData',
)) or visitor
if visitor:
v3_response['visitorData'] = visitor
return v3_response
def get_related_for_home(self, page_token=''):
"""
YouTube has deprecated this API, so we use history and related items to
form a recommended set.
@ -445,6 +579,7 @@ class YouTube(LoginClient):
video_ids = []
else:
return payload
for item in history_items:
try:
video_ids.append(item['snippet']['resourceId']['videoId'])
@ -670,10 +805,6 @@ class YouTube(LoginClient):
'regionCode': self._region,
'hl': self._language}
if channel_id == 'home':
recommended = self._get_recommendations_for_home()
if 'items' in recommended and recommended['items']:
return recommended
if channel_id == 'home':
params['home'] = 'true'
elif channel_id == 'mine':
@ -904,32 +1035,34 @@ class YouTube(LoginClient):
related_videos = self.json_traverse(
result,
path=((
'onResponseReceivedEndpoints',
0,
'appendContinuationItemsAction',
'continuationItems',
) if page_token else (
'contents',
'twoColumnWatchNextResults',
'secondaryResults',
'secondaryResults',
'results',
)) + (
slice(None),
(
(
'compactVideoRenderer',
# 'videoId',
),
(
'continuationItemRenderer',
'continuationEndpoint',
'continuationCommand',
# 'token',
),
),
)
path=(
(
'onResponseReceivedEndpoints',
0,
'appendContinuationItemsAction',
'continuationItems',
) if page_token else (
'contents',
'twoColumnWatchNextResults',
'secondaryResults',
'secondaryResults',
'results',
)
) + (
slice(None),
(
(
'compactVideoRenderer',
# 'videoId',
),
(
'continuationItemRenderer',
'continuationEndpoint',
'continuationCommand',
# 'token',
),
),
)
)
if not related_videos:
return []

View file

@ -382,7 +382,9 @@ def response_to_items(provider,
yt_total_results = int(page_info.get('totalResults', 0))
yt_results_per_page = int(page_info.get('resultsPerPage', 0))
page = int(context.get_param('page', 1))
yt_visitor_data = json_data.get('visitorData', '')
yt_next_page_token = json_data.get('nextPageToken', '')
yt_click_tracking = json_data.get('clickTracking', '')
if yt_next_page_token or (page * yt_results_per_page < yt_total_results):
if not yt_next_page_token:
client = provider.get_client(context)
@ -392,6 +394,10 @@ def response_to_items(provider,
new_params = dict(context.get_params(),
page_token=yt_next_page_token)
if yt_click_tracking:
new_params['visitor'] = yt_visitor_data
if yt_click_tracking:
new_params['click_tracking'] = yt_click_tracking
new_context = context.clone(new_params=new_params)
current_page = new_context.get_param('page', 1)
next_page_item = NextPageItem(new_context, current_page)

View file

@ -26,17 +26,22 @@ from ...kodion.utils import strip_html_from_text
def _process_related_videos(provider, context):
context.set_content(content.VIDEOS)
video_id = context.get_param('video_id', '')
if not video_id:
return []
function_cache = context.get_function_cache()
json_data = function_cache.get(
provider.get_client(context).get_related_videos,
function_cache.ONE_HOUR,
video_id=video_id,
page_token=context.get_param('page_token', ''),
)
video_id = context.get_param('video_id', '')
if video_id:
json_data = function_cache.get(
provider.get_client(context).get_related_videos,
function_cache.ONE_HOUR,
video_id=video_id,
page_token=context.get_param('page_token', ''),
)
else:
json_data = function_cache.get(
provider.get_client(context).get_related_for_home,
function_cache.ONE_HOUR,
page_token=context.get_param('page_token', ''),
)
if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
@ -72,17 +77,24 @@ def _process_child_comments(provider, context):
def _process_recommendations(provider, context):
context.set_content(content.VIDEOS)
json_data = provider.get_client(context).get_activities(
channel_id='home', page_token=context.get_param('page_token', '')
params = context.get_params()
function_cache = context.get_function_cache()
json_data = function_cache.get(
provider.get_client(context).get_recommended_for_home,
function_cache.ONE_HOUR,
visitor=params.get('visitor', ''),
page_token=params.get('page_token', ''),
click_tracking=params.get('click_tracking', ''),
)
if not json_data:
return False
return v3.response_to_items(provider, context, json_data)
def _process_popular_right_now(provider, context):
def _process_trending(provider, context):
context.set_content(content.VIDEOS)
json_data = provider.get_client(context).get_popular_videos(
json_data = provider.get_client(context).get_trending_videos(
page_token=context.get_param('page_token', '')
)
if not json_data:
@ -295,9 +307,9 @@ def process(category, provider, context):
if category == 'related_videos':
return _process_related_videos(provider, context)
if category == 'popular_right_now':
return _process_popular_right_now(provider, context)
if category == 'recommendations':
if category == 'trending':
return _process_trending(provider, context)
if category == 'recommended':
return _process_recommendations(provider, context)
if category == 'browse_channels':
return _process_browse_channels(provider, context)

View file

@ -1294,22 +1294,31 @@ class Provider(AbstractProvider):
history_id = logged_in and access_manager.get_watch_history_id()
local_history = settings.use_local_history()
# Recommendations
if settings.get_bool('youtube.folder.recommendations.show', True):
# Home / Recommended
if settings.get_bool('youtube.folder.recommended.show', True):
recommendations_item = DirectoryItem(
localize('recommendations'),
create_uri(('special', 'recommended')),
image='{media}/home.png',
)
result.append(recommendations_item)
# Related
if settings.get_bool('youtube.folder.related.show', True):
if history_id and history_id != 'HL' or local_history:
recommendations_item = DirectoryItem(
localize('recommendations'),
create_uri(('special', 'recommendations')),
image='{media}/what_to_watch.png',
related_item = DirectoryItem(
localize('related_videos'),
create_uri(('special', 'related_videos')),
image='{media}/related_videos.png',
)
result.append(recommendations_item)
result.append(related_item)
# Trending
if settings.get_bool('youtube.folder.popular_right_now.show', True):
if settings.get_bool('youtube.folder.trending.show', True):
trending_item = DirectoryItem(
localize('popular_right_now'),
create_uri(('special', 'popular_right_now')),
image='{media}/popular.png',
localize('trending'),
create_uri(('special', 'trending')),
image='{media}/trending.png',
)
result.append(trending_item)

View file

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before After
Before After

View file

@ -416,12 +416,17 @@
<heading>30585</heading>
</control>
</setting>
<setting id="youtube.folder.recommendations.show" type="boolean" label="30551" help="">
<setting id="youtube.folder.recommended.show" type="boolean" label="30551" help="">
<level>0</level>
<default>true</default>
<control type="toggle"/>
</setting>
<setting id="youtube.folder.popular_right_now.show" type="boolean" label="30513" help="">
<setting id="youtube.folder.related.show" type="boolean" label="30514" help="">
<level>0</level>
<default>true</default>
<control type="toggle"/>
</setting>
<setting id="youtube.folder.trending.show" type="boolean" label="30513" help="">
<level>0</level>
<default>true</default>
<control type="toggle"/>