Beispiel #1
0
 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")
Beispiel #2
0
 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)
Beispiel #3
0
    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
        }
Beispiel #4
0
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()
Beispiel #5
0
    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
Beispiel #6
0
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")
Beispiel #8
0
 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
     }
Beispiel #9
0
    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()
Beispiel #10
0
    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
        }
Beispiel #11
0
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()
Beispiel #12
0
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)
Beispiel #13
0
 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)
Beispiel #14
0
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)
Beispiel #15
0
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"]))
Beispiel #16
0
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"]))
Beispiel #17
0
 def __init__(self, url):
     super(Mitele, self).__init__(url)
     self.cache = Cache("mitele.cache")
Beispiel #18
0
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()
Beispiel #19
0
 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
Beispiel #20
0
 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")
Beispiel #21
0
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()
Beispiel #22
0
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()
Beispiel #23
0
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()
Beispiel #24
0
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"])