def _hello(self): log.debug('_hello ...') app_token = self.session.http.get( '{0}/token.json'.format(self.base_url), schema=validate.Schema(validate.parse_json(), { 'success': bool, 'session_token': validate.text, }, validate.get('session_token'))) if self._uuid: __uuid = self._uuid else: __uuid = str(uuid.uuid4()) self._session_attributes.set('uuid', __uuid, expires=self.TIME_SESSION) params = { 'app_version': '3.2120.1', 'client_app_token': app_token, 'format': 'json', 'lang': 'en', 'uuid': __uuid, } res = self.session.http.post( '{0}/zapi/v3/session/hello'.format(self.base_url), headers=self.headers, data=params, schema=validate.Schema( validate.parse_json(), validate.any({'active': bool}, {'success': bool}))) if res.get('active') or res.get('success'): log.debug('Hello was successful.') else: log.debug('Hello failed.')
def encrypt_password(self, email, password): """ Get the RSA key for the user and encrypt the users password :param email: steam account :param password: password for account :return: encrypted password """ rsadata = self.session.http.get( self._get_rsa_key_url, params=dict( username=email, donotcache=str(int(time.time() * 1000)) ), schema=validate.Schema( validate.parse_json(), { "publickey_exp": validate.all(validate.text, validate.transform(lambda x: int(x, 16))), "publickey_mod": validate.all(validate.text, validate.transform(lambda x: int(x, 16))), "success": True, "timestamp": validate.text, "token_gid": validate.text } ) ) rsa = RSA.construct((rsadata["publickey_mod"], rsadata["publickey_exp"])) cipher = PKCS1_v1_5.new(rsa) return base64.b64encode(cipher.encrypt(password.encode("utf8"))), rsadata["timestamp"]
def _get_hls_streams(self, url, restricted_bitrates, **extra_params): time_offset = self.params.get("t", 0) if time_offset: try: time_offset = hours_minutes_seconds(time_offset) except ValueError: time_offset = 0 try: streams = TwitchHLSStream.parse_variant_playlist( self.session, url, start_offset=time_offset, **extra_params) except IOError as err: err = str(err) if "404 Client Error" in err or "Failed to parse playlist" in err: return else: raise PluginError(err) for name in restricted_bitrates: if name not in streams: log.warning( "The quality '{0}' is not available since it requires a subscription." .format(name)) return streams
def test_variant_playlist(self): streams = self.subject("hls/test_master.m3u8") self.assertEqual( [str(key) for key in streams.keys()], [u"720p", u"720p_alt", u"480p", u"360p", u"160p", u"1080p (source)", u"90k"], "Finds all streams in master playlist", ) self.assertTrue(all([isinstance(stream, HLSStream) for stream in streams.values()]), "Returns HLSStream instances")
def parse_tag_ext_x_daterange(self, value): super(TwitchM3U8Parser, self).parse_tag_ext_x_daterange(value) daterange = self.m3u8.dateranges[-1] is_ad = (daterange.classname == "twitch-stitched-ad" or str(daterange.id or "").startswith("stitched-ad-") or any( attr_key.startswith("X-TV-TWITCH-AD-") for attr_key in daterange.x.keys())) if is_ad: self.m3u8.dateranges_ads.append(daterange)
def _get_streams(self): root = self.session.http.get(self.url, schema=validate.Schema( validate.parse_html() )) video_id = root.xpath("string(.//div[@data-provider='dvideo'][@data-id][1]/@data-id)") if video_id: return self._get_streams_api(str(video_id)) yt_id = root.xpath("string(.//script[contains(@src,'/yt.js')][@data-video]/@data-video)") if yt_id: return self.session.streams("https://www.youtube.com/watch?v={0}".format(yt_id)) yt_iframe = root.xpath("string(.//iframe[starts-with(@src,'https://www.youtube.com/')][1]/@src)") if yt_iframe: return self.session.streams(str(yt_iframe)) delfi = root.xpath("string(.//iframe[@name='delfi-stream'][@src][1]/@src)") if delfi: return self._get_streams_delfi(str(delfi))
def __init__(self, *args, **kwargs): SegmentedStreamWorker.__init__(self, *args, **kwargs) self.stream = self.reader.stream self.playlist_changed = False self.playlist_end = None self.playlist_sequence = -1 self.playlist_sequences = [] self.playlist_reload_time = 15 self.playlist_reload_time_override = self.session.options.get( "hls-playlist-reload-time") self.playlist_reload_retries = self.session.options.get( "hls-playlist-reload-attempts") self.live_edge = self.session.options.get("hls-live-edge") self.duration_offset_start = int(self.stream.start_offset + ( self.session.options.get("hls-start-offset") or 0)) self.duration_limit = self.stream.duration or ( int(self.session.options.get("hls-duration")) if self.session.options.get("hls-duration") else None) self.hls_live_restart = self.stream.force_restart or self.session.options.get( "hls-live-restart") if str(self.playlist_reload_time_override).isnumeric() and float( self.playlist_reload_time_override) >= 2: self.playlist_reload_time_override = float( self.playlist_reload_time_override) elif self.playlist_reload_time_override not in [ "segment", "live-edge" ]: self.playlist_reload_time_override = 0 self.reload_playlist() if self.playlist_end is None: if self.duration_offset_start > 0: log.debug( "Time offsets negative for live streams, skipping back {0} seconds", self.duration_offset_start) # live playlist, force offset durations back to None self.duration_offset_start = -self.duration_offset_start if self.duration_offset_start != 0: self.playlist_sequence = self.duration_to_sequence( self.duration_offset_start, self.playlist_sequences) if self.playlist_sequences: log.debug("First Sequence: {0}; Last Sequence: {1}", self.playlist_sequences[0].num, self.playlist_sequences[-1].num) log.debug( "Start offset: {0}; Duration: {1}; Start Sequence: {2}; End Sequence: {3}", self.duration_offset_start, self.duration_limit, self.playlist_sequence, self.playlist_end)
def _format(self, string, mapper, defaults): # type: (str, Callable[[str], str], Dict[str, str]) -> str result = [] for literal_text, field_name, format_spec, conversion in _stringformatter.parse( string): if literal_text: result.append(literal_text) if field_name is None: continue value = self._get_value(field_name, format_spec, defaults) result.append(mapper(str(value))) return "".join(result)
def _supports_param(self, param, timeout=5.0): try: rtmpdump = self.spawn(dict(help=True), timeout=timeout, stderr=subprocess.PIPE) except StreamError as err: raise StreamError("Error while checking rtmpdump compatibility: {0}".format(err.message)) for line in rtmpdump.stderr.readlines(): m = re.match(r"^--(\w+)", str(line, "ascii")) if not m: continue if m.group(1) == param: return True return False
def _supports_param(self, param, timeout=5.0): try: rtmpdump = self.spawn(dict(help=True), timeout=timeout, stderr=subprocess.PIPE) except StreamError as err: raise StreamError("Error while checking rtmpdump compatibility: {0}".format(err.message)) for line in rtmpdump.stderr.readlines(): m = re.match(r"^--(\w+)", str(line, "ascii")) if not m: continue if m.group(1) == param: return True return False
def generate(self): if not self.stream.swf: raise StreamError("A SWF URL is required to create session token") res = self.stream.session.http.get(self.stream.swf, exception=StreamError) data = swfdecompress(res.content) md5 = hashlib.md5() md5.update(data) data = bytes(self.stream.sessionid, "ascii") + md5.digest() sig = hmac.new(b"foo", data, hashlib.sha1) b64 = base64.encodestring(sig.digest()) token = str(b64, "ascii").replace("\n", "") return token
def handshake(self, fd): try: self.flv = FLV(fd) except FLVError as err: raise StreamError(str(err)) self.buffer.write(self.flv.header.serialize()) log.debug("Attempting to handshake") for i, tag in enumerate(self.flv): if i == 10: raise StreamError( "No OnEdge metadata in FLV after 10 tags, probably not a AkamaiHD stream" ) self.process_tag(tag, exception=StreamError) if self.completed_handshake: log.debug("Handshake successful") break
def _update_redirect(self, stderr): tcurl, redirect = None, None stderr = str(stderr, "utf8") m = re.search(r"DEBUG: Property: <Name:\s+redirect,\s+STRING:\s+(\w+://.+?)>", stderr) if m: redirect = m.group(1) if redirect: log.debug("Found redirect tcUrl: {0}", redirect) if "rtmp" in self.parameters: tcurl, playpath = rtmpparse(self.parameters["rtmp"]) if playpath: rtmp = "{redirect}/{playpath}".format(redirect=redirect, playpath=playpath) else: rtmp = redirect self.parameters["rtmp"] = rtmp if "tcUrl" in self.parameters: self.parameters["tcUrl"] = redirect
def _update_redirect(self, stderr): tcurl, redirect = None, None stderr = str(stderr, "utf8") m = re.search(r"DEBUG: Property: <Name:\s+redirect,\s+STRING:\s+(\w+://.+?)>", stderr) if m: redirect = m.group(1) if redirect: log.debug("Found redirect tcUrl: {0}", redirect) if "rtmp" in self.parameters: tcurl, playpath = rtmpparse(self.parameters["rtmp"]) if playpath: rtmp = "{redirect}/{playpath}".format(redirect=redirect, playpath=playpath) else: rtmp = redirect self.parameters["rtmp"] = rtmp if "tcUrl" in self.parameters: self.parameters["tcUrl"] = redirect
def _get_streams(self): is_live = self.match.group("is_live") parsed_html = self.session.http.get(self.url, schema=validate.Schema(validate.parse_html())) if is_live: uvid = self._get_live_uvid(parsed_html) if not uvid or not uvid.isdecimal(): return token_data = self._get_tokenizer("live", uvid) self.id = uvid self.author = "Blaze" self.title = "Live TV" self.category = "Live" else: data = self._get_vod_uvid(parsed_html) if not data["id"] or not data["id"].isdecimal(): return token_data = self._get_tokenizer("replay", data["id"]) self.id = data["id"] self.author = data["series_title"] self.title = data["title"] self.category = "S{0}E{1}".format(data['season'], data['episode']) log.trace("token_data={0!r}".format(token_data)) hls_url = self.session.http.get( token_data["url"], headers={ "Token": token_data["token"], "Token-Expiry": str(token_data["expiry"]), "Uvid": token_data["uvid"], }, schema=validate.Schema( validate.parse_json(), {"Streams": {"Adaptive": validate.url()}}, validate.get(("Streams", "Adaptive")), ), ) return HLSStream.parse_variant_playlist(self.session, hls_url)
def open(self): self.guid = cache_bust_string(12) self.islive = None self.sessionid = None self.flv = None self.buffer = Buffer() self.completed_handshake = False url = self.StreamURLFormat.format(host=self.host, streamname=self.streamname) params = self._create_params(seek=self.seek) log.debug("Opening host={} streamname={}", self.host, self.streamname) try: res = self.session.http.get(url, stream=True, params=params) self.fd = StreamIOIterWrapper(res.iter_content(8192)) except Exception as err: raise StreamError(str(err)) self.handshake(self.fd) return self
def _get_api_data(self, type, slug, filter=None): log.debug("slug={0}".format(slug)) app_version = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string( ".//head/meta[@name='appVersion']/@content"), validate.any(None, validate.text), ), ) if not app_version: return log.debug("app_version={0}".format(app_version)) return self.session.http.get( "https://boot.pluto.tv/v4/start", params={ "appName": "web", "appVersion": app_version, "deviceVersion": "94.0.0", "deviceModel": "web", "deviceMake": "firefox", "deviceType": "web", "clientID": str(uuid4()), "clientModelNumber": "1.0", type: slug, }, schema=validate.Schema( validate.parse_json(), { "servers": { "stitcher": validate.url(), }, validate.optional("EPG"): [{ "name": validate.text, "id": validate.text, "slug": validate.text, "stitched": { "path": validate.text, }, }], validate.optional("VOD"): [{ "name": validate.text, "id": validate.text, "slug": validate.text, "genre": validate.text, "stitched": { "path": validate.text, }, validate.optional("seasons"): [{ "episodes": validate.all( [{ "name": validate.text, "_id": validate.text, "slug": validate.text, "stitched": { "path": validate.text, }, }], validate.filter( lambda k: filter and k["slug"] == filter), ), }], }], "sessionToken": validate.text, "stitcherParams": validate.text, }, ), )
def get_id(self): # type: () -> Optional[str] return None if self.id is None else str(self.id).strip()
def get_title(self): # type: () -> Optional[str] return None if self.title is None else str(self.title).strip()
def get_author(self): # type: () -> Optional[str] return None if self.author is None else str(self.author).strip()
def get_category(self): # type: () -> Optional[str] return None if self.category is None else str(self.category).strip()
def time(self): res = self.session.http.get(self.get_time_url) data = self.session.http.json(res) return str(data.get("serverTime", int(time.time() * 1000)))