# -*- coding: utf-8 -*- """ Copyright (C) 2017-2025 plugin.video.youtube SPDX-License-Identifier: GPL-2.0-only See LICENSES/GPL-2.0-only for more information. """ from __future__ import absolute_import, division, unicode_literals import re from youtube_plugin.youtube.provider import Provider from youtube_plugin.kodion.context import XbmcContext def __get_core_components(addon_id=None): """ :param addon_id: addon id associated with developer keys to use for requests :return: addon provider, context and client """ provider = Provider() if addon_id is not None: context = XbmcContext(params={'addon_id': addon_id}) else: context = XbmcContext() client = provider.get_client(context=context) return provider, context, client def v3_request(method='GET', headers=None, path=None, post_data=None, params=None, addon_id=None): """ https://developers.google.com/youtube/v3/docs/ :param method: :param headers: :param path: :param post_data: :param params: :param addon_id: addon id associated with developer keys to use for requests :type addon_id: str """ provider, context, client = __get_core_components(addon_id) return client.perform_v3_request(method=method, headers=headers, path=path, post_data=post_data, params=params, notify=False, pass_data=True, raise_exc=False) def _append_missing_page_token(items): if items and isinstance(items, list) and 'nextPageToken' not in items[-1]: items.append({'nextPageToken': ''}) return items def get_videos(video_id, addon_id=None): """ :param video_id: video id(s) :param addon_id: addon id associated with developer keys to use for requests :type video_id: str | list :type addon_id: str :return: list of for the given video id(s) see also https://developers.google.com/youtube/v3/docs/videos#resource :rtype: list of dict """ provider, context, client = __get_core_components(addon_id) json_data = client.get_videos(video_id, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] return json_data.get('items', [{}]) def get_activities(channel_id, page_token='', all_pages=False, addon_id=None): """ :param channel_id: channel id :param page_token: nextPageToken for starting page :param all_pages: return all pages(starting at page_token) or single page :param addon_id: addon id associated with developer keys to use for requests :type channel_id: str :type page_token: str :type all_pages: bool :type addon_id: str :return: list of for the given channel id see also https://developers.google.com/youtube/v3/docs/activities#resource last item contains nextPageToken :rtype: list of dict """ provider, context, client = __get_core_components(addon_id) items = [] def get_items(_page_token=''): json_data = client.get_activities(channel_id, page_token=_page_token, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] items.extend(json_data.get('items', [{}])) error = False next_page_token = json_data.get('nextPageToken') if not next_page_token: return error if all_pages: error = get_items(_page_token=next_page_token) else: items.append({'nextPageToken': next_page_token}) return error error = get_items(_page_token=page_token) if error: return error items = _append_missing_page_token(items) return items def get_playlist_items(playlist_id, page_token='', all_pages=False, addon_id=None): """ :param playlist_id: playlist id :param page_token: nextPageToken for starting page :param all_pages: return all pages(starting at page_token) or single page :param addon_id: addon id associated with developer keys to use for requests :type playlist_id: str :type page_token: str :type all_pages: bool :type addon_id: str :return: list of for the given playlist id see also https://developers.google.com/youtube/v3/docs/playlistItems#resource last item contains nextPageToken :rtype: list of dict """ provider, context, client = __get_core_components(addon_id) items = [] def get_items(_page_token=''): json_data = client.get_playlist_items(playlist_id, page_token=_page_token, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] items.extend(json_data.get('items', [{}])) error = False next_page_token = json_data.get('nextPageToken') if not next_page_token: return error if all_pages: error = get_items(_page_token=next_page_token) else: items.append({'nextPageToken': next_page_token}) return error error = get_items(_page_token=page_token) if error: return error items = _append_missing_page_token(items) return items def get_channel_id(identifier, mine=False, handle=False, addon_id=None): """ :param str identifier: channel username to retrieve channel ID for :param bool mine: treat identifier as request for authenticated user :param bool handle: treat identifier as request for handle :param str addon_id: addon id associated with developer keys to use for requests :return: list of for the given channel name see also https://developers.google.com/youtube/v3/docs/channels#resource :rtype: list of dict """ provider, context, client = __get_core_components(addon_id) json_data = client.get_channel_by_identifier(identifier=identifier, mine=mine, handle=handle, as_json=True, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] return json_data.get('items', [{}]) def get_channels(channel_id, addon_id=None): """ :param channel_id: channel id(s) :param addon_id: addon id associated with developer keys to use for requests :type channel_id: str | list :type addon_id: str :return: list of for the given channel id(s) see also https://developers.google.com/youtube/v3/docs/channels#resource :rtype: list of dict """ provider, context, client = __get_core_components(addon_id) json_data = client.get_channels(channel_id, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] return json_data.get('items', [{}]) def get_channel_sections(channel_id, addon_id=None): """ :param channel_id: channel id :param addon_id: addon id associated with developer keys to use for requests :type channel_id: str :type addon_id: str :return: list of for the given channel id see also https://developers.google.com/youtube/v3/docs/channelSections#resource :rtype: list of dict """ provider, context, client = __get_core_components(addon_id) json_data = client.get_channel_sections(channel_id, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] return json_data.get('items', [{}]) def get_playlists_of_channel(channel_id, page_token='', all_pages=False, addon_id=None): """ :param channel_id: channel id :param page_token: nextPageToken for starting page :param all_pages: return all pages(starting at page_token) or single page :param addon_id: addon id associated with developer keys to use for requests :type channel_id: str :type page_token: str :type all_pages: bool :type addon_id: str :return: list of for the given channel id see also https://developers.google.com/youtube/v3/docs/playlists#resource last item contains nextPageToken :rtype: list of dict """ provider, context, client = __get_core_components(addon_id) items = [] def get_items(_page_token=''): json_data = client.get_playlists_of_channel(channel_id, page_token=_page_token, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] items.extend(json_data.get('items', [{}])) error = False next_page_token = json_data.get('nextPageToken') if not next_page_token: return error if all_pages: error = get_items(_page_token=next_page_token) else: items.append({'nextPageToken': next_page_token}) return error error = get_items(_page_token=page_token) if error: return error items = _append_missing_page_token(items) return items def get_playlists(playlist_id, addon_id=None): """ :param playlist_id: playlist id(s) :param addon_id: addon id associated with developer keys to use for requests :type playlist_id: str | list :type addon_id: str :return: list of for the given playlist id(s) see also https://developers.google.com/youtube/v3/docs/playlists#resource :rtype: list of dict """ provider, context, client = __get_core_components(addon_id) json_data = client.get_playlists(playlist_id, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] return json_data.get('items', [{}]) def get_related_videos(video_id, page_token='', addon_id=None): """ :param video_id: video id :param page_token: nextPageToken for page :param addon_id: addon id associated with developer keys to use for requests :type video_id: str :type page_token: str :type addon_id: str :return: list of for the given video id see also https://developers.google.com/youtube/v3/docs/search#resource last item contains nextPageToken :rtype: list of dict :note: this is a search api request with high cost """ provider, context, client = __get_core_components(addon_id) items = [] def get_items(_page_token=''): json_data = client.get_related_videos(video_id, page_token=_page_token, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] items.extend([item for item in json_data.get('items', [{}]) if 'snippet' in item]) error = False next_page_token = json_data.get('nextPageToken') if next_page_token: items.append({'nextPageToken': next_page_token}) return error error = get_items(_page_token=page_token) if error: return error items = _append_missing_page_token(items) return items def get_search(q, search_type='', event_type='', channel_id='', order='relevance', safe_search='moderate', page_token='', addon_id=None): """ :param q: search query :param search_type: acceptable values are: 'video' | 'channel' | 'playlist', defaults to ['video', 'channel', 'playlist'] :param event_type: 'live', 'completed', 'upcoming' :param channel_id: limit search to channel id :param order: one of: 'date', 'rating', 'relevance', 'title', 'videoCount', 'viewCount' :param safe_search: one of: 'moderate', 'none', 'strict' :param page_token: nextPageToken for page :param addon_id: addon id associated with developer keys to use for requests :type q: str :type search_type: str | list :type event_type: str :type channel_id: str :type order: str :type safe_search: str :type page_token: str :type addon_id: str :return: list of for the given parameters, see also https://developers.google.com/youtube/v3/docs/search#resource last item contains nextPageToken :rtype: list of dict :note: this is a search api request with high cost """ search_type = search_type or ['video', 'channel', 'playlist'] provider, context, client = __get_core_components(addon_id) items = [] def get_items(_page_token=''): json_data = client.search(q, search_type=search_type, event_type=event_type, channel_id=channel_id, order=order, safe_search=safe_search, page_token=_page_token, notify=False, pass_data=True, raise_exc=False) if not json_data or 'error' in json_data: return [json_data] items.extend(json_data.get('items', [{}])) error = False next_page_token = json_data.get('nextPageToken') if next_page_token: items.append({'nextPageToken': next_page_token}) return error error = get_items(_page_token=page_token) if error: return error items = _append_missing_page_token(items) return items def get_live(channel_id=None, user=None, url=None, addon_id=None): """ :param channel_id: a channel id One of channel_id, user, or url required ex. UCLA_DiR1FfKNvjuUpBHmylQ :param user: a channel username One of channel_id, user, or url required ex. NASAtelevision :param url: a channel url One of channel_id, channel_id, or url required ex. https://www.youtube.com/channel/UCLA_DiR1FfKNvjuUpBHmylQ https://www.youtube.com/channel/UCLA_DiR1FfKNvjuUpBHmylQ/live https://www.youtube.com/user/NASAtelevision https://www.youtube.com/user/NASAtelevision/live :param addon_id: addon id associated with developer keys to use for requests :type channel_id: str, optional :type user: str, optional :type url: str, optional :type addon_id: str, optional :return: all live stream items for the given channel :rtype: list of dicts, or None """ if not channel_id and not user and not url: return None matched_id = None matched_type = None live_content = [] if channel_id: matched_id = channel_id matched_type = 'channel' elif user: matched_id = user matched_type = 'user' elif url: patterns = [r'^(?:http)*s*:*[/]{0,2}(?:w{3}\.|m\.)*youtu(?:\.be|be\.com)/' r'(?Pchannel|user)/(?P[^/]+)(?:/live)*$'] for pattern in patterns: match = re.search(pattern, url) if match: matched_id = match.group(CHANNEL_ID) matched_type = match.group('type') break if not matched_id or not matched_type: return None if matched_type == 'user': items = get_channel_id(matched_id, addon_id=addon_id) if not items or not isinstance(items, list) or 'id' not in items[0]: return None matched_id = items[0]['id'] search_results = get_search(q='', search_type='video', event_type='live', channel_id=matched_id, safe_search='none', addon_id=addon_id) if not search_results: return None for search_result in search_results: if 'id' in search_result and 'videoId' in search_result['id'] and 'snippet' in search_result: search_result['snippet']['videoId'] = search_result['id']['videoId'] live_content.append(search_result['snippet']) return live_content