mirror of
https://github.com/anxdpanic/plugin.video.youtube.git
synced 2026-05-30 15:52:19 -07:00
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:
parent
e2e7d1329f
commit
75488c3f2b
14 changed files with 235 additions and 68 deletions
|
|
@ -354,7 +354,7 @@ msgid "Browse Channels"
|
|||
msgstr ""
|
||||
|
||||
msgctxt "#30513"
|
||||
msgid "Popular right now"
|
||||
msgid "Trending"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30514"
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ msgid "Browse Channels"
|
|||
msgstr ""
|
||||
|
||||
msgctxt "#30513"
|
||||
msgid "Popular right now"
|
||||
msgid "Trending"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30514"
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ msgid "Browse Channels"
|
|||
msgstr ""
|
||||
|
||||
msgctxt "#30513"
|
||||
msgid "Popular right now"
|
||||
msgid "Trending"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30514"
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ msgid "Browse Channels"
|
|||
msgstr ""
|
||||
|
||||
msgctxt "#30513"
|
||||
msgid "Popular right now"
|
||||
msgid "Trending"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30514"
|
||||
|
|
|
|||
|
|
@ -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=''):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
resources/media/related_videos.png
Normal file
BIN
resources/media/related_videos.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
|
@ -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"/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue