def getTwitchStreamInfo(self) -> List[Union[bool, HLS]]: arguments = { 'allow_source': 'true', 'allow_spectre': 'true', 'p': randint(1000000, 10000000), 'sig': self.access_token['sig'].encode('utf-8'), 'supported_codecs': 'avc1', 'token': self.access_token['token'].encode('utf-8'), 'cdm': 'wv', } manifest_url = '{0}/api/channel/hls/{1}.m3u8?{2}'.format( self._USHER_START_PATH, self.channel_name, urlencode(arguments)) download_object = download_website( manifest_url, CookieDict=self.sharedCookieDict, headers={ 'Accept': 'application/x-mpegURL, application/vnd.apple.mpegurl, application/json, text/plain' }) if download_object.status_code == 404: # TRANSCODE DOESNT EXIST. THAT IS NORMAL. SO NOT LIVE return [False, None] elif download_object.response_headers[ 'Content-Type'] == "application/vnd.apple.mpegurl": return [True, download_object.parse_m3u8_formats()] print("Unable To Handle Status: {0} For Twitch.".format( download_object.status_code)) return [False, None]
def __callAPI__(self, path): url_referer = 'https://www.twitch.tv/{0}'.format(self.channel_name) download_object = download_website( '{0}/{1}'.format(self._API_START_PATH, path), headers={ 'DNT': '1', 'Referer': url_referer, 'Sec-Fetch-Mode': 'cors', 'Client-ID': self.globalVariables.get("client_id") }, CookieDict=self.sharedCookieDict) return download_object.parse_json()
def loadVideoData(self): url = "https://www.twitch.tv/{0}".format(self.channel_name) download_object = download_website(url, CookieDict=self.sharedCookieDict) if download_object.status_code == 404: return [ False, "Failed getting Twitch Data! \"{0}\" doesn't exist as a channel name!" .format(self.channel_name) ] if download_object.text is None: return [ False, "Failed getting Youtube Data from the internet! " "This means there is no good internet available!" ] if self.globalVariables.get("client_id") is None: verbose("Getting Client ID. [TWITCH]") okay, client_id = find_client_id(download_object.text) if okay is False: warning(client_id) self.globalVariables.set("client_id", client_id) verbose('Getting Channel ID. [TWITCH]') self.access_token = self.__callAPI__( 'api/channels/{0}/access_token?{1}&oauth_token'.format( self.channel_name, urlencode({ 'need_https': 'true', 'platform': 'web', 'player_backend': 'mediaplayer', 'player_type': 'site' }))) token = parse_json(self.access_token['token']) self.channel_id = try_get(token, lambda x: x['channel_id']) # website_dict = self.__callAPI__('kraken/channels/{0}'.format( # self.channel_name)) # self.channel_image = try_get(website_dict, lambda x: x['logo']) # self.channel_id = try_get(website_dict, lambda x: x['_id'], int) self.live_streaming, hls = self.getTwitchStreamInfo() if hls is not None: self.StreamFormat = get_format_from_data( hls, self.cachedDataHandler.getValue('recordingResolution')) if not self.channel_id: return [False, "Unable to find Channel ID."] return [True, "OK"]
def get_sponsor_channel(self, html_code=None): # from .. import is_google_account_login_in if True: verbose("Checking if account sponsored {0}.".format( self.channel_name)) if html_code is None: html_code = download_website( "https://www.youtube.com/channel/{0}/live".format( self.channel_id), CookieDict=self.sharedCookieDict) if html_code is None: return None html_code = str(html_code) array = re.findall( '/channel/{0}/membership'.format(self.channel_id), html_code) if array: return True return False
def searchChannels(search, CookieDict, globalVariables): """ :type CookieDict: dict :type globalVariables: GlobalVariables """ def formatChannel(channel): return { 'channel_identifier': try_get(channel, lambda x: x['displayName'], str), 'channel_name': try_get(channel, lambda x: x['displayName'], str), 'channel_image': try_get(channel, lambda x: x['profileImageURL'], str), 'follower_count': str(try_get(channel, lambda x: x['followers']['totalCount'], int)), 'platform': 'TWITCH' } referer = 'https://www.twitch.tv/search?term={0}&type=channels'.format( search) client_id = globalVariables.get("client_id") if client_id is None: verbose("Getting Client ID. [TWITCH]") downloadOBJECT = download_website(referer, CookieDict=CookieDict) okay, client_id = find_client_id(downloadOBJECT.text) if okay is False: warning(client_id) globalVariables.set("client_id", client_id) data = [{ "operationName": "SearchResultsPage_SearchResults", "variables": { "query": search, "options": { "targets": [{ "index": "CHANNEL" }] } }, "extensions": { "persistedQuery": { "version": 1, "sha256Hash": '1d3ca64005f07f8e34a33677d119ba8601e7deac745ea127e67b7925535ed735' } } }] # TODO: reverse the sha265Hash. :p downloadOBJECT = download_website('https://gql.twitch.tv/gql', RequestMethod='POST', data=data, headers={ 'Origin': 'https://www.twitch.tv', 'Referer': referer, 'Client-Id': client_id, 'Content-Length': '225' }, CookieDict=CookieDict) data = try_get(downloadOBJECT.parse_json(), lambda x: x[0]['data'], dict) items = try_get(data, lambda x: x['searchFor']['channels']['items'], list) channels = list(map(formatChannel, items)) return [True, {'channels': channels}]
def is_live(channel_Class, CookieDict=None, globalVariables=None, json=None): """ Checks if channel is live using the normal Youtube heartbeat. Also sets heartbeat related variables. :type CookieDict: dict :type channel_Class: TemplateChannel :type globalVariables: GlobalVariables """ if globalVariables is None: globalVariables = TempGlobalVariables() try: if json is None: referer_url = 'https://www.youtube.com/channel/{0}/live'.format( channel_Class.channel_id) headers = { 'Accept': "*/*", 'Accept-Language': 'en-US,en;q=0.9', 'dnt': '1', 'referer': referer_url, 'x-youtube-client-name': '1' } url_arguments = { 'video_id': channel_Class.video_id, 'heartbeat_token': '', 'c': (globalVariables.get("client_name") if globalVariables.get("client_name") is not None else 'WEB'), 'sequence_number': str(channel_Class.sequence_number) } if globalVariables.get("account_playback_token") is not None: headers.update({ 'x-youtube-identity-token': globalVariables.get("account_playback_token") }) if globalVariables.get("page_build_label") is not None: headers.update({ 'x-youtube-page-label': globalVariables.get("page_build_label") }) if globalVariables.get("page_cl") is not None: headers.update( {'x-youtube-page-cl': globalVariables.get("page_cl")}) if globalVariables.get("variants_checksum") is not None: headers.update({ 'x-youtube-variants-checksum': globalVariables.get("variants_checksum") }) if globalVariables.get("utf_offset") is not None: headers.update({ 'x-youtube-utc-offset': str(globalVariables.get("utf_offset")) }) url_arguments.update({ 'utc_offset_minutes': str(globalVariables.get("utf_offset")) }) if globalVariables.get("client_version") is not None: headers.update({ 'x-youtube-client-version': globalVariables.get("client_version") }) url_arguments.update( {'cver': str(globalVariables.get("client_version"))}) if globalVariables.get("timezone") is not None: url_arguments.update( {'time_zone': str(globalVariables.get("timezone"))}) if channel_Class.cpn is not None: url_arguments.update({'cpn': channel_Class.cpn}) websiteClass = download_website( 'https://www.youtube.com/heartbeat?{0}'.format( urlencode(url_arguments)), headers=headers, CookieDict=CookieDict) CookieDict.update(websiteClass.cookies) if websiteClass.text is None: return None json = websiteClass.parse_json() channel_Class.sequence_number += 1 reply('FROM YOUTUBE -> {0}'.format(json)) # SETTING VARIABLES liveStreamAbilityRenderer = try_get( json, lambda x: x['liveStreamability']['liveStreamabilityRenderer'], dict) if liveStreamAbilityRenderer: thumbnail = get_thumbnail(liveStreamAbilityRenderer) if thumbnail: channel_Class.thumbnail_url = thumbnail channel_Class.pollDelayMs = get_poll_delay_ms( liveStreamAbilityRenderer, channel_Class) channel_Class.live_scheduled = get_unix_schedule_time( liveStreamAbilityRenderer) is not None channel_Class.broadcast_id = get_broadcast_id( liveStreamAbilityRenderer) video_id = get_video_id(liveStreamAbilityRenderer) if video_id: if video_id != channel_Class.video_id: channel_Class.add_youtube_queue( ) # just in case something happens. channel_Class.video_id = video_id if channel_Class.live_scheduled is True: channel_Class.live_scheduled_timeString = get_schedule_time( liveStreamAbilityRenderer) unix_time = get_unix_schedule_time(liveStreamAbilityRenderer) if unix_time: channel_Class.live_scheduled_time = datetime.fromtimestamp( unix_time) if 'stop_heartbeat' in json: channel_Class.add_youtube_queue() channel_Class.loadVideoData() return False if try_get(liveStreamAbilityRenderer, lambda x: x['displayEndscreen'], bool): last_video_id = channel_Class.video_id channel_Class.loadVideoData() # CHECK IF A VIDEO ID CHANGE BEFORE ADDING. if last_video_id != channel_Class.video_id: channel_Class.add_youtube_queue() return False status = try_get(json, lambda x: x['status'], str) # type: str if status: # Sometimes status is removed and causes an error. if "OK" in status.upper(): return True if "STOP" in status.upper(): channel_Class.add_youtube_queue() channel_Class.loadVideoData() return False if "ERROR" in status.upper(): warning("Getting the Live Data, failed on Youtube's Side. " "Youtube Replied with: {0}.".format(json['reason'])) return False if "LIVE_STREAM_OFFLINE" in status.upper(): return False warning( "The Program couldn't find any value that matches the normal heartbeat. Returning False." ) return False except KeyboardInterrupt: pass except BrokenPipeError: exit() except Exception: warning("Error occurred when doing Heartbeat.") error_warning(traceback.format_exc()) return -1
def loadVideoData(self, video_id=None): self.video_id = video_id if video_id: url = "https://www.youtube.com/watch?v={0}".format(video_id) else: url = "https://www.youtube.com/channel/{0}/live".format( self.channel_id) website_object = download_website(url, CookieDict=self.sharedCookieDict) # self.sharedCookieDict.update(websiteClass.cookies) if website_object.text is None: return [ False, "Failed getting Youtube Data from the internet! " "This means there is no good internet available!" ] if website_object.status_code == 404: return [ False, "Failed getting Youtube Data! \"{0}\" doesn't exist as a channel id!" .format(self.channel_id) ] website_string = website_object.text endpoint_type = get_endpoint_type(website_string) if endpoint_type: if endpoint_type == 'browse': array = re.findall(r'property="og:title" content="(.+?)"', website_string) if array: channel_name = array[0] warning( "{0} has the live stream " "currently unlisted or private, or only for members. " "Using safeguard. This may not be the best to leave on.\n" .format(channel_name)) self.channel_name = channel_name self.video_id = None self.privateStream = True else: if not endpoint_type == 'watch': warning("Unrecognized endpoint type. Endpoint Type: {0}.". format(endpoint_type)) verbose("Getting Video ID.") youtube_initial_data = get_yt_initial_data(website_string) yt_player_config = try_get( get_yt_player_config(website_string), lambda x: x, dict) player_response = parse_json( try_get(yt_player_config, lambda x: x['args']['player_response'], str)) videoDetails = try_get(player_response, lambda x: x['videoDetails'], dict) if yt_player_config and videoDetails: if "isLiveContent" in videoDetails and \ videoDetails['isLiveContent'] and \ ("isLive" in videoDetails or "isUpcoming" in videoDetails): self.channel_name = try_get(videoDetails, lambda x: x['author'], str) self.video_id = try_get(videoDetails, lambda x: x['videoId'], str) self.privateStream = False if not self.channel_id: self.channel_id = try_get(videoDetails, lambda x: x['channelId'], str) else: return [ False, "Found a stream, the stream seemed to be a non-live stream." ] else: return [ False, "Unable to get yt player config, and videoDetails." ] contents = try_get( youtube_initial_data, lambda x: x['contents']['twoColumnWatchNextResults'][ 'results']['results']['contents'], list) videoSecondaryInfoRenderer = try_get([ content for content in contents if content.get("videoSecondaryInfoRenderer") is not None ], lambda x: x[0], dict).get("videoSecondaryInfoRenderer") channelImageFormats = try_get( videoSecondaryInfoRenderer, lambda x: x['owner'][ 'videoOwnerRenderer']['thumbnail']['thumbnails'], list) if channelImageFormats is not None: self.channel_image = max( channelImageFormats, key=lambda x: x.get("height")).get("url") if not self.privateStream: # TO AVOID REPEATING REQUESTS. if player_response: # playabilityStatus is legit heartbeat all over again.. playabilityStatus = try_get( player_response, lambda x: x['playabilityStatus'], dict) status = try_get(playabilityStatus, lambda x: x['status'], str) # type: str reason = try_get(playabilityStatus, lambda x: x['reason'], str) # type: str if playabilityStatus and status: if 'OK' in status.upper(): if reason and 'ended' in reason: return [False, reason] streamingData = try_get( player_response, lambda x: x['streamingData'], dict) if streamingData: if 'licenseInfos' in streamingData: licenseInfo = streamingData.get( 'licenseInfos') drmFamilies = map( lambda x: x.get('drmFamily'), licenseInfo) return [ False, "This live stream contains DRM and cannot be recorded.\n" "DRM Families: {0}".format( ', '.join(drmFamilies)) ] manifest_url = str( try_get(streamingData, lambda x: x['hlsManifestUrl'], str)) if not manifest_url: return [ False, "Unable to find HLS Manifest URL." ] downloadOBJECT = download_website( manifest_url, CookieDict=self.sharedCookieDict) hls = downloadOBJECT.parse_m3u8_formats() if len(hls.formats) == 0: return [ False, "There were no formats found! Even when the streamer is live." ] format_ = get_format_from_data( hls, self.cachedDataHandler.getValue( 'recordingResolution')) if not videoDetails: videoDetails = try_get( player_response, lambda x: x['videoDetails'], dict) thumbnails = try_get( videoDetails, lambda x: x['thumbnail']['thumbnails'], list) if thumbnails: self.thumbnail_url = get_highest_thumbnail( thumbnails) self.dvr_enabled = try_get( videoDetails, lambda x: x['isLiveDvrEnabled'], bool) self.StreamFormat = format_ self.title = try_get( videoDetails, lambda x: x['title'], str) self.description = videoDetails[ 'shortDescription'] else: return [ False, "No StreamingData, YouTube bugged out!" ] self.live_streaming = self.is_live( json=playabilityStatus) # GET YOUTUBE GLOBAL VARIABLES if self.globalVariables.get( "checkedYouTubeVariables") is None: def getSettingsValue(ServiceSettings, settings_nameLook, name=None): for service in ServiceSettings: service_name = try_get(service, lambda x: x['key'], str) if service_name is not None and service_name in settings_nameLook: value = try_get(service, lambda x: x['value'], str) if name: if not value: warning( "Something happened when finding the " + name) return None return value return None def getServiceSettings(serviceTrackingParamsList, service_nameLook): if serviceTrackingParamsList: for service in serviceTrackingParamsList: service_name = try_get( service, lambda x: x['service'], str) if service_name is not None and service_name in service_nameLook: return service return None if self.globalVariables.get( "alreadyChecked" ) is False or self.globalVariables.get( "alreadyChecked") is None: verbose("Getting Global YouTube Variables.") e_catcher = getServiceSettings( try_get( youtube_initial_data, lambda x: x['responseContext'][ 'serviceTrackingParams'], list), "ECATCHER") account_playback_token = try_get( yt_player_config, lambda x: x['args'][ 'account_playback_token'][:-1], str) ps = try_get(yt_player_config, lambda x: x['args']['ps'], str) cbr = try_get(yt_player_config, lambda x: x['args']['cbr']) client_os = try_get(yt_player_config, lambda x: x['args']['cos']) client_os_version = try_get( yt_player_config, lambda x: x['args']['cosver']) if account_playback_token is None: warning( "Unable to find account playback token in the YouTube player config." ) if ps is None: warning( "Unable to find ps in the YouTube player config." ) if cbr is None: warning( "Unable to find cbr in the YouTube player config." ) if client_os is None: warning( "Unable to find Client OS in the YouTube player config." ) if client_os_version is None: warning( "Unable to find Client OS Version in the YouTube player config." ) self.globalVariables.set("checkedYouTubeVariables", None) if not youtube_initial_data: warning( "Unable to get Youtube Initial Data. Cannot find all Youtube Variables." ) elif e_catcher is None: warning( "Unable to get ECATCHER service data in Youtube Initial Data. " "Cannot find all Youtube Variables.") else: params = try_get(e_catcher, lambda x: x['params'], list) page_build_label = getSettingsValue( params, 'innertube.build.label', name="Page Build Label") page_cl = getSettingsValue( params, 'innertube.build.changelist', name="Page CL") variants_checksum = getSettingsValue( params, 'innertube.build.variants.checksum', name="Variants Checksum") client_version = getSettingsValue( params, 'client.version', name="Client Version") client_name = getSettingsValue( params, 'client.name', name="Client Name") self.globalVariables.set( "page_build_label", page_build_label) self.globalVariables.set("page_cl", page_cl) self.globalVariables.set( "client_version", client_version) self.globalVariables.set( "client_name", client_name) self.globalVariables.set( "variants_checksum", variants_checksum) self.globalVariables.set("ps", ps) self.globalVariables.set("cbr", cbr) self.globalVariables.set("client_os", client_os) self.globalVariables.set("client_os_version", client_os_version) self.globalVariables.set("account_playback_token", account_playback_token) self.globalVariables.set("utf_offset", get_utc_offset()) self.globalVariables.set("timezone", getTimeZone()) self.globalVariables.set("alreadyChecked", True) # ONLY WORKS IF LOGGED IN self.sponsor_on_channel = self.get_sponsor_channel( html_code=website_string) self.cpn = self.generate_cpn() return [True, "OK"]
def searchChannels(search, CookieDict): def formatChannel(channelContent): channelRenderer = channelContent.get("channelRenderer") channel_image = None channelImageFormats = try_get(channelRenderer, lambda x: x['thumbnail']['thumbnails'], list) if channelImageFormats is not None: channel_image = "https:{0}".format( max(channelImageFormats, key=lambda x: x.get("height")).get("url")) channel = { 'channel_identifier': try_get(channelRenderer, lambda x: x['channelId'], str), 'channel_name': try_get(channelRenderer, lambda x: x['title']['simpleText'], str), 'follower_count': try_get(channelRenderer, lambda x: x['subscriberCountText']['simpleText'], str), 'channel_image': channel_image, 'platform': 'YOUTUBE' } return channel def formatDidYouMean(didYouMeanContent): didYouMeanRenderer = didYouMeanContent.get("didYouMeanRenderer") didYouMean = { 'didYouMean': try_get(didYouMeanRenderer, lambda x: x['didYouMean']['runs'][0]['text'], str), 'correctedQuery': try_get(didYouMeanRenderer, lambda x: x['correctedQuery']['runs'][0]['text'], str) } return didYouMean # For some stupid reason, YouTube will only provide Searches on BR Content Encoding downloadOBJECT = download_website( "https://www.youtube.com/results?{0}".format( urlencode({ 'search_query': search, 'sp': 'EgIQAg%3D%3D' })), headers={ 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', }, CookieDict=CookieDict) if downloadOBJECT.response_headers.get('Content-Encoding') == 'br': # Check for support for that. try: import brotli except ImportError: return [ False, 'No Support for BR Content Encoding on Server. Required Packages: requests, brotlipy.' ] websiteString = downloadOBJECT.text youtube_initial_data = get_yt_initial_data(websiteString) if youtube_initial_data is None: return [False, 'Unable to find YouTube initial data.'] contents = try_get( youtube_initial_data, lambda x: x['contents']['twoColumnSearchResultsRenderer'][ 'primaryContents']['sectionListRenderer']['contents'], list) if contents is None: return [False, 'Unable to find contents.'] itemSectionRenderer = try_get([ content for content in contents if content.get("itemSectionRenderer") is not None ] or [], lambda x: x[0], dict).get("itemSectionRenderer") if itemSectionRenderer is None: return [False, 'Unable to find itemSectionRenderer.'] contents = itemSectionRenderer.get("contents") if itemSectionRenderer is None: return [False, "Unable to find itemSectionRenderer contents."] channels = list( map(formatChannel, [x for x in contents if 'channelRenderer' in x])) response = {'channels': channels} didYouMean = list( map(formatDidYouMean, [x for x in contents if 'didYouMeanRenderer' in x])) if len(didYouMean) > 0: response.update({'didYouMean': didYouMean}) return [True, response]
def get_video_info(self): """ Gets the stream info from channelClass. Looked at for reference: https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L1675 """ url_arguments = {'html5': 1, 'video_id': self.video_id} if self.globalVariables.get("ps") is not None: url_arguments.update({'ps': self.globalVariables.get("ps")}) url_arguments.update({'eurl': ''}) url_arguments.update({'hl': 'en_US'}) if self.globalVariables.get("client_name") is not None: url_arguments.update( {'c': self.globalVariables.get("client_name")}) if self.globalVariables.get("cbr") is not None: url_arguments.update({'cbr': self.globalVariables.get("cbr")}) if self.globalVariables.get("client_version") is not None: url_arguments.update( {'cver': self.globalVariables.get("client_version")}) if self.globalVariables.get("client_os") is not None: url_arguments.update( {'cos': self.globalVariables.get("client_os")}) if self.globalVariables.get("client_os_version") is not None: url_arguments.update( {'cosver': self.globalVariables.get("client_os_version")}) if self.cpn is not None: url_arguments.update({'cpn': self.cpn}) downloadClass = download_website( 'https://www.youtube.com/get_video_info?{0}'.format( urlencode(url_arguments)), CookieDict=self.sharedCookieDict) video_info_website = downloadClass.text video_info = parse_qs(video_info_website) player_response = parse_json( try_get(video_info, lambda x: x['player_response'][0], str)) if player_response: video_details = try_get(player_response, lambda x: x['videoDetails'], dict) if "streamingData" not in player_response: warning("No StreamingData, Youtube bugged out!") return None manifest_url = str( try_get(player_response, lambda x: x['streamingData']['hlsManifestUrl'], str)) if not manifest_url: warning("Unable to find HLS Manifest URL.") return None downloadOBJECT = download_website(manifest_url, CookieDict=self.sharedCookieDict) if downloadOBJECT.status_code != 200: return None hls = downloadOBJECT.parse_m3u8_formats() if len(hls.formats) == 0: warning( "There were no formats found! Even when the streamer is live." ) return None return { 'formats': hls, 'manifest_url': manifest_url, 'video_details': video_details, } return None