def __init__(self, url): super(WWENetwork, self).__init__(url) http.headers.update({"User-Agent": useragents.CHROME}) self._session_attributes = Cache(filename="plugin-cache.json", key_prefix="wwenetwork:attributes") self._session_key = self.cache.get("session_key") self._authed = self._session_attributes.get( "ipid") and self._session_attributes.get("fprt")
def __init__(self, url): super(ABweb, self).__init__(url) self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='abweb:attributes') self._authed = self._session_attributes.get( 'ASP.NET_SessionId') and self._session_attributes.get( '.abportail1') self._expires = self._session_attributes.get( 'expires', time.time() + self.expires_time)
def __init__(self, url): super(Zattoo, self).__init__(url) self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='zattoo:attributes') self._authed = self._session_attributes.get('beaker.session.id') and self._session_attributes.get('pzuid') and self._session_attributes.get('power_guide_hash') self._uuid = self._session_attributes.get('uuid') self._expires = self._session_attributes.get('expires', 946684800) self.base_url = 'https://{0}'.format(Zattoo._url_re.match(url).group('base_url')) self.headers = { 'User-Agent': useragents.CHROME, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': self.base_url }
def check_version(force=False): cache = Cache(filename="cli.json") latest_version = cache.get("latest_version") if force or not latest_version: res = requests.get("https://pypi.python.org/pypi/streamlink/json") data = res.json() latest_version = data.get("info").get("version") cache.set("latest_version", latest_version, (60 * 60 * 24)) version_info_printed = cache.get("version_info_printed") if not force and version_info_printed: return installed_version = StrictVersion(streamlink.version) latest_version = StrictVersion(latest_version) if latest_version > installed_version: log.info("A new version of Streamlink ({0}) is " "available!".format(latest_version)) cache.set("version_info_printed", True, (60 * 60 * 6)) elif force: log.info("Your Streamlink version ({0}) is up to date!".format( installed_version)) if force: sys.exit()
def _pv_params(cls, session, pvswf, pv, **request_params): """Returns any parameters needed for Akamai HD player verification. Algorithm originally documented by KSV, source: http://stream-recorder.com/forum/showpost.php?p=43761&postcount=13 """ try: data, hdntl = pv.split(";") except ValueError: data = pv hdntl = "" cache = Cache(filename="stream.json") key = "akamaihd-player:" + pvswf cached = cache.get(key) request_params = deepcopy(request_params) headers = request_params.pop("headers", {}) if cached: headers["If-Modified-Since"] = cached["modified"] swf = session.http.get(pvswf, headers=headers, **request_params) if cached and swf.status_code == 304: # Server says not modified hash = cached["hash"] else: # Calculate SHA-256 hash of the uncompressed SWF file, base-64 # encoded hash = sha256() hash.update(swfdecompress(swf.content)) hash = base64.b64encode(hash.digest()).decode("ascii") modified = swf.headers.get("Last-Modified", "") # Only save in cache if a valid date is given if len(modified) < 40: cache.set(key, dict(hash=hash, modified=modified)) msg = "st=0~exp=9999999999~acl=*~data={0}!{1}".format(data, hash) auth = hmac.new(AKAMAIHD_PV_KEY, msg.encode("ascii"), sha256) pvtoken = "{0}~hmac={1}".format(msg, auth.hexdigest()) # The "hdntl" parameter can be accepted as a cookie or passed in the # query string, but the "pvtoken" parameter can only be in the query # string params = [("pvtoken", pvtoken)] params.extend(parse_qsl(hdntl, keep_blank_values=True)) return params
def check_version(force=False): cache = Cache(filename="cli.json") latest_version = cache.get("latest_version") if force or not latest_version: res = requests.get("https://pypi.python.org/pypi/streamlink/json") data = res.json() latest_version = data.get("info").get("version") cache.set("latest_version", latest_version, (60 * 60 * 24)) version_info_printed = cache.get("version_info_printed") if not force and version_info_printed: return installed_version = StrictVersion(streamlink.version) latest_version = StrictVersion(latest_version) if latest_version > installed_version: console.logger.info("A new version of Streamlink ({0}) is " "available!".format(latest_version)) cache.set("version_info_printed", True, (60 * 60 * 6)) elif force: console.logger.info("Your Streamlink version ({0}) is up to date!", installed_version) if force: sys.exit()
def bind(cls, session, module, user_input_requester=None): cls.cache = Cache(filename="plugin-cache.json", key_prefix=module) cls.logger = logging.getLogger("streamlink.plugin." + module) cls.module = module cls.session = session if user_input_requester is not None: if isinstance(user_input_requester, UserInputRequester): cls._user_input_requester = user_input_requester else: raise RuntimeError("user-input-requester must be an instance of UserInputRequester")
def __init__(self, url): super().__init__(url) self.domain = self.match.group('base_url') self._session_attributes = Cache( filename='plugin-cache.json', key_prefix='zattoo:attributes:{0}'.format(self.domain)) self._uuid = self._session_attributes.get('uuid') self._authed = (self._session_attributes.get('power_guide_hash') and self._uuid and self.session.http.cookies.get('pzuid', domain=self.domain) and self.session.http.cookies.get('beaker.session.id', domain=self.domain) ) self._session_control = self._session_attributes.get('session_control', False) self.base_url = 'https://{0}'.format(self.domain) self.headers = { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': self.base_url }
def __init__(self, options=None): self.http = api.HTTPSession() self.options = Options({ "interface": None, "ipv4": False, "ipv6": False, "hds-live-edge": 10.0, "hds-segment-attempts": 3, "hds-segment-threads": 1, "hds-segment-timeout": 10.0, "hds-timeout": 60.0, "hls-live-edge": 3, "hls-segment-attempts": 3, "hls-segment-ignore-names": [], "hls-segment-threads": 1, "hls-segment-timeout": 10.0, "hls-segment-stream-data": False, "hls-timeout": 60.0, "hls-playlist-reload-attempts": 3, "hls-playlist-reload-time": "default", "hls-start-offset": 0, "hls-duration": None, "http-stream-timeout": 60.0, "hls-token-period": 60.0, "ringbuffer-size": 1024 * 1024 * 16, # 16 MB "rtmp-timeout": 60.0, "rtmp-rtmpdump": is_win32 and "rtmpdump.exe" or "rtmpdump", "rtmp-proxy": None, "stream-segment-attempts": 3, "stream-segment-threads": 1, "stream-segment-timeout": 10.0, "stream-timeout": 60.0, "subprocess-errorlog": False, "subprocess-errorlog-path": None, "ffmpeg-ffmpeg": None, "ffmpeg-fout": None, "ffmpeg-video-transcode": None, "ffmpeg-audio-transcode": None, "ffmpeg-copyts": False, "ffmpeg-start-at-zero": False, "mux-subtitles": False, "locale": None, "user-input-requester": None }) # felix add update headers cookies self.cache = Cache(filename="StreamlinkSession.json") if options: self.options.update(options) self.plugins = OrderedDict({}) self.load_builtin_plugins()
class Zattoo(Plugin): API_HELLO = '{0}/zapi/session/hello' API_LOGIN = '******' API_CHANNELS = '{0}/zapi/v2/cached/channels/{1}?details=False' API_WATCH = '{0}/zapi/watch' API_WATCH_REC = '{0}/zapi/watch/recording/{1}' API_WATCH_VOD = '{0}/zapi/avod/videos/{1}/watch' _url_re = re.compile(r''' https?:// (?P<base_url> zattoo\.com | tvonline\.ewe\.de | nettv\.netcologne\.de )/ (?: (?:ondemand/)?(?:watch/(?:[^/\s]+)(?:/[^/]+/(?P<recording_id>\d+))) | watch/(?P<channel>[^/\s]+) | ondemand/watch/(?P<vod_id>[^-]+)- ) ''', re.VERBOSE) _app_token_re = re.compile(r"""window\.appToken\s+=\s+'([^']+)'""") _channels_schema = validate.Schema({ 'success': int, 'channel_groups': [{ 'channels': [ { 'display_alias': validate.text, 'cid': validate.text }, ] }]}, validate.get('channel_groups'), ) arguments = PluginArguments( PluginArgument( "email", requires=["password"], metavar="EMAIL", help=""" The email associated with your zattoo account, required to access any zattoo stream. """ ), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help=""" A zattoo account password to use with --zattoo-email. """ ), PluginArgument( "purge-credentials", action="store_true", help=""" Purge cached zattoo credentials to initiate a new session and reauthenticate. """ ) ) def __init__(self, url): super(Zattoo, self).__init__(url) self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='zattoo:attributes') self._authed = self._session_attributes.get('beaker.session.id') and self._session_attributes.get( 'pzuid') and self._session_attributes.get('power_guide_hash') self._uuid = self._session_attributes.get('uuid') self._expires = self._session_attributes.get('expires', 946684800) self.base_url = 'https://{0}'.format(Zattoo._url_re.match(url).group('base_url')) self.headers = { 'User-Agent': useragents.CHROME, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': self.base_url } @classmethod def can_handle_url(cls, url): return Zattoo._url_re.match(url) def _hello(self): self.logger.debug('_hello ...') res = http.get(self.base_url) match = self._app_token_re.search(res.text) app_token = match.group(1) hello_url = self.API_HELLO.format(self.base_url) if self._uuid: __uuid = self._uuid else: __uuid = str(uuid.uuid4()) self._session_attributes.set('uuid', __uuid, expires=3600 * 24) params = { 'client_app_token': app_token, 'uuid': __uuid, 'lang': 'en', 'format': 'json' } res = http.post(hello_url, headers=self.headers, data=params) return res def _login(self, email, password, _hello): self.logger.debug('_login ... Attempting login as {0}'.format(email)) login_url = self.API_LOGIN.format(self.base_url) params = { 'login': email, 'password': password, 'remember': 'true' } res = http.post(login_url, headers=self.headers, data=params, cookies=_hello.cookies) data = http.json(res) self._authed = data['success'] if self._authed: self.logger.debug('New Session Data') self._session_attributes.set('beaker.session.id', res.cookies.get('beaker.session.id'), expires=3600 * 24) self._session_attributes.set('pzuid', res.cookies.get('pzuid'), expires=3600 * 24) self._session_attributes.set('power_guide_hash', data['session']['power_guide_hash'], expires=3600 * 24) return self._authed else: return None def _watch(self): self.logger.debug('_watch ...') match = self._url_re.match(self.url) if not match: self.logger.debug('_watch ... no match') return channel = match.group('channel') vod_id = match.group('vod_id') recording_id = match.group('recording_id') cookies = { 'beaker.session.id': self._session_attributes.get('beaker.session.id'), 'pzuid': self._session_attributes.get('pzuid') } watch_url = [] if channel: params, watch_url = self._watch_live(channel, cookies) elif vod_id: params, watch_url = self._watch_vod(vod_id) elif recording_id: params, watch_url = self._watch_recording(recording_id) if not watch_url: self.logger.debug('Missing watch_url') return res = [] try: res = http.post(watch_url, headers=self.headers, data=params, cookies=cookies) except Exception as e: if '404 Client Error' in str(e): self.logger.error( 'Unfortunately streaming is not permitted in this country or this channel does not exist.') elif '402 Client Error: Payment Required' in str(e): self.logger.error('Paid subscription required for this channel.') self.logger.info('If paid subscription exist, use --zattoo-purge-credentials to start a new session.') else: self.logger.error(str(e)) return self.logger.debug('Found post data') data = http.json(res) if data['success']: for hls_url in data['stream']['watch_urls']: for s in HLSStream.parse_variant_playlist(self.session, hls_url['url']).items(): yield s def _watch_live(self, channel, cookies): self.logger.debug('_watch_live ... Channel: {0}'.format(channel)) watch_url = self.API_WATCH.format(self.base_url) channels_url = self.API_CHANNELS.format(self.base_url, self._session_attributes.get('power_guide_hash')) res = http.get(channels_url, headers=self.headers, cookies=cookies) data = http.json(res, schema=self._channels_schema) c_list = [] for d in data: for c in d['channels']: c_list.append(c) cid = [] zattoo_list = [] for c in c_list: zattoo_list.append(c['display_alias']) if c['display_alias'] == channel: cid = c['cid'] self.logger.debug('Available zattoo channels in this country: {0}'.format(', '.join(sorted(zattoo_list)))) if not cid: cid = channel self.logger.debug('CHANNEL ID: {0}'.format(cid)) params = { 'cid': cid, 'https_watch_urls': True, 'stream_type': 'hls' } return params, watch_url def _watch_recording(self, recording_id): self.logger.debug('_watch_recording ...') watch_url = self.API_WATCH_REC.format(self.base_url, recording_id) params = { 'https_watch_urls': True, 'stream_type': 'hls' } return params, watch_url def _watch_vod(self, vod_id): self.logger.debug('_watch_vod ...') watch_url = self.API_WATCH_VOD.format(self.base_url, vod_id) params = { 'https_watch_urls': True, 'stream_type': 'hls' } return params, watch_url def _get_streams(self): email = self.get_option('email') password = self.get_option('password') if self.options.get('purge_credentials'): self._session_attributes.set('beaker.session.id', None, expires=0) self._session_attributes.set('expires', None, expires=0) self._session_attributes.set('power_guide_hash', None, expires=0) self._session_attributes.set('pzuid', None, expires=0) self._session_attributes.set('uuid', None, expires=0) self._authed = False self.logger.info('All credentials were successfully removed.') if not self._authed and (not email and not password): self.logger.error( 'A login for Zattoo is required, use --zattoo-email EMAIL --zattoo-password PASSWORD to set them') return if self._authed: if self._expires < time.time(): # login after 24h expires = time.time() + 3600 * 24 self._session_attributes.set('expires', expires, expires=3600 * 24) self._authed = False if not self._authed: __hello = self._hello() if not self._login(email, password, __hello): self.logger.error('Failed to login, check your username/password') return return self._watch()
class ABweb(Plugin): '''BIS Livestreams of french AB Groupe http://www.abweb.com/BIS-TV-Online/ ''' login_url = 'http://www.abweb.com/BIS-TV-Online/Default.aspx' _url_re = re.compile( r'https?://(?:www\.)?abweb\.com/BIS-TV-Online/bistvo-tele-universal.aspx', re.IGNORECASE) _hls_re = re.compile( r'''["']file["']:\s?["'](?P<url>[^"']+\.m3u8[^"']+)["']''') _iframe_re = re.compile(r'''<iframe[^>]+src=["'](?P<url>[^"']+)["']''') _input_re = re.compile(r'''(<input[^>]+>)''') _name_re = re.compile(r'''name=["']([^"']*)["']''') _value_re = re.compile(r'''value=["']([^"']*)["']''') expires_time = 3600 * 24 arguments = PluginArguments( PluginArgument("username", required=True, requires=["password"], metavar="USERNAME", help=""" The username associated with your ABweb account, required to access any ABweb stream. """, prompt="Enter ABweb username"), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help="A ABweb account password to use with --abweb-username.", prompt="Enter ABweb password"), PluginArgument("purge-credentials", action="store_true", help=""" Purge cached ABweb credentials to initiate a new session and reauthenticate. """)) def __init__(self, url): super(ABweb, self).__init__(url) self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='abweb:attributes') self._authed = self._session_attributes.get( 'ASP.NET_SessionId') and self._session_attributes.get( '.abportail1') self._expires = self._session_attributes.get( 'expires', time.time() + self.expires_time) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def set_expires_time_cache(self): expires = time.time() + self.expires_time self._session_attributes.set('expires', expires, expires=self.expires_time) def get_iframe_url(self): self.logger.debug('search for an iframe') res = http.get(self.url) m = self._iframe_re.search(res.text) if not m: raise PluginError('No iframe found.') iframe_url = m.group('url') iframe_url = update_scheme('http://', iframe_url) self.logger.debug('IFRAME URL={0}'.format(iframe_url)) return iframe_url def get_hls_url(self, iframe_url): self.logger.debug('search for hls url') res = http.get(iframe_url) m = self._hls_re.search(res.text) if not m: raise PluginError('No playlist found.') return m and m.group('url') def _login(self, username, password): '''login and update cached cookies''' self.logger.debug('login ...') res = http.get(self.login_url) input_list = self._input_re.findall(res.text) if not input_list: raise PluginError('Missing input data on login website.') data = {} for _input_data in input_list: try: _input_name = self._name_re.search(_input_data).group(1) except AttributeError: continue try: _input_value = self._value_re.search(_input_data).group(1) except AttributeError: _input_value = '' data[_input_name] = _input_value login_data = { 'ctl00$Login1$UserName': username, 'ctl00$Login1$Password': password, 'ctl00$Login1$LoginButton.x': '0', 'ctl00$Login1$LoginButton.y': '0' } data.update(login_data) res = http.post(self.login_url, data=data) for cookie in http.cookies: self._session_attributes.set(cookie.name, cookie.value, expires=3600 * 24) if self._session_attributes.get( 'ASP.NET_SessionId') and self._session_attributes.get( '.abportail1'): self.logger.debug('New session data') self.set_expires_time_cache() return True else: self.logger.error('Failed to login, check your username/password') return False def _get_streams(self): http.headers.update({ 'User-Agent': useragents.CHROME, 'Referer': 'http://www.abweb.com/BIS-TV-Online/bistvo-tele-universal.aspx' }) login_username = self.get_option('username') login_password = self.get_option('password') if self.options.get('purge_credentials'): self._session_attributes.set('ASP.NET_SessionId', None, expires=0) self._session_attributes.set('.abportail1', None, expires=0) self._authed = False self.logger.info('All credentials were successfully removed.') if not self._authed and not (login_username and login_password): self.logger.error( 'A login for ABweb is required, use --abweb-username USERNAME --abweb-password PASSWORD' ) return if self._authed: if self._expires < time.time(): self.logger.debug('get new cached cookies') # login after 24h self.set_expires_time_cache() self._authed = False else: self.logger.info( 'Attempting to authenticate using cached cookies') http.cookies.set( 'ASP.NET_SessionId', self._session_attributes.get('ASP.NET_SessionId')) http.cookies.set('.abportail1', self._session_attributes.get('.abportail1')) if not self._authed and not self._login(login_username, login_password): return iframe_url = self.get_iframe_url() http.headers.update({'Referer': iframe_url}) hls_url = self.get_hls_url(iframe_url) hls_url = update_scheme(self.url, hls_url) self.logger.debug('URL={0}'.format(hls_url)) variant = HLSStream.parse_variant_playlist(self.session, hls_url) if variant: for q, s in variant.items(): yield q, s else: yield 'live', HLSStream(self.session, hls_url)
def __init__(self, url): super(ABweb, self).__init__(url) self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='abweb:attributes') self._authed = self._session_attributes.get('ASP.NET_SessionId') and self._session_attributes.get('.abportail1') self._expires = self._session_attributes.get('expires', time.time() + self.expires_time)
class ABweb(Plugin): '''BIS Livestreams of french AB Groupe http://www.abweb.com/BIS-TV-Online/ ''' login_url = 'http://www.abweb.com/BIS-TV-Online/Default.aspx' _url_re = re.compile(r'https?://(?:www\.)?abweb\.com/BIS-TV-Online/bistvo-tele-universal.aspx', re.IGNORECASE) _hls_re = re.compile(r'''["']file["']:\s?["'](?P<url>[^"']+\.m3u8[^"']+)["']''') _iframe_re = re.compile(r'''<iframe[^>]+src=["'](?P<url>[^"']+)["']''') _input_re = re.compile(r'''(<input[^>]+>)''') _name_re = re.compile(r'''name=["']([^"']*)["']''') _value_re = re.compile(r'''value=["']([^"']*)["']''') expires_time = 3600 * 24 arguments = PluginArguments( PluginArgument( "username", required=True, requires=["password"], metavar="USERNAME", help="The username associated with your ABweb account, required to access any ABweb stream.", prompt="Enter ABweb username" ), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help="A ABweb account password to use with --abweb-username.", prompt="Enter ABweb password" ), PluginArgument( "purge-credentials", action="store_true", help=""" Purge cached ABweb credentials to initiate a new session and reauthenticate. """ ) ) def __init__(self, url): super(ABweb, self).__init__(url) self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='abweb:attributes') self._authed = self._session_attributes.get('ASP.NET_SessionId') and self._session_attributes.get('.abportail1') self._expires = self._session_attributes.get('expires', time.time() + self.expires_time) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def set_expires_time_cache(self): expires = time.time() + self.expires_time self._session_attributes.set('expires', expires, expires=self.expires_time) def get_iframe_url(self): self.logger.debug('search for an iframe') res = http.get(self.url) m = self._iframe_re.search(res.text) if not m: raise PluginError('No iframe found.') iframe_url = m.group('url') iframe_url = update_scheme('http://', iframe_url) self.logger.debug('IFRAME URL={0}'.format(iframe_url)) return iframe_url def get_hls_url(self, iframe_url): self.logger.debug('search for hls url') res = http.get(iframe_url) m = self._hls_re.search(res.text) if not m: raise PluginError('No playlist found.') return m and m.group('url') def _login(self, username, password): '''login and update cached cookies''' self.logger.debug('login ...') res = http.get(self.login_url) input_list = self._input_re.findall(res.text) if not input_list: raise PluginError('Missing input data on login website.') data = {} for _input_data in input_list: try: _input_name = self._name_re.search(_input_data).group(1) except AttributeError: continue try: _input_value = self._value_re.search(_input_data).group(1) except AttributeError: _input_value = '' data[_input_name] = _input_value login_data = { 'ctl00$Login1$UserName': username, 'ctl00$Login1$Password': password, 'ctl00$Login1$LoginButton.x': '0', 'ctl00$Login1$LoginButton.y': '0' } data.update(login_data) res = http.post(self.login_url, data=data) for cookie in http.cookies: self._session_attributes.set(cookie.name, cookie.value, expires=3600 * 24) if self._session_attributes.get('ASP.NET_SessionId') and self._session_attributes.get('.abportail1'): self.logger.debug('New session data') self.set_expires_time_cache() return True else: self.logger.error('Failed to login, check your username/password') return False def _get_streams(self): http.headers.update({'User-Agent': useragents.CHROME, 'Referer': 'http://www.abweb.com/BIS-TV-Online/bistvo-tele-universal.aspx'}) login_username = self.get_option('username') login_password = self.get_option('password') if self.options.get('purge_credentials'): self._session_attributes.set('ASP.NET_SessionId', None, expires=0) self._session_attributes.set('.abportail1', None, expires=0) self._authed = False self.logger.info('All credentials were successfully removed.') if not self._authed and not (login_username and login_password): self.logger.error('A login for ABweb is required, use --abweb-username USERNAME --abweb-password PASSWORD') return if self._authed: if self._expires < time.time(): self.logger.debug('get new cached cookies') # login after 24h self.set_expires_time_cache() self._authed = False else: self.logger.info('Attempting to authenticate using cached cookies') http.cookies.set('ASP.NET_SessionId', self._session_attributes.get('ASP.NET_SessionId')) http.cookies.set('.abportail1', self._session_attributes.get('.abportail1')) if not self._authed and not self._login(login_username, login_password): return iframe_url = self.get_iframe_url() http.headers.update({'Referer': iframe_url}) hls_url = self.get_hls_url(iframe_url) hls_url = update_scheme(self.url, hls_url) self.logger.debug('URL={0}'.format(hls_url)) variant = HLSStream.parse_variant_playlist(self.session, hls_url) if variant: for q, s in variant.items(): yield q, s else: yield 'live', HLSStream(self.session, hls_url)
class WWENetwork(Plugin): url_re = re.compile(r"https?://network.wwe.com") content_id_re = re.compile(r'''"content_id" : "(\d+)"''') playback_scenario = "HTTP_CLOUD_WIRED" login_url = "https://secure.net.wwe.com/workflow.do" login_page_url = "https://secure.net.wwe.com/enterworkflow.do?flowId=account.login&forwardUrl=http%3A%2F%2Fnetwork.wwe.com" api_url = "https://ws.media.net.wwe.com/ws/media/mf/op-findUserVerifiedEvent/v-2.3" _info_schema = validate.Schema( validate.union({ "status": validate.union({ "code": validate.all(validate.xml_findtext(".//status-code"), validate.transform(int)), "message": validate.xml_findtext(".//status-message"), }), "urls": validate.all( validate.xml_findall(".//url"), [validate.getattr("text")] ), validate.optional("fingerprint"): validate.xml_findtext(".//updated-fingerprint"), validate.optional("session_key"): validate.xml_findtext(".//session-key"), "session_attributes": validate.all( validate.xml_findall(".//session-attribute"), [validate.getattr("attrib"), validate.union({ "name": validate.get("name"), "value": validate.get("value") })] ) }) ) arguments = PluginArguments( PluginArgument( "email", required=True, metavar="EMAIL", requires=["password"], help=""" The email associated with your WWE Network account, required to access any WWE Network stream. """ ), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help=""" A WWE Network account password to use with --wwenetwork-email. """ ) ) def __init__(self, url): super(WWENetwork, self).__init__(url) http.headers.update({"User-Agent": useragents.CHROME}) self._session_attributes = Cache(filename="plugin-cache.json", key_prefix="wwenetwork:attributes") self._session_key = self.cache.get("session_key") self._authed = self._session_attributes.get("ipid") and self._session_attributes.get("fprt") @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def login(self, email, password): self.logger.debug("Attempting login as {0}", email) # sets some required cookies to login http.get(self.login_page_url) # login res = http.post(self.login_url, data=dict(registrationAction='identify', emailAddress=email, password=password, submitButton=""), headers={"Referer": self.login_page_url}, allow_redirects=False) self._authed = "Authentication Error" not in res.text if self._authed: self._session_attributes.set("ipid", res.cookies.get("ipid"), expires=3600 * 1.5) self._session_attributes.set("fprt", res.cookies.get("fprt"), expires=3600 * 1.5) return self._authed def _update_session_attribute(self, key, value): if value: self._session_attributes.set(key, value, expires=3600 * 1.5) # 1h30m expiry http.cookies.set(key, value) @property def session_key(self): return self._session_key @session_key.setter def session_key(self, value): self.cache.set("session_key", value) self._session_key = value def _get_media_info(self, content_id): """ Get the info about the content, based on the ID :param content_id: :return: """ params = {"identityPointId": self._session_attributes.get("ipid"), "fingerprint": self._session_attributes.get("fprt"), "contentId": content_id, "playbackScenario": self.playback_scenario, "platform": "WEB_MEDIAPLAYER_5", "subject": "LIVE_EVENT_COVERAGE", "frameworkURL": "https://ws.media.net.wwe.com", "_": int(time.time())} if self.session_key: params["sessionKey"] = self.session_key url = self.api_url.format(id=content_id) res = http.get(url, params=params) return http.xml(res, ignore_ns=True, schema=self._info_schema) def _get_content_id(self): # check the page to find the contentId res = http.get(self.url) m = self.content_id_re.search(res.text) if m: return m.group(1) def _get_streams(self): email = self.get_option("email") password = self.get_option("password") if not self._authed and (not email and not password): self.logger.error("A login for WWE Network is required, use --wwenetwork-email/" "--wwenetwork-password to set them") return if not self._authed: if not self.login(email, password): self.logger.error("Failed to login, check your username/password") return content_id = self._get_content_id() if content_id: self.logger.debug("Found content ID: {0}", content_id) info = self._get_media_info(content_id) if info["status"]["code"] == 1: # update the session attributes self._update_session_attribute("fprt", info.get("fingerprint")) for attr in info["session_attributes"]: self._update_session_attribute(attr["name"], attr["value"]) if info.get("session_key"): self.session_key = info.get("session_key") for url in info["urls"]: for s in HLSStream.parse_variant_playlist(self.session, url, name_fmt="{pixels}_{bitrate}").items(): yield s else: raise PluginError("Could not load streams: {message} ({code})".format(**info["status"]))
class WWENetwork(Plugin): url_re = re.compile(r"https?://network.wwe.com") content_id_re = re.compile(r'''"content_id" : "(\d+)"''') playback_scenario = "HTTP_CLOUD_WIRED" login_url = "https://secure.net.wwe.com/workflow.do" login_page_url = "https://secure.net.wwe.com/enterworkflow.do?flowId=account.login&forwardUrl=http%3A%2F%2Fnetwork.wwe.com" api_url = "https://ws.media.net.wwe.com/ws/media/mf/op-findUserVerifiedEvent/v-2.3" _info_schema = validate.Schema( validate.union({ "status": validate.union({ "code": validate.all(validate.xml_findtext(".//status-code"), validate.transform(int)), "message": validate.xml_findtext(".//status-message"), }), "urls": validate.all(validate.xml_findall(".//url"), [validate.getattr("text")]), validate.optional("fingerprint"): validate.xml_findtext(".//updated-fingerprint"), validate.optional("session_key"): validate.xml_findtext(".//session-key"), "session_attributes": validate.all(validate.xml_findall(".//session-attribute"), [ validate.getattr("attrib"), validate.union({ "name": validate.get("name"), "value": validate.get("value") }) ]) })) arguments = PluginArguments( PluginArgument("email", required=True, metavar="EMAIL", requires=["password"], help=""" The email associated with your WWE Network account, required to access any WWE Network stream. """), PluginArgument("password", sensitive=True, metavar="PASSWORD", help=""" A WWE Network account password to use with --wwenetwork-email. """)) def __init__(self, url): super(WWENetwork, self).__init__(url) http.headers.update({"User-Agent": useragents.CHROME}) self._session_attributes = Cache(filename="plugin-cache.json", key_prefix="wwenetwork:attributes") self._session_key = self.cache.get("session_key") self._authed = self._session_attributes.get( "ipid") and self._session_attributes.get("fprt") @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def login(self, email, password): self.logger.debug("Attempting login as {0}", email) # sets some required cookies to login http.get(self.login_page_url) # login res = http.post(self.login_url, data=dict(registrationAction='identify', emailAddress=email, password=password, submitButton=""), headers={"Referer": self.login_page_url}, allow_redirects=False) self._authed = "Authentication Error" not in res.text if self._authed: self._session_attributes.set("ipid", res.cookies.get("ipid"), expires=3600 * 1.5) self._session_attributes.set("fprt", res.cookies.get("fprt"), expires=3600 * 1.5) return self._authed def _update_session_attribute(self, key, value): if value: self._session_attributes.set(key, value, expires=3600 * 1.5) # 1h30m expiry http.cookies.set(key, value) @property def session_key(self): return self._session_key @session_key.setter def session_key(self, value): self.cache.set("session_key", value) self._session_key = value def _get_media_info(self, content_id): """ Get the info about the content, based on the ID :param content_id: :return: """ params = { "identityPointId": self._session_attributes.get("ipid"), "fingerprint": self._session_attributes.get("fprt"), "contentId": content_id, "playbackScenario": self.playback_scenario, "platform": "WEB_MEDIAPLAYER_5", "subject": "LIVE_EVENT_COVERAGE", "frameworkURL": "https://ws.media.net.wwe.com", "_": int(time.time()) } if self.session_key: params["sessionKey"] = self.session_key url = self.api_url.format(id=content_id) res = http.get(url, params=params) return http.xml(res, ignore_ns=True, schema=self._info_schema) def _get_content_id(self): # check the page to find the contentId res = http.get(self.url) m = self.content_id_re.search(res.text) if m: return m.group(1) def _get_streams(self): email = self.get_option("email") password = self.get_option("password") if not self._authed and (not email and not password): self.logger.error( "A login for WWE Network is required, use --wwenetwork-email/" "--wwenetwork-password to set them") return if not self._authed: if not self.login(email, password): self.logger.error( "Failed to login, check your username/password") return content_id = self._get_content_id() if content_id: self.logger.debug("Found content ID: {0}", content_id) info = self._get_media_info(content_id) if info["status"]["code"] == 1: # update the session attributes self._update_session_attribute("fprt", info.get("fingerprint")) for attr in info["session_attributes"]: self._update_session_attribute(attr["name"], attr["value"]) if info.get("session_key"): self.session_key = info.get("session_key") for url in info["urls"]: for s in HLSStream.parse_variant_playlist( self.session, url, name_fmt="{pixels}_{bitrate}").items(): yield s else: raise PluginError( "Could not load streams: {message} ({code})".format( **info["status"]))
def __init__(self, url): super(Mitele, self).__init__(url) self.cache = Cache("mitele.cache")
class Zattoo(Plugin): API_CHANNELS = '{0}/zapi/v2/cached/channels/{1}?details=False' API_HELLO = '{0}/zapi/session/hello' API_HELLO_V3 = '{0}/zapi/v3/session/hello' API_LOGIN = '******' API_LOGIN_V3 = '{0}/zapi/v3/account/login' API_SESSION = '{0}/zapi/v2/session' API_WATCH = '{0}/zapi/watch' API_WATCH_REC = '{0}/zapi/watch/recording/{1}' API_WATCH_VOD = '{0}/zapi/avod/videos/{1}/watch' STREAMS_ZATTOO = ['dash', 'hls', 'hls5'] TIME_CONTROL = 60 * 60 * 2 TIME_SESSION = 60 * 60 * 24 * 30 _url_re = re.compile(r'''(?x) https?:// (?P<base_url> (?:(?: iptv\.glattvision|www\.(?:myvisiontv|saktv|vtxtv) )\.ch )|(?:(?: mobiltv\.quickline|www\.quantum-tv|zattoo )\.com )|(?:(?: tvonline\.ewe|nettv\.netcologne|tvplus\.m-net )\.de )|(?:(?: player\.waly|www\.(?:1und1|netplus) )\.tv) |www\.bbv-tv\.net |www\.meinewelt\.cc )/ (?: (?: recording(?:s\?recording=|/) | (?:ondemand/)?(?:watch/(?:[^/\s]+)(?:/[^/]+/)) )(?P<recording_id>\d+) | (?: (?:live/|watch/)|(?:channels(?:/\w+)?|guide)\?channel= )(?P<channel>[^/\s]+) | ondemand(?:\?video=|/watch/)(?P<vod_id>[^-]+) ) ''') _app_token_re = re.compile(r"""window\.appToken\s+=\s+'([^']+)'""") _channels_schema = validate.Schema( { 'success': bool, 'channel_groups': [{ 'channels': [ { 'display_alias': validate.text, 'cid': validate.text }, ] }] }, validate.get('channel_groups'), ) _session_schema = validate.Schema( { 'success': bool, 'session': { 'loggedin': bool } }, validate.get('session')) arguments = PluginArguments( PluginArgument("email", requires=["password"], metavar="EMAIL", help=""" The email associated with your zattoo account, required to access any zattoo stream. """), PluginArgument("password", sensitive=True, metavar="PASSWORD", help=""" A zattoo account password to use with --zattoo-email. """), PluginArgument("purge-credentials", action="store_true", help=""" Purge cached zattoo credentials to initiate a new session and reauthenticate. """), PluginArgument('stream-types', metavar='TYPES', type=comma_list_filter(STREAMS_ZATTOO), default=['hls'], help=''' A comma-delimited list of stream types which should be used, the following types are allowed: - {0} Default is "hls". '''.format('\n - '.join(STREAMS_ZATTOO)))) def __init__(self, url): super(Zattoo, self).__init__(url) self.domain = self._url_re.match(url).group('base_url') self._session_attributes = Cache( filename='plugin-cache.json', key_prefix='zattoo:attributes:{0}'.format(self.domain)) self._uuid = self._session_attributes.get('uuid') self._authed = (self._session_attributes.get('power_guide_hash') and self._uuid and self.session.http.cookies.get( 'pzuid', domain=self.domain) and self.session.http.cookies.get('beaker.session.id', domain=self.domain)) self._session_control = self._session_attributes.get( 'session_control', False) self.base_url = 'https://{0}'.format(self.domain) self.headers = { 'User-Agent': useragents.CHROME, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': self.base_url } @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def _hello(self): log.debug('_hello ...') # a new session is required for the app_token self.session.http.cookies = cookiejar_from_dict({}) if self.base_url == 'https://zattoo.com': app_token_url = 'https://zattoo.com/int/' elif self.base_url == 'https://www.quantum-tv.com': app_token_url = 'https://www.quantum-tv.com/token-4d0d61d4ce0bf8d9982171f349d19f34.json' else: app_token_url = self.base_url res = self.session.http.get(app_token_url) match = self._app_token_re.search(res.text) if self.base_url == 'https://www.quantum-tv.com': app_token = self.session.http.json(res)["session_token"] hello_url = self.API_HELLO_V3.format(self.base_url) else: app_token = match.group(1) hello_url = self.API_HELLO.format(self.base_url) if self._uuid: __uuid = self._uuid else: __uuid = str(uuid.uuid4()) self._session_attributes.set('uuid', __uuid, expires=self.TIME_SESSION) params = { 'client_app_token': app_token, 'uuid': __uuid, } if self.base_url == 'https://www.quantum-tv.com': params['app_version'] = '3.2028.3' else: params['lang'] = 'en' params['format'] = 'json' res = self.session.http.post(hello_url, headers=self.headers, data=params) def _login(self, email, password): log.debug('_login ... Attempting login as {0}'.format(email)) params = {'login': email, 'password': password, 'remember': 'true'} if self.base_url == 'https://quantum-tv.com': login_url = self.API_LOGIN_V3.format(self.base_url) else: login_url = self.API_LOGIN.format(self.base_url) try: res = self.session.http.post(login_url, headers=self.headers, data=params) except Exception as e: if '400 Client Error' in str(e): raise PluginError( 'Failed to login, check your username/password') raise e data = self.session.http.json(res) self._authed = data['success'] log.debug('New Session Data') self.save_cookies(default_expires=self.TIME_SESSION) self._session_attributes.set('power_guide_hash', data['session']['power_guide_hash'], expires=self.TIME_SESSION) self._session_attributes.set('session_control', True, expires=self.TIME_CONTROL) def _watch(self): log.debug('_watch ...') match = self._url_re.match(self.url) if not match: log.debug('_watch ... no match') return channel = match.group('channel') vod_id = match.group('vod_id') recording_id = match.group('recording_id') params = {'https_watch_urls': True} if channel: watch_url = self.API_WATCH.format(self.base_url) params_cid = self._get_params_cid(channel) if not params_cid: return params.update(params_cid) elif vod_id: log.debug('Found vod_id: {0}'.format(vod_id)) watch_url = self.API_WATCH_VOD.format(self.base_url, vod_id) elif recording_id: log.debug('Found recording_id: {0}'.format(recording_id)) watch_url = self.API_WATCH_REC.format(self.base_url, recording_id) else: log.debug('Missing watch_url') return zattoo_stream_types = self.get_option('stream-types') or ['hls'] for stream_type in zattoo_stream_types: params_stream_type = {'stream_type': stream_type} params.update(params_stream_type) try: res = self.session.http.post(watch_url, headers=self.headers, data=params) except Exception as e: if '404 Client Error' in str(e): log.error('Unfortunately streaming is not permitted in ' 'this country or this channel does not exist.') elif '402 Client Error: Payment Required' in str(e): log.error('Paid subscription required for this channel.') log.info('If paid subscription exist, use --zattoo-purge' '-credentials to start a new session.') elif '403 Client Error' in str(e): log.debug('Force session reset for watch_url') self.reset_session() else: log.error(str(e)) return data = self.session.http.json(res) log.debug('Found data for {0}'.format(stream_type)) if data['success'] and stream_type in ['hls', 'hls5']: for url in data['stream']['watch_urls']: for s in HLSStream.parse_variant_playlist( self.session, url['url']).items(): yield s elif data['success'] and stream_type == 'dash': for url in data['stream']['watch_urls']: for s in DASHStream.parse_manifest(self.session, url['url']).items(): yield s def _get_params_cid(self, channel): log.debug('get channel ID for {0}'.format(channel)) channels_url = self.API_CHANNELS.format( self.base_url, self._session_attributes.get('power_guide_hash')) try: res = self.session.http.get(channels_url, headers=self.headers) except Exception: log.debug('Force session reset for _get_params_cid') self.reset_session() return False data = self.session.http.json(res, schema=self._channels_schema) c_list = [] for d in data: for c in d['channels']: c_list.append(c) cid = [] zattoo_list = [] for c in c_list: zattoo_list.append(c['display_alias']) if c['display_alias'] == channel: cid = c['cid'] log.debug('Available zattoo channels in this country: {0}'.format( ', '.join(sorted(zattoo_list)))) if not cid: cid = channel log.debug('CHANNEL ID: {0}'.format(cid)) return {'cid': cid} def reset_session(self): self._session_attributes.set('power_guide_hash', None, expires=0) self._session_attributes.set('uuid', None, expires=0) self.clear_cookies() self._authed = False def _get_streams(self): email = self.get_option('email') password = self.get_option('password') if self.options.get('purge_credentials'): self.reset_session() log.info('All credentials were successfully removed.') elif (self._authed and not self._session_control): # check every two hours, if the session is actually valid log.debug('Session control for {0}'.format(self.domain)) res = self.session.http.get(self.API_SESSION.format(self.base_url)) res = self.session.http.json(res, schema=self._session_schema) if res['loggedin']: self._session_attributes.set('session_control', True, expires=self.TIME_CONTROL) else: log.debug('User is not logged in') self._authed = False if not self._authed and (not email and not password): log.error( 'A login for Zattoo is required, use --zattoo-email EMAIL' ' --zattoo-password PASSWORD to set them') return if not self._authed: self._hello() self._login(email, password) return self._watch()
def bind(cls, session, module): cls.cache = Cache(filename="plugin-cache.json", key_prefix=module) cls.logger = logging.getLogger("streamlink.plugin." + module) cls.module = module cls.session = session
def __init__(self, url): super(WWENetwork, self).__init__(url) http.headers.update({"User-Agent": useragents.CHROME}) self._session_attributes = Cache(filename="plugin-cache.json", key_prefix="wwenetwork:attributes") self._session_key = self.cache.get("session_key") self._authed = self._session_attributes.get("ipid") and self._session_attributes.get("fprt")
class Zattoo(Plugin): STREAMS_ZATTOO = ['dash', 'hls7'] TIME_CONTROL = 60 * 60 * 2 TIME_SESSION = 60 * 60 * 24 * 30 arguments = PluginArguments( PluginArgument( "email", requires=["password"], metavar="EMAIL", help=""" The email associated with your zattoo account, required to access any zattoo stream. """), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help=""" A zattoo account password to use with --zattoo-email. """), PluginArgument( "purge-credentials", action="store_true", help=""" Purge cached zattoo credentials to initiate a new session and reauthenticate. """), PluginArgument( 'stream-types', metavar='TYPES', type=comma_list_filter(STREAMS_ZATTOO), default=['dash'], help=''' A comma-delimited list of stream types which should be used, the following types are allowed: - {0} Default is "dash". '''.format('\n - '.join(STREAMS_ZATTOO)) ) ) def __init__(self, url): super().__init__(url) self.domain = self.match.group('base_url') self._session_attributes = Cache( filename='plugin-cache.json', key_prefix='zattoo:attributes:{0}'.format(self.domain)) self._uuid = self._session_attributes.get('uuid') self._authed = (self._session_attributes.get('power_guide_hash') and self._uuid and self.session.http.cookies.get('pzuid', domain=self.domain) and self.session.http.cookies.get('beaker.session.id', domain=self.domain) ) self._session_control = self._session_attributes.get('session_control', False) self.base_url = 'https://{0}'.format(self.domain) self.headers = { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': self.base_url } def _hello(self): log.debug('_hello ...') app_token = self.session.http.get( f'{self.base_url}/token.json', schema=validate.Schema(validate.parse_json(), { 'success': bool, 'session_token': str, }, 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( f'{self.base_url}/zapi/v3/session/hello', 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 _login(self, email, password): log.debug('_login ...') data = self.session.http.post( f'{self.base_url}/zapi/v3/account/login', headers=self.headers, data={ 'login': email, 'password': password, 'remember': 'true', 'format': 'json', }, acceptable_status=(200, 400), schema=validate.Schema(validate.parse_json(), validate.any( {'active': bool, 'power_guide_hash': str}, {'success': bool}, )), ) if data.get('active'): log.debug('Login was successful.') else: log.debug('Login failed.') return self._authed = data['active'] self.save_cookies(default_expires=self.TIME_SESSION) self._session_attributes.set('power_guide_hash', data['power_guide_hash'], expires=self.TIME_SESSION) self._session_attributes.set( 'session_control', True, expires=self.TIME_CONTROL) def _watch(self): log.debug('_watch ...') channel = self.match.group('channel') vod_id = self.match.group('vod_id') recording_id = self.match.group('recording_id') params = {'https_watch_urls': True} if channel: watch_url = f'{self.base_url}/zapi/watch' params_cid = self._get_params_cid(channel) if not params_cid: return params.update(params_cid) elif vod_id: log.debug('Found vod_id: {0}'.format(vod_id)) watch_url = f'{self.base_url}/zapi/avod/videos/{vod_id}/watch' elif recording_id: log.debug('Found recording_id: {0}'.format(recording_id)) watch_url = f'{self.base_url}/zapi/watch/recording/{recording_id}' else: log.debug('Missing watch_url') return zattoo_stream_types = self.get_option('stream-types') for stream_type in zattoo_stream_types: params_stream_type = {'stream_type': stream_type} params.update(params_stream_type) data = self.session.http.post( watch_url, headers=self.headers, data=params, acceptable_status=(200, 402, 403, 404), schema=validate.Schema(validate.parse_json(), validate.any({ 'success': validate.transform(bool), 'stream': { 'watch_urls': [{ 'url': validate.url(), validate.optional('maxrate'): int, validate.optional('audio_channel'): str, }], validate.optional('quality'): str, }, }, { 'success': validate.transform(bool), 'internal_code': int, validate.optional('http_status'): int, })), ) if not data['success']: if data['internal_code'] == 401: log.error(f'invalid stream_type {stream_type}') elif data['internal_code'] == 421: log.error('Unfortunately streaming is not permitted in this country or this channel does not exist.') elif data['internal_code'] == 422: log.error('Paid subscription required for this channel.') log.info('If paid subscription exist, use --zattoo-purge-credentials to start a new session.') else: log.debug(f'unknown error {data!r}') log.debug('Force session reset for watch_url') self.reset_session() continue log.debug(f'Found data for {stream_type}') if stream_type == 'hls7': for url in data['stream']['watch_urls']: yield from HLSStream.parse_variant_playlist(self.session, url['url']).items() elif stream_type == 'dash': for url in data['stream']['watch_urls']: yield from DASHStream.parse_manifest(self.session, url['url']).items() def _get_params_cid(self, channel): log.debug('get channel ID for {0}'.format(channel)) try: res = self.session.http.get( f'{self.base_url}/zapi/v2/cached/channels/{self._session_attributes.get("power_guide_hash")}', headers=self.headers, params={'details': 'False'} ) except Exception: log.debug('Force session reset for _get_params_cid') self.reset_session() return False data = self.session.http.json( res, schema=validate.Schema({ 'success': validate.transform(bool), 'channel_groups': [{ 'channels': [ { 'display_alias': str, 'cid': str, 'qualities': [{ 'title': str, 'stream_types': validate.all( [str], validate.filter(lambda n: not re.match(r"(.+_(?:fairplay|playready|widevine))", n)) ), 'level': str, 'availability': str, }], }, ], }]}, validate.get('channel_groups'), ) ) c_list = [] for d in data: for c in d['channels']: c_list.append(c) cid = None zattoo_list = [] for c in c_list: zattoo_list.append(c['display_alias']) if c['display_alias'] == channel: cid = c['cid'] log.debug(f'{c!r}') log.trace('Available zattoo channels in this country: {0}'.format( ', '.join(sorted(zattoo_list)))) if not cid: cid = channel log.debug('CHANNEL ID: {0}'.format(cid)) return {'cid': cid} def reset_session(self): self._session_attributes.set('power_guide_hash', None, expires=0) self._session_attributes.set('uuid', None, expires=0) self.clear_cookies() self._authed = False def _get_streams(self): email = self.get_option('email') password = self.get_option('password') if self.options.get('purge_credentials'): self.reset_session() log.info('All credentials were successfully removed.') elif (self._authed and not self._session_control): # check every two hours, if the session is actually valid log.debug('Session control for {0}'.format(self.domain)) active = self.session.http.get( f'{self.base_url}/zapi/v3/session', schema=validate.Schema(validate.parse_json(), {'active': bool}, validate.get('active')) ) if active: self._session_attributes.set( 'session_control', True, expires=self.TIME_CONTROL) log.debug('User is logged in') else: log.debug('User is not logged in') self._authed = False if not self._authed and (not email and not password): log.error( 'A login for Zattoo is required, use --zattoo-email EMAIL' ' --zattoo-password PASSWORD to set them') return if not self._authed: self._hello() self._login(email, password) if self._authed: return self._watch()
class Zattoo(Plugin): API_HELLO = '{0}/zapi/session/hello' API_LOGIN = '******' API_CHANNELS = '{0}/zapi/v2/cached/channels/{1}?details=False' API_WATCH = '{0}/zapi/watch' API_WATCH_VOD = '{0}/zapi/avod/videos/{1}/watch' _url_re = re.compile( r''' https?:// (?P<base_url> zattoo\.com | tvonline\.ewe\.de | nettv\.netcologne\.de )/(?:watch/(?P<channel>[^/\s]+) | ondemand/watch/(?P<vod_id>[^-]+)-) ''', re.VERBOSE) _app_token_re = re.compile(r"""window\.appToken\s+=\s+'([^']+)'""") _channels_schema = validate.Schema( { 'success': int, 'channel_groups': [{ 'channels': [ { 'display_alias': validate.text, 'cid': validate.text }, ] }] }, validate.get('channel_groups'), ) options = PluginOptions({ 'email': None, 'password': None, 'purge_credentials': None }) def __init__(self, url): super(Zattoo, self).__init__(url) self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='zattoo:attributes') self._authed = self._session_attributes.get( 'beaker.session.id') and self._session_attributes.get( 'pzuid') and self._session_attributes.get('power_guide_hash') self._uuid = self._session_attributes.get('uuid') self._expires = self._session_attributes.get('expires') self.base_url = 'https://{0}'.format( Zattoo._url_re.match(url).group('base_url')) self.headers = { 'User-Agent': useragents.CHROME, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': self.base_url } @classmethod def can_handle_url(cls, url): return Zattoo._url_re.match(url) def _hello(self): self.logger.debug('_hello ...') res = http.get(self.base_url) match = self._app_token_re.search(res.text) app_token = match.group(1) hello_url = self.API_HELLO.format(self.base_url) if self._uuid: __uuid = self._uuid else: __uuid = str(uuid.uuid4()) self._session_attributes.set('uuid', __uuid, expires=3600 * 24) params = { 'client_app_token': app_token, 'uuid': __uuid, 'lang': 'en', 'format': 'json' } res = http.post(hello_url, headers=self.headers, data=params) return res def _login(self, email, password, _hello): self.logger.debug('_login ... Attempting login as {0}'.format(email)) login_url = self.API_LOGIN.format(self.base_url) params = {'login': email, 'password': password, 'remember': 'true'} res = http.post(login_url, headers=self.headers, data=params, cookies=_hello.cookies) data = http.json(res) self._authed = data['success'] if self._authed: self.logger.debug('New Session Data') self._session_attributes.set('beaker.session.id', res.cookies.get('beaker.session.id'), expires=3600 * 24) self._session_attributes.set('pzuid', res.cookies.get('pzuid'), expires=3600 * 24) self._session_attributes.set('power_guide_hash', data['session']['power_guide_hash'], expires=3600 * 24) return self._authed else: return None def _watch(self): self.logger.debug('_watch ...') match = self._url_re.match(self.url) if not match: return channel = match.group('channel') vod_id = match.group('vod_id') cookies = { 'beaker.session.id': self._session_attributes.get('beaker.session.id'), 'pzuid': self._session_attributes.get('pzuid') } watch_url = [] if channel: params, watch_url = self._watch_live(channel, cookies) elif vod_id: params, watch_url = self._watch_vod(vod_id) if not watch_url: return res = [] try: res = http.post(watch_url, headers=self.headers, data=params, cookies=cookies) except Exception as e: if '404 Client Error' in str(e): self.logger.error( 'Unfortunately streaming is not permitted in this country or this channel does not exist.' ) elif '402 Client Error: Payment Required' in str(e): self.logger.error( 'Paid subscription required for this channel.') self.logger.info( 'If paid subscription exist, use --zattoo-purge-credentials to start a new session.' ) else: self.logger.error(str(e)) return data = http.json(res) if data['success']: for hls_url in data['stream']['watch_urls']: for s in HLSStream.parse_variant_playlist( self.session, hls_url['url']).items(): yield s def _watch_live(self, channel, cookies): self.logger.debug('_watch_live ... Channel: {0}'.format(channel)) watch_url = self.API_WATCH.format(self.base_url) channels_url = self.API_CHANNELS.format( self.base_url, self._session_attributes.get('power_guide_hash')) res = http.get(channels_url, headers=self.headers, cookies=cookies) data = http.json(res, schema=self._channels_schema) c_list = [] for d in data: for c in d['channels']: c_list.append(c) cid = [] zattoo_list = [] for c in c_list: zattoo_list.append(c['display_alias']) if c['display_alias'] == channel: cid = c['cid'] self.logger.debug( 'Available zattoo channels in this country: {0}'.format(', '.join( sorted(zattoo_list)))) if not cid: cid = channel self.logger.debug('CHANNEL ID: {0}'.format(cid)) params = {'cid': cid, 'https_watch_urls': True, 'stream_type': 'hls'} return params, watch_url def _watch_vod(self, vod_id): self.logger.debug('_watch_vod ...') watch_url = self.API_WATCH_VOD.format(self.base_url, vod_id) params = {'https_watch_urls': True, 'stream_type': 'hls'} return params, watch_url def _get_streams(self): email = self.get_option('email') password = self.get_option('password') if self.options.get('purge_credentials'): self._session_attributes.set('beaker.session.id', None, expires=0) self._session_attributes.set('expires', None, expires=0) self._session_attributes.set('power_guide_hash', None, expires=0) self._session_attributes.set('pzuid', None, expires=0) self._session_attributes.set('uuid', None, expires=0) self._authed = False self.logger.info('All credentials were successfully removed.') if not self._authed and (not email and not password): self.logger.error( 'A login for Zattoo is required, use --zattoo-email EMAIL --zattoo-password PASSWORD to set them' ) return if self._authed: if self._expires < time.time(): # login after 24h expires = time.time() + 3600 * 24 self._session_attributes.set('expires', expires, expires=3600 * 24) self._authed = False if not self._authed: __hello = self._hello() if not self._login(email, password, __hello): self.logger.error( 'Failed to login, check your username/password') return return self._watch()
class Zattoo(Plugin): API_CHANNELS = '{0}/zapi/v2/cached/channels/{1}?details=False' API_HELLO = '{0}/zapi/session/hello' API_LOGIN = '******' API_SESSION = '{0}/zapi/v2/session' API_WATCH = '{0}/zapi/watch' API_WATCH_REC = '{0}/zapi/watch/recording/{1}' API_WATCH_VOD = '{0}/zapi/avod/videos/{1}/watch' STREAMS_ZATTOO = ['dash', 'hls', 'hls5'] TIME_CONTROL = 60 * 60 * 2 TIME_SESSION = 60 * 60 * 24 * 30 _url_re = re.compile(r''' https?:// (?P<base_url> (?:(?: iptv\.glattvision|www\.(?:myvisiontv|saktv|vtxtv) )\.ch )|(?:(?: mobiltv\.quickline|www\.quantum-tv|zattoo )\.com )|(?:(?: tvonline\.ewe|nettv\.netcologne|tvplus\.m-net )\.de )|(?:(?: player\.waly|www\.netplus )\.tv) |www\.bbv-tv\.net |www\.meinewelt\.cc )/ (?: (?:ondemand/)?(?:watch/(?:[^/\s]+)(?:/[^/]+/(?P<recording_id>\d+))) | watch/(?P<channel>[^/\s]+) | ondemand/watch/(?P<vod_id>[^-]+)- ) ''', re.VERBOSE) _app_token_re = re.compile(r"""window\.appToken\s+=\s+'([^']+)'""") _channels_schema = validate.Schema({ 'success': bool, 'channel_groups': [{ 'channels': [ { 'display_alias': validate.text, 'cid': validate.text }, ] }]}, validate.get('channel_groups'), ) _session_schema = validate.Schema({ 'success': bool, 'session': { 'loggedin': bool } }, validate.get('session')) arguments = PluginArguments( PluginArgument( "email", requires=["password"], metavar="EMAIL", help=""" The email associated with your zattoo account, required to access any zattoo stream. """), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help=""" A zattoo account password to use with --zattoo-email. """), PluginArgument( "purge-credentials", action="store_true", help=""" Purge cached zattoo credentials to initiate a new session and reauthenticate. """), PluginArgument( 'stream-types', metavar='TYPES', type=comma_list_filter(STREAMS_ZATTOO), default=['hls'], help=''' A comma-delimited list of stream types which should be used, the following types are allowed: - {0} Default is "hls". '''.format('\n - '.join(STREAMS_ZATTOO)) ) ) def __init__(self, url): super(Zattoo, self).__init__(url) self.domain = self._url_re.match(url).group('base_url') self._session_attributes = Cache( filename='plugin-cache.json', key_prefix='zattoo:attributes:{0}'.format(self.domain)) self._uuid = self._session_attributes.get('uuid') self._authed = (self._session_attributes.get('power_guide_hash') and self._uuid and self.session.http.cookies.get( 'pzuid', domain=self.domain) and self.session.http.cookies.get( 'beaker.session.id', domain=self.domain) ) self._session_control = self._session_attributes.get('session_control', False) self.base_url = 'https://{0}'.format(self.domain) self.headers = { 'User-Agent': useragents.CHROME, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': self.base_url } @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def _hello(self): log.debug('_hello ...') # a new session is required for the app_token self.session.http.cookies = cookiejar_from_dict({}) res = self.session.http.get(self.base_url) match = self._app_token_re.search(res.text) app_token = match.group(1) hello_url = self.API_HELLO.format(self.base_url) if self._uuid: __uuid = self._uuid else: __uuid = str(uuid.uuid4()) self._session_attributes.set( 'uuid', __uuid, expires=self.TIME_SESSION) params = { 'client_app_token': app_token, 'uuid': __uuid, 'lang': 'en', 'format': 'json' } res = self.session.http.post(hello_url, headers=self.headers, data=params) def _login(self, email, password): log.debug('_login ... Attempting login as {0}'.format(email)) login_url = self.API_LOGIN.format(self.base_url) params = { 'login': email, 'password': password, 'remember': 'true' } try: res = self.session.http.post(login_url, headers=self.headers, data=params) except Exception as e: if '400 Client Error' in str(e): raise PluginError( 'Failed to login, check your username/password') raise e data = self.session.http.json(res) self._authed = data['success'] log.debug('New Session Data') self.save_cookies(default_expires=self.TIME_SESSION) self._session_attributes.set('power_guide_hash', data['session']['power_guide_hash'], expires=self.TIME_SESSION) self._session_attributes.set( 'session_control', True, expires=self.TIME_CONTROL) def _watch(self): log.debug('_watch ...') match = self._url_re.match(self.url) if not match: log.debug('_watch ... no match') return channel = match.group('channel') vod_id = match.group('vod_id') recording_id = match.group('recording_id') params = {'https_watch_urls': True} if channel: watch_url = self.API_WATCH.format(self.base_url) params_cid = self._get_params_cid(channel) if not params_cid: return params.update(params_cid) elif vod_id: log.debug('Found vod_id: {0}'.format(vod_id)) watch_url = self.API_WATCH_VOD.format(self.base_url, vod_id) elif recording_id: log.debug('Found recording_id: {0}'.format(recording_id)) watch_url = self.API_WATCH_REC.format(self.base_url, recording_id) else: log.debug('Missing watch_url') return zattoo_stream_types = self.get_option('stream-types') or ['hls'] for stream_type in zattoo_stream_types: params_stream_type = {'stream_type': stream_type} params.update(params_stream_type) try: res = self.session.http.post(watch_url, headers=self.headers, data=params) except Exception as e: if '404 Client Error' in str(e): log.error('Unfortunately streaming is not permitted in ' 'this country or this channel does not exist.') elif '402 Client Error: Payment Required' in str(e): log.error('Paid subscription required for this channel.') log.info('If paid subscription exist, use --zattoo-purge' '-credentials to start a new session.') elif '403 Client Error' in str(e): log.debug('Force session reset for watch_url') self.reset_session() else: log.error(str(e)) return data = self.session.http.json(res) log.debug('Found data for {0}'.format(stream_type)) if data['success'] and stream_type in ['hls', 'hls5']: for url in data['stream']['watch_urls']: for s in HLSStream.parse_variant_playlist( self.session, url['url']).items(): yield s elif data['success'] and stream_type == 'dash': for url in data['stream']['watch_urls']: for s in DASHStream.parse_manifest( self.session, url['url']).items(): yield s def _get_params_cid(self, channel): log.debug('get channel ID for {0}'.format(channel)) channels_url = self.API_CHANNELS.format( self.base_url, self._session_attributes.get('power_guide_hash')) try: res = self.session.http.get(channels_url, headers=self.headers) except Exception: log.debug('Force session reset for _get_params_cid') self.reset_session() return False data = self.session.http.json(res, schema=self._channels_schema) c_list = [] for d in data: for c in d['channels']: c_list.append(c) cid = [] zattoo_list = [] for c in c_list: zattoo_list.append(c['display_alias']) if c['display_alias'] == channel: cid = c['cid'] log.debug('Available zattoo channels in this country: {0}'.format( ', '.join(sorted(zattoo_list)))) if not cid: cid = channel log.debug('CHANNEL ID: {0}'.format(cid)) return {'cid': cid} def reset_session(self): self._session_attributes.set('power_guide_hash', None, expires=0) self._session_attributes.set('uuid', None, expires=0) self.clear_cookies() self._authed = False def _get_streams(self): email = self.get_option('email') password = self.get_option('password') if self.options.get('purge_credentials'): self.reset_session() log.info('All credentials were successfully removed.') elif (self._authed and not self._session_control): # check every two hours, if the session is actually valid log.debug('Session control for {0}'.format(self.domain)) res = self.session.http.get(self.API_SESSION.format(self.base_url)) res = self.session.http.json(res, schema=self._session_schema) if res['loggedin']: self._session_attributes.set( 'session_control', True, expires=self.TIME_CONTROL) else: log.debug('User is not logged in') self._authed = False if not self._authed and (not email and not password): log.error( 'A login for Zattoo is required, use --zattoo-email EMAIL' ' --zattoo-password PASSWORD to set them') return if not self._authed: self._hello() self._login(email, password) return self._watch()
class Mitele(Plugin): url_re = re.compile(r"https?://(?:www.)?mitele.es/directo/(\w+)") supported_channels = ("telecinco", "bemad", "boing", "cuatro") app_key = "56c3464fe4b0b8a18ac02511" session_url = "https://appgrid-api.cloud.accedo.tv/session" config_url = "https://appgrid-api.cloud.accedo.tv/metadata/general_configuration, web_configuration?" \ "sessionKey={key}" channel_id_url = "http://indalo.mediaset.es/mmc-player/api/mmc/v1/{channel}/live/flash.json" stream_info_url = "http://player.ooyala.com/sas/player_api/v2/authorization/embed_code/{key}/{yoo}?" \ "device=html5&domain=www.mitele.es" session_schema = validate.Schema({ "sessionKey": validate.text, "expiration": validate.transform( lambda d: datetime.strptime(d, "%Y%m%dT%H:%M:%S+0000")) }) config_schema = validate.Schema({ "general_configuration": { "api_configuration": { "ooyala_discovery": { "api_key": validate.text } } } }) channel_id_schema = validate.Schema( validate.all({"locations": [{ "yoo": validate.text }]}, validate.get("locations"), validate.get(0), validate.get("yoo"))) stream_info_schema = validate.Schema({ "authorization_data": { validate.text: { "authorized": bool, "message": validate.text, validate.optional("streams"): [{ "delivery_type": validate.text, "url": { "format": "encoded", "data": validate.all( validate.text, validate.transform(b64decode), validate.transform(lambda d: d.decode("utf8")), validate.url()) } }] } } }) def __init__(self, url): super(Mitele, self).__init__(url) self.cache = Cache("mitele.cache") @classmethod def can_handle_url(cls, url): m = cls.url_re.match(url) return m and m.group(1) in cls.supported_channels @property def session_key(self): """ Get a cached or new session key, uuid is a random uuid (type 4) :return: """ session_key = self.cache.get("sessionKey") if session_key: self.logger.debug("Using cached sessionKey") return session_key else: self.logger.debug("Requesting new sessionKey") uuid = uuid4() res = http.get(self.session_url, params=dict(appKey=self.app_key, uuid=uuid)) data = parse_json(res.text, schema=self.session_schema) # when to expire the sessionKey, -1 hour for good measure expires_in = (data["expiration"] - datetime.now()).total_seconds() - 3600 self.cache.set("sessionKey", data["sessionKey"], expires=expires_in) return data["sessionKey"] @property def config(self): """ Get the API config data """ config_res = http.get(self.config_url.format(key=self.session_key)) return parse_json(config_res.text, schema=self.config_schema) def get_channel_id(self, channel): """ Get the ID of the channel form the name :param channel: channel name :return: channel id """ channel_id_res = http.get(self.channel_id_url.format(channel=channel)) return parse_json(channel_id_res.text, schema=self.channel_id_schema) def get_stream_info(self, key, channel_id): """ Get details about the streams :param key: API key :param channel_id: channel id :return: stream info """ stream_info_res = http.get( self.stream_info_url.format(key=key, yoo=channel_id)) return parse_json(stream_info_res.text, schema=self.stream_info_schema) def _get_streams(self): channel = self.url_re.match(self.url).group(1) key, sig = self.config["general_configuration"]["api_configuration"][ "ooyala_discovery"]["api_key"].split(".") self.logger.debug("Got api key: {}.{}", key, sig) channel_id = self.get_channel_id(channel) self.logger.debug("Got channel ID {} for channel {}", channel_id, channel) data = self.get_stream_info(key, channel_id) stream_info = data["authorization_data"][channel_id] if stream_info["authorized"]: for stream in stream_info["streams"]: if stream["delivery_type"] == "hls": for s in HLSStream.parse_variant_playlist( self.session, stream["url"]["data"]).items(): yield s else: self.logger.error("Cannot load streams: {}", stream_info["message"])